├── .dockerignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── COMMANDS.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── GETTING_STARTED.md ├── LICENSE.TXT ├── README.md ├── diesel.toml ├── migrations ├── .gitkeep ├── 00000000000000_diesel_initial_setup │ ├── down.sql │ └── up.sql ├── 2019-05-27-225104_tags │ ├── down.sql │ └── up.sql ├── 2019-08-19-210929_roles │ ├── down.sql │ └── up.sql ├── 2019-08-19-211002_messages │ ├── down.sql │ └── up.sql ├── 2019-08-22-231258_users │ ├── down.sql │ └── up.sql └── 2020-08-25-224210_bans │ ├── down.sql │ └── up.sql ├── postgres-docker └── Dockerfile └── src ├── api.rs ├── ban.rs ├── command_history.rs ├── commands.rs ├── crates.rs ├── db.rs ├── jobs.rs ├── main.rs ├── playground.rs ├── schema.rs ├── state_machine.rs ├── tags.rs ├── text.rs └── welcome.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | env: 5 | AWS_ACCESS_KEY_ID: AKIA46X5W6CZNZLSA3AD 6 | 7 | jobs: 8 | ci: 9 | name: CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Checkout the source code 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 1 17 | 18 | - name: Install Rust 19 | run: rustup update stable && rustup default stable 20 | 21 | - name: Check formatting 22 | run: cargo fmt --check 23 | 24 | - name: Run the test suite 25 | run: cargo test 26 | 27 | - name: Build the Docker container 28 | run: docker build -t discord-mods-bot . 29 | 30 | - name: Deploy to production 31 | uses: rust-lang/simpleinfra/github-actions/upload-docker-image@master 32 | with: 33 | image: discord-mods-bot 34 | repository: discord-mods-bot 35 | region: us-west-1 36 | redeploy_ecs_cluster: rust-ecs-prod 37 | redeploy_ecs_service: discord-mods-bot 38 | aws_access_key_id: "${{ env.AWS_ACCESS_KEY_ID }}" 39 | aws_secret_access_key: "${{ secrets.AWS_SECRET_ACCESS_KEY }}" 40 | if: github.ref == 'refs/heads/master' 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /COMMANDS.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | Commands for the bot are managed using the `Commands` struct. The `.add` method 3 | is used to define commands for the bot to react to. 4 | 5 | ## Defining Commands 6 | Commands are defined using a string and a function. The string is the command 7 | that the user must type and the function is the handler for that command. 8 | 9 | An example command using an anonymous function as a handler. 10 | ```rust 11 | let mut cmds = Commands::new(); 12 | 13 | cmds.add("?greet {name}", |args: Args<'_>| -> Result { 14 | println!("Hello {}!", args.params.get("name").unwrap()); 15 | }); 16 | ``` 17 | 18 | The same command using a function pointer as a handler. 19 | ```rust 20 | fn print_hello_name(args: Args) -> Result { 21 | println!("Hello {}!", args.params.get("name").unwrap()); 22 | }; 23 | 24 | let mut cmds = Commands::new(); 25 | cmds.add("?greet {name}", print_hello_name); 26 | ``` 27 | 28 | ## Command Syntax 29 | Commands use a syntax with 3 different kinds of elements that can be used 30 | together. 31 | 32 | + Static elements must be matched exactly, these are strings like `?talk` and 33 | `!ban`. 34 | + Dynamic elements match any input other than a space. To use a dynamic 35 | element in your command use `{key}` where `key` can be any name that 36 | represents that particular input element. 37 | + Quoted elements match any input but must be surrounded by quotes. To use a 38 | quoted element in your command use `[key]` where `key` can be any name that 39 | represents that particular input element. 40 | 41 | ## Command Handlers 42 | Functions are used as handlers for commands. Specifically handler functions 43 | must use the following signature: `(Args<'_>) -> Result`. 44 | 45 | ## Args 46 | The `Args` type encapsulated the parameters extracted from the input as well as 47 | the `Message` and `Context` types from the Serenity crate. 48 | 49 | ### Serenity 50 | The library the bot uses to communicate with discord is called Serenity. 51 | Serenity abstracts all communication with discord into methods available through 52 | the `Message` and `Context` types. 53 | 54 | -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.7.8" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 25 | dependencies = [ 26 | "getrandom 0.2.15", 27 | "once_cell", 28 | "version_check", 29 | ] 30 | 31 | [[package]] 32 | name = "android-tzdata" 33 | version = "0.1.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 36 | 37 | [[package]] 38 | name = "android_system_properties" 39 | version = "0.1.5" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 42 | dependencies = [ 43 | "libc", 44 | ] 45 | 46 | [[package]] 47 | name = "async-trait" 48 | version = "0.1.86" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" 51 | dependencies = [ 52 | "proc-macro2", 53 | "quote", 54 | "syn 2.0.98", 55 | ] 56 | 57 | [[package]] 58 | name = "async-tungstenite" 59 | version = "0.17.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" 62 | dependencies = [ 63 | "futures-io", 64 | "futures-util", 65 | "log", 66 | "pin-project-lite", 67 | "tokio", 68 | "tokio-rustls 0.23.4", 69 | "tungstenite", 70 | "webpki-roots 0.22.6", 71 | ] 72 | 73 | [[package]] 74 | name = "atoi" 75 | version = "0.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" 78 | dependencies = [ 79 | "num-traits", 80 | ] 81 | 82 | [[package]] 83 | name = "autocfg" 84 | version = "1.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 87 | 88 | [[package]] 89 | name = "backtrace" 90 | version = "0.3.74" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 93 | dependencies = [ 94 | "addr2line", 95 | "cfg-if", 96 | "libc", 97 | "miniz_oxide", 98 | "object", 99 | "rustc-demangle", 100 | "windows-targets 0.52.6", 101 | ] 102 | 103 | [[package]] 104 | name = "base64" 105 | version = "0.13.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 108 | 109 | [[package]] 110 | name = "base64" 111 | version = "0.21.7" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 114 | 115 | [[package]] 116 | name = "bitflags" 117 | version = "1.3.2" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 120 | 121 | [[package]] 122 | name = "bitflags" 123 | version = "2.8.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 126 | 127 | [[package]] 128 | name = "block-buffer" 129 | version = "0.10.4" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 132 | dependencies = [ 133 | "generic-array", 134 | ] 135 | 136 | [[package]] 137 | name = "bumpalo" 138 | version = "3.17.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 141 | 142 | [[package]] 143 | name = "byteorder" 144 | version = "1.5.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 147 | 148 | [[package]] 149 | name = "bytes" 150 | version = "1.10.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 153 | 154 | [[package]] 155 | name = "cc" 156 | version = "1.2.14" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" 159 | dependencies = [ 160 | "shlex", 161 | ] 162 | 163 | [[package]] 164 | name = "cfg-if" 165 | version = "1.0.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 168 | 169 | [[package]] 170 | name = "chrono" 171 | version = "0.4.39" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" 174 | dependencies = [ 175 | "android-tzdata", 176 | "iana-time-zone", 177 | "num-traits", 178 | "serde", 179 | "windows-targets 0.52.6", 180 | ] 181 | 182 | [[package]] 183 | name = "core-foundation" 184 | version = "0.9.4" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 187 | dependencies = [ 188 | "core-foundation-sys", 189 | "libc", 190 | ] 191 | 192 | [[package]] 193 | name = "core-foundation-sys" 194 | version = "0.8.7" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 197 | 198 | [[package]] 199 | name = "cpufeatures" 200 | version = "0.2.17" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 203 | dependencies = [ 204 | "libc", 205 | ] 206 | 207 | [[package]] 208 | name = "crc" 209 | version = "2.1.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" 212 | dependencies = [ 213 | "crc-catalog", 214 | ] 215 | 216 | [[package]] 217 | name = "crc-catalog" 218 | version = "1.1.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" 221 | 222 | [[package]] 223 | name = "crc32fast" 224 | version = "1.4.2" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 227 | dependencies = [ 228 | "cfg-if", 229 | ] 230 | 231 | [[package]] 232 | name = "crossbeam-queue" 233 | version = "0.3.12" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 236 | dependencies = [ 237 | "crossbeam-utils", 238 | ] 239 | 240 | [[package]] 241 | name = "crossbeam-utils" 242 | version = "0.8.21" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 245 | 246 | [[package]] 247 | name = "crypto-common" 248 | version = "0.1.6" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 251 | dependencies = [ 252 | "generic-array", 253 | "typenum", 254 | ] 255 | 256 | [[package]] 257 | name = "dashmap" 258 | version = "5.5.3" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 261 | dependencies = [ 262 | "cfg-if", 263 | "hashbrown 0.14.5", 264 | "lock_api", 265 | "once_cell", 266 | "parking_lot_core 0.9.10", 267 | "serde", 268 | ] 269 | 270 | [[package]] 271 | name = "deranged" 272 | version = "0.3.11" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 275 | dependencies = [ 276 | "powerfmt", 277 | "serde", 278 | ] 279 | 280 | [[package]] 281 | name = "diesel" 282 | version = "1.4.8" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" 285 | dependencies = [ 286 | "bitflags 1.3.2", 287 | "byteorder", 288 | "diesel_derives", 289 | "pq-sys", 290 | ] 291 | 292 | [[package]] 293 | name = "diesel_derives" 294 | version = "1.4.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" 297 | dependencies = [ 298 | "proc-macro2", 299 | "quote", 300 | "syn 1.0.109", 301 | ] 302 | 303 | [[package]] 304 | name = "diesel_migrations" 305 | version = "1.4.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c" 308 | dependencies = [ 309 | "migrations_internals", 310 | "migrations_macros", 311 | ] 312 | 313 | [[package]] 314 | name = "digest" 315 | version = "0.10.7" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 318 | dependencies = [ 319 | "block-buffer", 320 | "crypto-common", 321 | "subtle", 322 | ] 323 | 324 | [[package]] 325 | name = "dirs" 326 | version = "4.0.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 329 | dependencies = [ 330 | "dirs-sys", 331 | ] 332 | 333 | [[package]] 334 | name = "dirs-sys" 335 | version = "0.3.7" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 338 | dependencies = [ 339 | "libc", 340 | "redox_users", 341 | "winapi", 342 | ] 343 | 344 | [[package]] 345 | name = "displaydoc" 346 | version = "0.2.5" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 349 | dependencies = [ 350 | "proc-macro2", 351 | "quote", 352 | "syn 2.0.98", 353 | ] 354 | 355 | [[package]] 356 | name = "dotenv" 357 | version = "0.15.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 360 | 361 | [[package]] 362 | name = "either" 363 | version = "1.13.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 366 | 367 | [[package]] 368 | name = "encoding_rs" 369 | version = "0.8.35" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 372 | dependencies = [ 373 | "cfg-if", 374 | ] 375 | 376 | [[package]] 377 | name = "envy" 378 | version = "0.4.2" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" 381 | dependencies = [ 382 | "serde", 383 | ] 384 | 385 | [[package]] 386 | name = "equivalent" 387 | version = "1.0.2" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 390 | 391 | [[package]] 392 | name = "errno" 393 | version = "0.3.10" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 396 | dependencies = [ 397 | "libc", 398 | "windows-sys 0.59.0", 399 | ] 400 | 401 | [[package]] 402 | name = "event-listener" 403 | version = "2.5.3" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 406 | 407 | [[package]] 408 | name = "fastrand" 409 | version = "2.3.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 412 | 413 | [[package]] 414 | name = "flate2" 415 | version = "1.0.35" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 418 | dependencies = [ 419 | "crc32fast", 420 | "miniz_oxide", 421 | ] 422 | 423 | [[package]] 424 | name = "fnv" 425 | version = "1.0.7" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 428 | 429 | [[package]] 430 | name = "foreign-types" 431 | version = "0.3.2" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 434 | dependencies = [ 435 | "foreign-types-shared", 436 | ] 437 | 438 | [[package]] 439 | name = "foreign-types-shared" 440 | version = "0.1.1" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 443 | 444 | [[package]] 445 | name = "form_urlencoded" 446 | version = "1.2.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 449 | dependencies = [ 450 | "percent-encoding", 451 | ] 452 | 453 | [[package]] 454 | name = "futures" 455 | version = "0.3.31" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 458 | dependencies = [ 459 | "futures-channel", 460 | "futures-core", 461 | "futures-executor", 462 | "futures-io", 463 | "futures-sink", 464 | "futures-task", 465 | "futures-util", 466 | ] 467 | 468 | [[package]] 469 | name = "futures-channel" 470 | version = "0.3.31" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 473 | dependencies = [ 474 | "futures-core", 475 | "futures-sink", 476 | ] 477 | 478 | [[package]] 479 | name = "futures-core" 480 | version = "0.3.31" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 483 | 484 | [[package]] 485 | name = "futures-executor" 486 | version = "0.3.31" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 489 | dependencies = [ 490 | "futures-core", 491 | "futures-task", 492 | "futures-util", 493 | ] 494 | 495 | [[package]] 496 | name = "futures-intrusive" 497 | version = "0.4.2" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" 500 | dependencies = [ 501 | "futures-core", 502 | "lock_api", 503 | "parking_lot 0.11.2", 504 | ] 505 | 506 | [[package]] 507 | name = "futures-io" 508 | version = "0.3.31" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 511 | 512 | [[package]] 513 | name = "futures-macro" 514 | version = "0.3.31" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 517 | dependencies = [ 518 | "proc-macro2", 519 | "quote", 520 | "syn 2.0.98", 521 | ] 522 | 523 | [[package]] 524 | name = "futures-sink" 525 | version = "0.3.31" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 528 | 529 | [[package]] 530 | name = "futures-task" 531 | version = "0.3.31" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 534 | 535 | [[package]] 536 | name = "futures-util" 537 | version = "0.3.31" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 540 | dependencies = [ 541 | "futures-channel", 542 | "futures-core", 543 | "futures-io", 544 | "futures-macro", 545 | "futures-sink", 546 | "futures-task", 547 | "memchr", 548 | "pin-project-lite", 549 | "pin-utils", 550 | "slab", 551 | ] 552 | 553 | [[package]] 554 | name = "generic-array" 555 | version = "0.14.7" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 558 | dependencies = [ 559 | "typenum", 560 | "version_check", 561 | ] 562 | 563 | [[package]] 564 | name = "getrandom" 565 | version = "0.2.15" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 568 | dependencies = [ 569 | "cfg-if", 570 | "libc", 571 | "wasi 0.11.0+wasi-snapshot-preview1", 572 | ] 573 | 574 | [[package]] 575 | name = "getrandom" 576 | version = "0.3.1" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 579 | dependencies = [ 580 | "cfg-if", 581 | "libc", 582 | "wasi 0.13.3+wasi-0.2.2", 583 | "windows-targets 0.52.6", 584 | ] 585 | 586 | [[package]] 587 | name = "gimli" 588 | version = "0.31.1" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 591 | 592 | [[package]] 593 | name = "h2" 594 | version = "0.3.26" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 597 | dependencies = [ 598 | "bytes", 599 | "fnv", 600 | "futures-core", 601 | "futures-sink", 602 | "futures-util", 603 | "http", 604 | "indexmap 2.7.1", 605 | "slab", 606 | "tokio", 607 | "tokio-util", 608 | "tracing", 609 | ] 610 | 611 | [[package]] 612 | name = "hashbrown" 613 | version = "0.11.2" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 616 | dependencies = [ 617 | "ahash", 618 | ] 619 | 620 | [[package]] 621 | name = "hashbrown" 622 | version = "0.12.3" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 625 | 626 | [[package]] 627 | name = "hashbrown" 628 | version = "0.14.5" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 631 | 632 | [[package]] 633 | name = "hashbrown" 634 | version = "0.15.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 637 | 638 | [[package]] 639 | name = "hashlink" 640 | version = "0.7.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" 643 | dependencies = [ 644 | "hashbrown 0.11.2", 645 | ] 646 | 647 | [[package]] 648 | name = "heck" 649 | version = "0.4.1" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 652 | dependencies = [ 653 | "unicode-segmentation", 654 | ] 655 | 656 | [[package]] 657 | name = "hex" 658 | version = "0.4.3" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 661 | 662 | [[package]] 663 | name = "hkdf" 664 | version = "0.12.4" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 667 | dependencies = [ 668 | "hmac", 669 | ] 670 | 671 | [[package]] 672 | name = "hmac" 673 | version = "0.12.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 676 | dependencies = [ 677 | "digest", 678 | ] 679 | 680 | [[package]] 681 | name = "http" 682 | version = "0.2.12" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 685 | dependencies = [ 686 | "bytes", 687 | "fnv", 688 | "itoa", 689 | ] 690 | 691 | [[package]] 692 | name = "http-body" 693 | version = "0.4.6" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 696 | dependencies = [ 697 | "bytes", 698 | "http", 699 | "pin-project-lite", 700 | ] 701 | 702 | [[package]] 703 | name = "httparse" 704 | version = "1.10.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" 707 | 708 | [[package]] 709 | name = "httpdate" 710 | version = "1.0.3" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 713 | 714 | [[package]] 715 | name = "hyper" 716 | version = "0.14.32" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 719 | dependencies = [ 720 | "bytes", 721 | "futures-channel", 722 | "futures-core", 723 | "futures-util", 724 | "h2", 725 | "http", 726 | "http-body", 727 | "httparse", 728 | "httpdate", 729 | "itoa", 730 | "pin-project-lite", 731 | "socket2", 732 | "tokio", 733 | "tower-service", 734 | "tracing", 735 | "want", 736 | ] 737 | 738 | [[package]] 739 | name = "hyper-rustls" 740 | version = "0.24.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 743 | dependencies = [ 744 | "futures-util", 745 | "http", 746 | "hyper", 747 | "rustls 0.21.12", 748 | "tokio", 749 | "tokio-rustls 0.24.1", 750 | ] 751 | 752 | [[package]] 753 | name = "hyper-tls" 754 | version = "0.5.0" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 757 | dependencies = [ 758 | "bytes", 759 | "hyper", 760 | "native-tls", 761 | "tokio", 762 | "tokio-native-tls", 763 | ] 764 | 765 | [[package]] 766 | name = "iana-time-zone" 767 | version = "0.1.61" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 770 | dependencies = [ 771 | "android_system_properties", 772 | "core-foundation-sys", 773 | "iana-time-zone-haiku", 774 | "js-sys", 775 | "wasm-bindgen", 776 | "windows-core", 777 | ] 778 | 779 | [[package]] 780 | name = "iana-time-zone-haiku" 781 | version = "0.1.2" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 784 | dependencies = [ 785 | "cc", 786 | ] 787 | 788 | [[package]] 789 | name = "icu_collections" 790 | version = "1.5.0" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 793 | dependencies = [ 794 | "displaydoc", 795 | "yoke", 796 | "zerofrom", 797 | "zerovec", 798 | ] 799 | 800 | [[package]] 801 | name = "icu_locid" 802 | version = "1.5.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 805 | dependencies = [ 806 | "displaydoc", 807 | "litemap", 808 | "tinystr", 809 | "writeable", 810 | "zerovec", 811 | ] 812 | 813 | [[package]] 814 | name = "icu_locid_transform" 815 | version = "1.5.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 818 | dependencies = [ 819 | "displaydoc", 820 | "icu_locid", 821 | "icu_locid_transform_data", 822 | "icu_provider", 823 | "tinystr", 824 | "zerovec", 825 | ] 826 | 827 | [[package]] 828 | name = "icu_locid_transform_data" 829 | version = "1.5.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 832 | 833 | [[package]] 834 | name = "icu_normalizer" 835 | version = "1.5.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 838 | dependencies = [ 839 | "displaydoc", 840 | "icu_collections", 841 | "icu_normalizer_data", 842 | "icu_properties", 843 | "icu_provider", 844 | "smallvec", 845 | "utf16_iter", 846 | "utf8_iter", 847 | "write16", 848 | "zerovec", 849 | ] 850 | 851 | [[package]] 852 | name = "icu_normalizer_data" 853 | version = "1.5.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 856 | 857 | [[package]] 858 | name = "icu_properties" 859 | version = "1.5.1" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 862 | dependencies = [ 863 | "displaydoc", 864 | "icu_collections", 865 | "icu_locid_transform", 866 | "icu_properties_data", 867 | "icu_provider", 868 | "tinystr", 869 | "zerovec", 870 | ] 871 | 872 | [[package]] 873 | name = "icu_properties_data" 874 | version = "1.5.0" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 877 | 878 | [[package]] 879 | name = "icu_provider" 880 | version = "1.5.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 883 | dependencies = [ 884 | "displaydoc", 885 | "icu_locid", 886 | "icu_provider_macros", 887 | "stable_deref_trait", 888 | "tinystr", 889 | "writeable", 890 | "yoke", 891 | "zerofrom", 892 | "zerovec", 893 | ] 894 | 895 | [[package]] 896 | name = "icu_provider_macros" 897 | version = "1.5.0" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 900 | dependencies = [ 901 | "proc-macro2", 902 | "quote", 903 | "syn 2.0.98", 904 | ] 905 | 906 | [[package]] 907 | name = "idna" 908 | version = "1.0.3" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 911 | dependencies = [ 912 | "idna_adapter", 913 | "smallvec", 914 | "utf8_iter", 915 | ] 916 | 917 | [[package]] 918 | name = "idna_adapter" 919 | version = "1.2.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 922 | dependencies = [ 923 | "icu_normalizer", 924 | "icu_properties", 925 | ] 926 | 927 | [[package]] 928 | name = "indexmap" 929 | version = "1.9.3" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 932 | dependencies = [ 933 | "autocfg", 934 | "hashbrown 0.12.3", 935 | ] 936 | 937 | [[package]] 938 | name = "indexmap" 939 | version = "2.7.1" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 942 | dependencies = [ 943 | "equivalent", 944 | "hashbrown 0.15.2", 945 | ] 946 | 947 | [[package]] 948 | name = "instant" 949 | version = "0.1.13" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 952 | dependencies = [ 953 | "cfg-if", 954 | ] 955 | 956 | [[package]] 957 | name = "ipnet" 958 | version = "2.11.0" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 961 | 962 | [[package]] 963 | name = "itertools" 964 | version = "0.10.5" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 967 | dependencies = [ 968 | "either", 969 | ] 970 | 971 | [[package]] 972 | name = "itoa" 973 | version = "1.0.14" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 976 | 977 | [[package]] 978 | name = "js-sys" 979 | version = "0.3.77" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 982 | dependencies = [ 983 | "once_cell", 984 | "wasm-bindgen", 985 | ] 986 | 987 | [[package]] 988 | name = "lazy_static" 989 | version = "1.5.0" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 992 | 993 | [[package]] 994 | name = "libc" 995 | version = "0.2.169" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 998 | 999 | [[package]] 1000 | name = "libredox" 1001 | version = "0.1.3" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1004 | dependencies = [ 1005 | "bitflags 2.8.0", 1006 | "libc", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "linux-raw-sys" 1011 | version = "0.4.15" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1014 | 1015 | [[package]] 1016 | name = "litemap" 1017 | version = "0.7.4" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 1020 | 1021 | [[package]] 1022 | name = "lock_api" 1023 | version = "0.4.12" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1026 | dependencies = [ 1027 | "autocfg", 1028 | "scopeguard", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "log" 1033 | version = "0.4.25" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 1036 | 1037 | [[package]] 1038 | name = "md-5" 1039 | version = "0.10.6" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1042 | dependencies = [ 1043 | "cfg-if", 1044 | "digest", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "memchr" 1049 | version = "2.7.4" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1052 | 1053 | [[package]] 1054 | name = "migrations_internals" 1055 | version = "1.4.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860" 1058 | dependencies = [ 1059 | "diesel", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "migrations_macros" 1064 | version = "1.4.2" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" 1067 | dependencies = [ 1068 | "migrations_internals", 1069 | "proc-macro2", 1070 | "quote", 1071 | "syn 1.0.109", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "mime" 1076 | version = "0.3.17" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1079 | 1080 | [[package]] 1081 | name = "mime_guess" 1082 | version = "2.0.5" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1085 | dependencies = [ 1086 | "mime", 1087 | "unicase", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "minimal-lexical" 1092 | version = "0.2.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1095 | 1096 | [[package]] 1097 | name = "miniz_oxide" 1098 | version = "0.8.4" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" 1101 | dependencies = [ 1102 | "adler2", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "mio" 1107 | version = "1.0.3" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1110 | dependencies = [ 1111 | "libc", 1112 | "wasi 0.11.0+wasi-snapshot-preview1", 1113 | "windows-sys 0.52.0", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "native-tls" 1118 | version = "0.2.13" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" 1121 | dependencies = [ 1122 | "libc", 1123 | "log", 1124 | "openssl", 1125 | "openssl-probe", 1126 | "openssl-sys", 1127 | "schannel", 1128 | "security-framework", 1129 | "security-framework-sys", 1130 | "tempfile", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "nom" 1135 | version = "7.1.3" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1138 | dependencies = [ 1139 | "memchr", 1140 | "minimal-lexical", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "nu-ansi-term" 1145 | version = "0.46.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1148 | dependencies = [ 1149 | "overload", 1150 | "winapi", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "num-conv" 1155 | version = "0.1.0" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1158 | 1159 | [[package]] 1160 | name = "num-traits" 1161 | version = "0.2.19" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1164 | dependencies = [ 1165 | "autocfg", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "object" 1170 | version = "0.36.7" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1173 | dependencies = [ 1174 | "memchr", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "once_cell" 1179 | version = "1.20.3" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1182 | 1183 | [[package]] 1184 | name = "openssl" 1185 | version = "0.10.71" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" 1188 | dependencies = [ 1189 | "bitflags 2.8.0", 1190 | "cfg-if", 1191 | "foreign-types", 1192 | "libc", 1193 | "once_cell", 1194 | "openssl-macros", 1195 | "openssl-sys", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "openssl-macros" 1200 | version = "0.1.1" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1203 | dependencies = [ 1204 | "proc-macro2", 1205 | "quote", 1206 | "syn 2.0.98", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "openssl-probe" 1211 | version = "0.1.6" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1214 | 1215 | [[package]] 1216 | name = "openssl-sys" 1217 | version = "0.9.106" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" 1220 | dependencies = [ 1221 | "cc", 1222 | "libc", 1223 | "pkg-config", 1224 | "vcpkg", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "ordered-float" 1229 | version = "2.10.1" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" 1232 | dependencies = [ 1233 | "num-traits", 1234 | ] 1235 | 1236 | [[package]] 1237 | name = "overload" 1238 | version = "0.1.1" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1241 | 1242 | [[package]] 1243 | name = "parking_lot" 1244 | version = "0.11.2" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1247 | dependencies = [ 1248 | "instant", 1249 | "lock_api", 1250 | "parking_lot_core 0.8.6", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "parking_lot" 1255 | version = "0.12.3" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1258 | dependencies = [ 1259 | "lock_api", 1260 | "parking_lot_core 0.9.10", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "parking_lot_core" 1265 | version = "0.8.6" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 1268 | dependencies = [ 1269 | "cfg-if", 1270 | "instant", 1271 | "libc", 1272 | "redox_syscall 0.2.16", 1273 | "smallvec", 1274 | "winapi", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "parking_lot_core" 1279 | version = "0.9.10" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1282 | dependencies = [ 1283 | "cfg-if", 1284 | "libc", 1285 | "redox_syscall 0.5.8", 1286 | "smallvec", 1287 | "windows-targets 0.52.6", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "paste" 1292 | version = "1.0.15" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1295 | 1296 | [[package]] 1297 | name = "percent-encoding" 1298 | version = "2.3.1" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1301 | 1302 | [[package]] 1303 | name = "pin-project-lite" 1304 | version = "0.2.16" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1307 | 1308 | [[package]] 1309 | name = "pin-utils" 1310 | version = "0.1.0" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1313 | 1314 | [[package]] 1315 | name = "pkg-config" 1316 | version = "0.3.31" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1319 | 1320 | [[package]] 1321 | name = "powerfmt" 1322 | version = "0.2.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1325 | 1326 | [[package]] 1327 | name = "ppv-lite86" 1328 | version = "0.2.20" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1331 | dependencies = [ 1332 | "zerocopy", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "pq-sys" 1337 | version = "0.4.8" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" 1340 | dependencies = [ 1341 | "vcpkg", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "proc-macro2" 1346 | version = "1.0.93" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1349 | dependencies = [ 1350 | "unicode-ident", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "quote" 1355 | version = "1.0.38" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1358 | dependencies = [ 1359 | "proc-macro2", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "rand" 1364 | version = "0.8.5" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1367 | dependencies = [ 1368 | "libc", 1369 | "rand_chacha", 1370 | "rand_core", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "rand_chacha" 1375 | version = "0.3.1" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1378 | dependencies = [ 1379 | "ppv-lite86", 1380 | "rand_core", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "rand_core" 1385 | version = "0.6.4" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1388 | dependencies = [ 1389 | "getrandom 0.2.15", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "redox_syscall" 1394 | version = "0.2.16" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1397 | dependencies = [ 1398 | "bitflags 1.3.2", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "redox_syscall" 1403 | version = "0.5.8" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1406 | dependencies = [ 1407 | "bitflags 2.8.0", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "redox_users" 1412 | version = "0.4.6" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1415 | dependencies = [ 1416 | "getrandom 0.2.15", 1417 | "libredox", 1418 | "thiserror", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "reqwest" 1423 | version = "0.11.27" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1426 | dependencies = [ 1427 | "base64 0.21.7", 1428 | "bytes", 1429 | "encoding_rs", 1430 | "futures-core", 1431 | "futures-util", 1432 | "h2", 1433 | "http", 1434 | "http-body", 1435 | "hyper", 1436 | "hyper-rustls", 1437 | "hyper-tls", 1438 | "ipnet", 1439 | "js-sys", 1440 | "log", 1441 | "mime", 1442 | "mime_guess", 1443 | "native-tls", 1444 | "once_cell", 1445 | "percent-encoding", 1446 | "pin-project-lite", 1447 | "rustls 0.21.12", 1448 | "rustls-pemfile", 1449 | "serde", 1450 | "serde_json", 1451 | "serde_urlencoded", 1452 | "sync_wrapper", 1453 | "system-configuration", 1454 | "tokio", 1455 | "tokio-native-tls", 1456 | "tokio-rustls 0.24.1", 1457 | "tokio-util", 1458 | "tower-service", 1459 | "url", 1460 | "wasm-bindgen", 1461 | "wasm-bindgen-futures", 1462 | "wasm-streams", 1463 | "web-sys", 1464 | "webpki-roots 0.25.4", 1465 | "winreg", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "ring" 1470 | version = "0.16.20" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1473 | dependencies = [ 1474 | "cc", 1475 | "libc", 1476 | "once_cell", 1477 | "spin", 1478 | "untrusted 0.7.1", 1479 | "web-sys", 1480 | "winapi", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "ring" 1485 | version = "0.17.9" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" 1488 | dependencies = [ 1489 | "cc", 1490 | "cfg-if", 1491 | "getrandom 0.2.15", 1492 | "libc", 1493 | "untrusted 0.9.0", 1494 | "windows-sys 0.52.0", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "rustc-demangle" 1499 | version = "0.1.24" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1502 | 1503 | [[package]] 1504 | name = "rustix" 1505 | version = "0.38.44" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1508 | dependencies = [ 1509 | "bitflags 2.8.0", 1510 | "errno", 1511 | "libc", 1512 | "linux-raw-sys", 1513 | "windows-sys 0.59.0", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "rustlang_discord_mod_bot" 1518 | version = "0.1.0" 1519 | dependencies = [ 1520 | "diesel", 1521 | "diesel_migrations", 1522 | "envy", 1523 | "futures", 1524 | "indexmap 1.9.3", 1525 | "reqwest", 1526 | "serde", 1527 | "serde_derive", 1528 | "serenity", 1529 | "sqlx", 1530 | "tokio", 1531 | "tracing", 1532 | "tracing-subscriber", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "rustls" 1537 | version = "0.20.9" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" 1540 | dependencies = [ 1541 | "log", 1542 | "ring 0.16.20", 1543 | "sct", 1544 | "webpki", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "rustls" 1549 | version = "0.21.12" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 1552 | dependencies = [ 1553 | "log", 1554 | "ring 0.17.9", 1555 | "rustls-webpki", 1556 | "sct", 1557 | ] 1558 | 1559 | [[package]] 1560 | name = "rustls-pemfile" 1561 | version = "1.0.4" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1564 | dependencies = [ 1565 | "base64 0.21.7", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "rustls-webpki" 1570 | version = "0.101.7" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1573 | dependencies = [ 1574 | "ring 0.17.9", 1575 | "untrusted 0.9.0", 1576 | ] 1577 | 1578 | [[package]] 1579 | name = "rustversion" 1580 | version = "1.0.19" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1583 | 1584 | [[package]] 1585 | name = "ryu" 1586 | version = "1.0.19" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 1589 | 1590 | [[package]] 1591 | name = "schannel" 1592 | version = "0.1.27" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1595 | dependencies = [ 1596 | "windows-sys 0.59.0", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "scopeguard" 1601 | version = "1.2.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1604 | 1605 | [[package]] 1606 | name = "sct" 1607 | version = "0.7.1" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 1610 | dependencies = [ 1611 | "ring 0.17.9", 1612 | "untrusted 0.9.0", 1613 | ] 1614 | 1615 | [[package]] 1616 | name = "security-framework" 1617 | version = "2.11.1" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1620 | dependencies = [ 1621 | "bitflags 2.8.0", 1622 | "core-foundation", 1623 | "core-foundation-sys", 1624 | "libc", 1625 | "security-framework-sys", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "security-framework-sys" 1630 | version = "2.14.0" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1633 | dependencies = [ 1634 | "core-foundation-sys", 1635 | "libc", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "serde" 1640 | version = "1.0.217" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1643 | dependencies = [ 1644 | "serde_derive", 1645 | ] 1646 | 1647 | [[package]] 1648 | name = "serde-value" 1649 | version = "0.7.0" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 1652 | dependencies = [ 1653 | "ordered-float", 1654 | "serde", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "serde_derive" 1659 | version = "1.0.217" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1662 | dependencies = [ 1663 | "proc-macro2", 1664 | "quote", 1665 | "syn 2.0.98", 1666 | ] 1667 | 1668 | [[package]] 1669 | name = "serde_json" 1670 | version = "1.0.138" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 1673 | dependencies = [ 1674 | "itoa", 1675 | "memchr", 1676 | "ryu", 1677 | "serde", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "serde_urlencoded" 1682 | version = "0.7.1" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1685 | dependencies = [ 1686 | "form_urlencoded", 1687 | "itoa", 1688 | "ryu", 1689 | "serde", 1690 | ] 1691 | 1692 | [[package]] 1693 | name = "serenity" 1694 | version = "0.11.7" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6" 1697 | dependencies = [ 1698 | "async-trait", 1699 | "async-tungstenite", 1700 | "base64 0.21.7", 1701 | "bitflags 1.3.2", 1702 | "bytes", 1703 | "cfg-if", 1704 | "chrono", 1705 | "dashmap", 1706 | "flate2", 1707 | "futures", 1708 | "mime", 1709 | "mime_guess", 1710 | "parking_lot 0.12.3", 1711 | "percent-encoding", 1712 | "reqwest", 1713 | "serde", 1714 | "serde-value", 1715 | "serde_json", 1716 | "time", 1717 | "tokio", 1718 | "tracing", 1719 | "typemap_rev", 1720 | "url", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "sha-1" 1725 | version = "0.10.1" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" 1728 | dependencies = [ 1729 | "cfg-if", 1730 | "cpufeatures", 1731 | "digest", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "sha2" 1736 | version = "0.10.8" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1739 | dependencies = [ 1740 | "cfg-if", 1741 | "cpufeatures", 1742 | "digest", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "sharded-slab" 1747 | version = "0.1.7" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1750 | dependencies = [ 1751 | "lazy_static", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "shlex" 1756 | version = "1.3.0" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1759 | 1760 | [[package]] 1761 | name = "slab" 1762 | version = "0.4.9" 1763 | source = "registry+https://github.com/rust-lang/crates.io-index" 1764 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1765 | dependencies = [ 1766 | "autocfg", 1767 | ] 1768 | 1769 | [[package]] 1770 | name = "smallvec" 1771 | version = "1.14.0" 1772 | source = "registry+https://github.com/rust-lang/crates.io-index" 1773 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1774 | 1775 | [[package]] 1776 | name = "socket2" 1777 | version = "0.5.8" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1780 | dependencies = [ 1781 | "libc", 1782 | "windows-sys 0.52.0", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "spin" 1787 | version = "0.5.2" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1790 | 1791 | [[package]] 1792 | name = "sqlformat" 1793 | version = "0.1.8" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" 1796 | dependencies = [ 1797 | "itertools", 1798 | "nom", 1799 | "unicode_categories", 1800 | ] 1801 | 1802 | [[package]] 1803 | name = "sqlx" 1804 | version = "0.5.13" 1805 | source = "registry+https://github.com/rust-lang/crates.io-index" 1806 | checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" 1807 | dependencies = [ 1808 | "sqlx-core", 1809 | "sqlx-macros", 1810 | ] 1811 | 1812 | [[package]] 1813 | name = "sqlx-core" 1814 | version = "0.5.13" 1815 | source = "registry+https://github.com/rust-lang/crates.io-index" 1816 | checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" 1817 | dependencies = [ 1818 | "ahash", 1819 | "atoi", 1820 | "base64 0.13.1", 1821 | "bitflags 1.3.2", 1822 | "byteorder", 1823 | "bytes", 1824 | "chrono", 1825 | "crc", 1826 | "crossbeam-queue", 1827 | "dirs", 1828 | "either", 1829 | "event-listener", 1830 | "futures-channel", 1831 | "futures-core", 1832 | "futures-intrusive", 1833 | "futures-util", 1834 | "hashlink", 1835 | "hex", 1836 | "hkdf", 1837 | "hmac", 1838 | "indexmap 1.9.3", 1839 | "itoa", 1840 | "libc", 1841 | "log", 1842 | "md-5", 1843 | "memchr", 1844 | "once_cell", 1845 | "paste", 1846 | "percent-encoding", 1847 | "rand", 1848 | "serde", 1849 | "serde_json", 1850 | "sha-1", 1851 | "sha2", 1852 | "smallvec", 1853 | "sqlformat", 1854 | "sqlx-rt", 1855 | "stringprep", 1856 | "thiserror", 1857 | "tokio-stream", 1858 | "url", 1859 | "whoami", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "sqlx-macros" 1864 | version = "0.5.13" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" 1867 | dependencies = [ 1868 | "dotenv", 1869 | "either", 1870 | "heck", 1871 | "once_cell", 1872 | "proc-macro2", 1873 | "quote", 1874 | "sha2", 1875 | "sqlx-core", 1876 | "sqlx-rt", 1877 | "syn 1.0.109", 1878 | "url", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "sqlx-rt" 1883 | version = "0.5.13" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" 1886 | dependencies = [ 1887 | "native-tls", 1888 | "once_cell", 1889 | "tokio", 1890 | "tokio-native-tls", 1891 | ] 1892 | 1893 | [[package]] 1894 | name = "stable_deref_trait" 1895 | version = "1.2.0" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1898 | 1899 | [[package]] 1900 | name = "stringprep" 1901 | version = "0.1.5" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 1904 | dependencies = [ 1905 | "unicode-bidi", 1906 | "unicode-normalization", 1907 | "unicode-properties", 1908 | ] 1909 | 1910 | [[package]] 1911 | name = "subtle" 1912 | version = "2.6.1" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1915 | 1916 | [[package]] 1917 | name = "syn" 1918 | version = "1.0.109" 1919 | source = "registry+https://github.com/rust-lang/crates.io-index" 1920 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1921 | dependencies = [ 1922 | "proc-macro2", 1923 | "quote", 1924 | "unicode-ident", 1925 | ] 1926 | 1927 | [[package]] 1928 | name = "syn" 1929 | version = "2.0.98" 1930 | source = "registry+https://github.com/rust-lang/crates.io-index" 1931 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 1932 | dependencies = [ 1933 | "proc-macro2", 1934 | "quote", 1935 | "unicode-ident", 1936 | ] 1937 | 1938 | [[package]] 1939 | name = "sync_wrapper" 1940 | version = "0.1.2" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1943 | 1944 | [[package]] 1945 | name = "synstructure" 1946 | version = "0.13.1" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1949 | dependencies = [ 1950 | "proc-macro2", 1951 | "quote", 1952 | "syn 2.0.98", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "system-configuration" 1957 | version = "0.5.1" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1960 | dependencies = [ 1961 | "bitflags 1.3.2", 1962 | "core-foundation", 1963 | "system-configuration-sys", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "system-configuration-sys" 1968 | version = "0.5.0" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1971 | dependencies = [ 1972 | "core-foundation-sys", 1973 | "libc", 1974 | ] 1975 | 1976 | [[package]] 1977 | name = "tempfile" 1978 | version = "3.16.0" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" 1981 | dependencies = [ 1982 | "cfg-if", 1983 | "fastrand", 1984 | "getrandom 0.3.1", 1985 | "once_cell", 1986 | "rustix", 1987 | "windows-sys 0.59.0", 1988 | ] 1989 | 1990 | [[package]] 1991 | name = "thiserror" 1992 | version = "1.0.69" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1995 | dependencies = [ 1996 | "thiserror-impl", 1997 | ] 1998 | 1999 | [[package]] 2000 | name = "thiserror-impl" 2001 | version = "1.0.69" 2002 | source = "registry+https://github.com/rust-lang/crates.io-index" 2003 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2004 | dependencies = [ 2005 | "proc-macro2", 2006 | "quote", 2007 | "syn 2.0.98", 2008 | ] 2009 | 2010 | [[package]] 2011 | name = "thread_local" 2012 | version = "1.1.8" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 2015 | dependencies = [ 2016 | "cfg-if", 2017 | "once_cell", 2018 | ] 2019 | 2020 | [[package]] 2021 | name = "time" 2022 | version = "0.3.37" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 2025 | dependencies = [ 2026 | "deranged", 2027 | "itoa", 2028 | "num-conv", 2029 | "powerfmt", 2030 | "serde", 2031 | "time-core", 2032 | "time-macros", 2033 | ] 2034 | 2035 | [[package]] 2036 | name = "time-core" 2037 | version = "0.1.2" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 2040 | 2041 | [[package]] 2042 | name = "time-macros" 2043 | version = "0.2.19" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 2046 | dependencies = [ 2047 | "num-conv", 2048 | "time-core", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "tinystr" 2053 | version = "0.7.6" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 2056 | dependencies = [ 2057 | "displaydoc", 2058 | "zerovec", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "tinyvec" 2063 | version = "1.8.1" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" 2066 | dependencies = [ 2067 | "tinyvec_macros", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "tinyvec_macros" 2072 | version = "0.1.1" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2075 | 2076 | [[package]] 2077 | name = "tokio" 2078 | version = "1.43.0" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 2081 | dependencies = [ 2082 | "backtrace", 2083 | "bytes", 2084 | "libc", 2085 | "mio", 2086 | "pin-project-lite", 2087 | "socket2", 2088 | "tokio-macros", 2089 | "windows-sys 0.52.0", 2090 | ] 2091 | 2092 | [[package]] 2093 | name = "tokio-macros" 2094 | version = "2.5.0" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2097 | dependencies = [ 2098 | "proc-macro2", 2099 | "quote", 2100 | "syn 2.0.98", 2101 | ] 2102 | 2103 | [[package]] 2104 | name = "tokio-native-tls" 2105 | version = "0.3.1" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2108 | dependencies = [ 2109 | "native-tls", 2110 | "tokio", 2111 | ] 2112 | 2113 | [[package]] 2114 | name = "tokio-rustls" 2115 | version = "0.23.4" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 2118 | dependencies = [ 2119 | "rustls 0.20.9", 2120 | "tokio", 2121 | "webpki", 2122 | ] 2123 | 2124 | [[package]] 2125 | name = "tokio-rustls" 2126 | version = "0.24.1" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 2129 | dependencies = [ 2130 | "rustls 0.21.12", 2131 | "tokio", 2132 | ] 2133 | 2134 | [[package]] 2135 | name = "tokio-stream" 2136 | version = "0.1.17" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 2139 | dependencies = [ 2140 | "futures-core", 2141 | "pin-project-lite", 2142 | "tokio", 2143 | ] 2144 | 2145 | [[package]] 2146 | name = "tokio-util" 2147 | version = "0.7.13" 2148 | source = "registry+https://github.com/rust-lang/crates.io-index" 2149 | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" 2150 | dependencies = [ 2151 | "bytes", 2152 | "futures-core", 2153 | "futures-sink", 2154 | "pin-project-lite", 2155 | "tokio", 2156 | ] 2157 | 2158 | [[package]] 2159 | name = "tower-service" 2160 | version = "0.3.3" 2161 | source = "registry+https://github.com/rust-lang/crates.io-index" 2162 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2163 | 2164 | [[package]] 2165 | name = "tracing" 2166 | version = "0.1.41" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2169 | dependencies = [ 2170 | "log", 2171 | "pin-project-lite", 2172 | "tracing-attributes", 2173 | "tracing-core", 2174 | ] 2175 | 2176 | [[package]] 2177 | name = "tracing-attributes" 2178 | version = "0.1.28" 2179 | source = "registry+https://github.com/rust-lang/crates.io-index" 2180 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2181 | dependencies = [ 2182 | "proc-macro2", 2183 | "quote", 2184 | "syn 2.0.98", 2185 | ] 2186 | 2187 | [[package]] 2188 | name = "tracing-core" 2189 | version = "0.1.33" 2190 | source = "registry+https://github.com/rust-lang/crates.io-index" 2191 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2192 | dependencies = [ 2193 | "once_cell", 2194 | "valuable", 2195 | ] 2196 | 2197 | [[package]] 2198 | name = "tracing-log" 2199 | version = "0.2.0" 2200 | source = "registry+https://github.com/rust-lang/crates.io-index" 2201 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2202 | dependencies = [ 2203 | "log", 2204 | "once_cell", 2205 | "tracing-core", 2206 | ] 2207 | 2208 | [[package]] 2209 | name = "tracing-subscriber" 2210 | version = "0.3.19" 2211 | source = "registry+https://github.com/rust-lang/crates.io-index" 2212 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 2213 | dependencies = [ 2214 | "nu-ansi-term", 2215 | "sharded-slab", 2216 | "smallvec", 2217 | "thread_local", 2218 | "tracing-core", 2219 | "tracing-log", 2220 | ] 2221 | 2222 | [[package]] 2223 | name = "try-lock" 2224 | version = "0.2.5" 2225 | source = "registry+https://github.com/rust-lang/crates.io-index" 2226 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2227 | 2228 | [[package]] 2229 | name = "tungstenite" 2230 | version = "0.17.3" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" 2233 | dependencies = [ 2234 | "base64 0.13.1", 2235 | "byteorder", 2236 | "bytes", 2237 | "http", 2238 | "httparse", 2239 | "log", 2240 | "rand", 2241 | "rustls 0.20.9", 2242 | "sha-1", 2243 | "thiserror", 2244 | "url", 2245 | "utf-8", 2246 | "webpki", 2247 | ] 2248 | 2249 | [[package]] 2250 | name = "typemap_rev" 2251 | version = "0.1.5" 2252 | source = "registry+https://github.com/rust-lang/crates.io-index" 2253 | checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" 2254 | 2255 | [[package]] 2256 | name = "typenum" 2257 | version = "1.17.0" 2258 | source = "registry+https://github.com/rust-lang/crates.io-index" 2259 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2260 | 2261 | [[package]] 2262 | name = "unicase" 2263 | version = "2.8.1" 2264 | source = "registry+https://github.com/rust-lang/crates.io-index" 2265 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 2266 | 2267 | [[package]] 2268 | name = "unicode-bidi" 2269 | version = "0.3.18" 2270 | source = "registry+https://github.com/rust-lang/crates.io-index" 2271 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 2272 | 2273 | [[package]] 2274 | name = "unicode-ident" 2275 | version = "1.0.16" 2276 | source = "registry+https://github.com/rust-lang/crates.io-index" 2277 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 2278 | 2279 | [[package]] 2280 | name = "unicode-normalization" 2281 | version = "0.1.24" 2282 | source = "registry+https://github.com/rust-lang/crates.io-index" 2283 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 2284 | dependencies = [ 2285 | "tinyvec", 2286 | ] 2287 | 2288 | [[package]] 2289 | name = "unicode-properties" 2290 | version = "0.1.3" 2291 | source = "registry+https://github.com/rust-lang/crates.io-index" 2292 | checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 2293 | 2294 | [[package]] 2295 | name = "unicode-segmentation" 2296 | version = "1.12.0" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2299 | 2300 | [[package]] 2301 | name = "unicode_categories" 2302 | version = "0.1.1" 2303 | source = "registry+https://github.com/rust-lang/crates.io-index" 2304 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 2305 | 2306 | [[package]] 2307 | name = "untrusted" 2308 | version = "0.7.1" 2309 | source = "registry+https://github.com/rust-lang/crates.io-index" 2310 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 2311 | 2312 | [[package]] 2313 | name = "untrusted" 2314 | version = "0.9.0" 2315 | source = "registry+https://github.com/rust-lang/crates.io-index" 2316 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2317 | 2318 | [[package]] 2319 | name = "url" 2320 | version = "2.5.4" 2321 | source = "registry+https://github.com/rust-lang/crates.io-index" 2322 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2323 | dependencies = [ 2324 | "form_urlencoded", 2325 | "idna", 2326 | "percent-encoding", 2327 | "serde", 2328 | ] 2329 | 2330 | [[package]] 2331 | name = "utf-8" 2332 | version = "0.7.6" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2335 | 2336 | [[package]] 2337 | name = "utf16_iter" 2338 | version = "1.0.5" 2339 | source = "registry+https://github.com/rust-lang/crates.io-index" 2340 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2341 | 2342 | [[package]] 2343 | name = "utf8_iter" 2344 | version = "1.0.4" 2345 | source = "registry+https://github.com/rust-lang/crates.io-index" 2346 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2347 | 2348 | [[package]] 2349 | name = "valuable" 2350 | version = "0.1.1" 2351 | source = "registry+https://github.com/rust-lang/crates.io-index" 2352 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2353 | 2354 | [[package]] 2355 | name = "vcpkg" 2356 | version = "0.2.15" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2359 | 2360 | [[package]] 2361 | name = "version_check" 2362 | version = "0.9.5" 2363 | source = "registry+https://github.com/rust-lang/crates.io-index" 2364 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2365 | 2366 | [[package]] 2367 | name = "want" 2368 | version = "0.3.1" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2371 | dependencies = [ 2372 | "try-lock", 2373 | ] 2374 | 2375 | [[package]] 2376 | name = "wasi" 2377 | version = "0.11.0+wasi-snapshot-preview1" 2378 | source = "registry+https://github.com/rust-lang/crates.io-index" 2379 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2380 | 2381 | [[package]] 2382 | name = "wasi" 2383 | version = "0.13.3+wasi-0.2.2" 2384 | source = "registry+https://github.com/rust-lang/crates.io-index" 2385 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 2386 | dependencies = [ 2387 | "wit-bindgen-rt", 2388 | ] 2389 | 2390 | [[package]] 2391 | name = "wasite" 2392 | version = "0.1.0" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 2395 | 2396 | [[package]] 2397 | name = "wasm-bindgen" 2398 | version = "0.2.100" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2401 | dependencies = [ 2402 | "cfg-if", 2403 | "once_cell", 2404 | "rustversion", 2405 | "wasm-bindgen-macro", 2406 | ] 2407 | 2408 | [[package]] 2409 | name = "wasm-bindgen-backend" 2410 | version = "0.2.100" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2413 | dependencies = [ 2414 | "bumpalo", 2415 | "log", 2416 | "proc-macro2", 2417 | "quote", 2418 | "syn 2.0.98", 2419 | "wasm-bindgen-shared", 2420 | ] 2421 | 2422 | [[package]] 2423 | name = "wasm-bindgen-futures" 2424 | version = "0.4.50" 2425 | source = "registry+https://github.com/rust-lang/crates.io-index" 2426 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2427 | dependencies = [ 2428 | "cfg-if", 2429 | "js-sys", 2430 | "once_cell", 2431 | "wasm-bindgen", 2432 | "web-sys", 2433 | ] 2434 | 2435 | [[package]] 2436 | name = "wasm-bindgen-macro" 2437 | version = "0.2.100" 2438 | source = "registry+https://github.com/rust-lang/crates.io-index" 2439 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2440 | dependencies = [ 2441 | "quote", 2442 | "wasm-bindgen-macro-support", 2443 | ] 2444 | 2445 | [[package]] 2446 | name = "wasm-bindgen-macro-support" 2447 | version = "0.2.100" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2450 | dependencies = [ 2451 | "proc-macro2", 2452 | "quote", 2453 | "syn 2.0.98", 2454 | "wasm-bindgen-backend", 2455 | "wasm-bindgen-shared", 2456 | ] 2457 | 2458 | [[package]] 2459 | name = "wasm-bindgen-shared" 2460 | version = "0.2.100" 2461 | source = "registry+https://github.com/rust-lang/crates.io-index" 2462 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2463 | dependencies = [ 2464 | "unicode-ident", 2465 | ] 2466 | 2467 | [[package]] 2468 | name = "wasm-streams" 2469 | version = "0.4.2" 2470 | source = "registry+https://github.com/rust-lang/crates.io-index" 2471 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2472 | dependencies = [ 2473 | "futures-util", 2474 | "js-sys", 2475 | "wasm-bindgen", 2476 | "wasm-bindgen-futures", 2477 | "web-sys", 2478 | ] 2479 | 2480 | [[package]] 2481 | name = "web-sys" 2482 | version = "0.3.77" 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" 2484 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2485 | dependencies = [ 2486 | "js-sys", 2487 | "wasm-bindgen", 2488 | ] 2489 | 2490 | [[package]] 2491 | name = "webpki" 2492 | version = "0.22.4" 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" 2494 | checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" 2495 | dependencies = [ 2496 | "ring 0.17.9", 2497 | "untrusted 0.9.0", 2498 | ] 2499 | 2500 | [[package]] 2501 | name = "webpki-roots" 2502 | version = "0.22.6" 2503 | source = "registry+https://github.com/rust-lang/crates.io-index" 2504 | checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" 2505 | dependencies = [ 2506 | "webpki", 2507 | ] 2508 | 2509 | [[package]] 2510 | name = "webpki-roots" 2511 | version = "0.25.4" 2512 | source = "registry+https://github.com/rust-lang/crates.io-index" 2513 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2514 | 2515 | [[package]] 2516 | name = "whoami" 2517 | version = "1.5.2" 2518 | source = "registry+https://github.com/rust-lang/crates.io-index" 2519 | checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" 2520 | dependencies = [ 2521 | "redox_syscall 0.5.8", 2522 | "wasite", 2523 | "web-sys", 2524 | ] 2525 | 2526 | [[package]] 2527 | name = "winapi" 2528 | version = "0.3.9" 2529 | source = "registry+https://github.com/rust-lang/crates.io-index" 2530 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2531 | dependencies = [ 2532 | "winapi-i686-pc-windows-gnu", 2533 | "winapi-x86_64-pc-windows-gnu", 2534 | ] 2535 | 2536 | [[package]] 2537 | name = "winapi-i686-pc-windows-gnu" 2538 | version = "0.4.0" 2539 | source = "registry+https://github.com/rust-lang/crates.io-index" 2540 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2541 | 2542 | [[package]] 2543 | name = "winapi-x86_64-pc-windows-gnu" 2544 | version = "0.4.0" 2545 | source = "registry+https://github.com/rust-lang/crates.io-index" 2546 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2547 | 2548 | [[package]] 2549 | name = "windows-core" 2550 | version = "0.52.0" 2551 | source = "registry+https://github.com/rust-lang/crates.io-index" 2552 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2553 | dependencies = [ 2554 | "windows-targets 0.52.6", 2555 | ] 2556 | 2557 | [[package]] 2558 | name = "windows-sys" 2559 | version = "0.48.0" 2560 | source = "registry+https://github.com/rust-lang/crates.io-index" 2561 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2562 | dependencies = [ 2563 | "windows-targets 0.48.5", 2564 | ] 2565 | 2566 | [[package]] 2567 | name = "windows-sys" 2568 | version = "0.52.0" 2569 | source = "registry+https://github.com/rust-lang/crates.io-index" 2570 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2571 | dependencies = [ 2572 | "windows-targets 0.52.6", 2573 | ] 2574 | 2575 | [[package]] 2576 | name = "windows-sys" 2577 | version = "0.59.0" 2578 | source = "registry+https://github.com/rust-lang/crates.io-index" 2579 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2580 | dependencies = [ 2581 | "windows-targets 0.52.6", 2582 | ] 2583 | 2584 | [[package]] 2585 | name = "windows-targets" 2586 | version = "0.48.5" 2587 | source = "registry+https://github.com/rust-lang/crates.io-index" 2588 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2589 | dependencies = [ 2590 | "windows_aarch64_gnullvm 0.48.5", 2591 | "windows_aarch64_msvc 0.48.5", 2592 | "windows_i686_gnu 0.48.5", 2593 | "windows_i686_msvc 0.48.5", 2594 | "windows_x86_64_gnu 0.48.5", 2595 | "windows_x86_64_gnullvm 0.48.5", 2596 | "windows_x86_64_msvc 0.48.5", 2597 | ] 2598 | 2599 | [[package]] 2600 | name = "windows-targets" 2601 | version = "0.52.6" 2602 | source = "registry+https://github.com/rust-lang/crates.io-index" 2603 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2604 | dependencies = [ 2605 | "windows_aarch64_gnullvm 0.52.6", 2606 | "windows_aarch64_msvc 0.52.6", 2607 | "windows_i686_gnu 0.52.6", 2608 | "windows_i686_gnullvm", 2609 | "windows_i686_msvc 0.52.6", 2610 | "windows_x86_64_gnu 0.52.6", 2611 | "windows_x86_64_gnullvm 0.52.6", 2612 | "windows_x86_64_msvc 0.52.6", 2613 | ] 2614 | 2615 | [[package]] 2616 | name = "windows_aarch64_gnullvm" 2617 | version = "0.48.5" 2618 | source = "registry+https://github.com/rust-lang/crates.io-index" 2619 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2620 | 2621 | [[package]] 2622 | name = "windows_aarch64_gnullvm" 2623 | version = "0.52.6" 2624 | source = "registry+https://github.com/rust-lang/crates.io-index" 2625 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2626 | 2627 | [[package]] 2628 | name = "windows_aarch64_msvc" 2629 | version = "0.48.5" 2630 | source = "registry+https://github.com/rust-lang/crates.io-index" 2631 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2632 | 2633 | [[package]] 2634 | name = "windows_aarch64_msvc" 2635 | version = "0.52.6" 2636 | source = "registry+https://github.com/rust-lang/crates.io-index" 2637 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2638 | 2639 | [[package]] 2640 | name = "windows_i686_gnu" 2641 | version = "0.48.5" 2642 | source = "registry+https://github.com/rust-lang/crates.io-index" 2643 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2644 | 2645 | [[package]] 2646 | name = "windows_i686_gnu" 2647 | version = "0.52.6" 2648 | source = "registry+https://github.com/rust-lang/crates.io-index" 2649 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2650 | 2651 | [[package]] 2652 | name = "windows_i686_gnullvm" 2653 | version = "0.52.6" 2654 | source = "registry+https://github.com/rust-lang/crates.io-index" 2655 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2656 | 2657 | [[package]] 2658 | name = "windows_i686_msvc" 2659 | version = "0.48.5" 2660 | source = "registry+https://github.com/rust-lang/crates.io-index" 2661 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2662 | 2663 | [[package]] 2664 | name = "windows_i686_msvc" 2665 | version = "0.52.6" 2666 | source = "registry+https://github.com/rust-lang/crates.io-index" 2667 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2668 | 2669 | [[package]] 2670 | name = "windows_x86_64_gnu" 2671 | version = "0.48.5" 2672 | source = "registry+https://github.com/rust-lang/crates.io-index" 2673 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2674 | 2675 | [[package]] 2676 | name = "windows_x86_64_gnu" 2677 | version = "0.52.6" 2678 | source = "registry+https://github.com/rust-lang/crates.io-index" 2679 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2680 | 2681 | [[package]] 2682 | name = "windows_x86_64_gnullvm" 2683 | version = "0.48.5" 2684 | source = "registry+https://github.com/rust-lang/crates.io-index" 2685 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2686 | 2687 | [[package]] 2688 | name = "windows_x86_64_gnullvm" 2689 | version = "0.52.6" 2690 | source = "registry+https://github.com/rust-lang/crates.io-index" 2691 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2692 | 2693 | [[package]] 2694 | name = "windows_x86_64_msvc" 2695 | version = "0.48.5" 2696 | source = "registry+https://github.com/rust-lang/crates.io-index" 2697 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2698 | 2699 | [[package]] 2700 | name = "windows_x86_64_msvc" 2701 | version = "0.52.6" 2702 | source = "registry+https://github.com/rust-lang/crates.io-index" 2703 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2704 | 2705 | [[package]] 2706 | name = "winreg" 2707 | version = "0.50.0" 2708 | source = "registry+https://github.com/rust-lang/crates.io-index" 2709 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2710 | dependencies = [ 2711 | "cfg-if", 2712 | "windows-sys 0.48.0", 2713 | ] 2714 | 2715 | [[package]] 2716 | name = "wit-bindgen-rt" 2717 | version = "0.33.0" 2718 | source = "registry+https://github.com/rust-lang/crates.io-index" 2719 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 2720 | dependencies = [ 2721 | "bitflags 2.8.0", 2722 | ] 2723 | 2724 | [[package]] 2725 | name = "write16" 2726 | version = "1.0.0" 2727 | source = "registry+https://github.com/rust-lang/crates.io-index" 2728 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2729 | 2730 | [[package]] 2731 | name = "writeable" 2732 | version = "0.5.5" 2733 | source = "registry+https://github.com/rust-lang/crates.io-index" 2734 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2735 | 2736 | [[package]] 2737 | name = "yoke" 2738 | version = "0.7.5" 2739 | source = "registry+https://github.com/rust-lang/crates.io-index" 2740 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2741 | dependencies = [ 2742 | "serde", 2743 | "stable_deref_trait", 2744 | "yoke-derive", 2745 | "zerofrom", 2746 | ] 2747 | 2748 | [[package]] 2749 | name = "yoke-derive" 2750 | version = "0.7.5" 2751 | source = "registry+https://github.com/rust-lang/crates.io-index" 2752 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2753 | dependencies = [ 2754 | "proc-macro2", 2755 | "quote", 2756 | "syn 2.0.98", 2757 | "synstructure", 2758 | ] 2759 | 2760 | [[package]] 2761 | name = "zerocopy" 2762 | version = "0.7.35" 2763 | source = "registry+https://github.com/rust-lang/crates.io-index" 2764 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2765 | dependencies = [ 2766 | "byteorder", 2767 | "zerocopy-derive", 2768 | ] 2769 | 2770 | [[package]] 2771 | name = "zerocopy-derive" 2772 | version = "0.7.35" 2773 | source = "registry+https://github.com/rust-lang/crates.io-index" 2774 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2775 | dependencies = [ 2776 | "proc-macro2", 2777 | "quote", 2778 | "syn 2.0.98", 2779 | ] 2780 | 2781 | [[package]] 2782 | name = "zerofrom" 2783 | version = "0.1.5" 2784 | source = "registry+https://github.com/rust-lang/crates.io-index" 2785 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2786 | dependencies = [ 2787 | "zerofrom-derive", 2788 | ] 2789 | 2790 | [[package]] 2791 | name = "zerofrom-derive" 2792 | version = "0.1.5" 2793 | source = "registry+https://github.com/rust-lang/crates.io-index" 2794 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2795 | dependencies = [ 2796 | "proc-macro2", 2797 | "quote", 2798 | "syn 2.0.98", 2799 | "synstructure", 2800 | ] 2801 | 2802 | [[package]] 2803 | name = "zerovec" 2804 | version = "0.10.4" 2805 | source = "registry+https://github.com/rust-lang/crates.io-index" 2806 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2807 | dependencies = [ 2808 | "yoke", 2809 | "zerofrom", 2810 | "zerovec-derive", 2811 | ] 2812 | 2813 | [[package]] 2814 | name = "zerovec-derive" 2815 | version = "0.10.3" 2816 | source = "registry+https://github.com/rust-lang/crates.io-index" 2817 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2818 | dependencies = [ 2819 | "proc-macro2", 2820 | "quote", 2821 | "syn 2.0.98", 2822 | ] 2823 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustlang_discord_mod_bot" 3 | version = "0.1.0" 4 | authors = ["technetos "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | futures = { version = "0.3" } 12 | reqwest = { version = "0.11", features = ["json"] } 13 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } 14 | tracing = "0.1" 15 | tracing-subscriber = { version = "0.3" } 16 | diesel = { version = "1.4.0", features = ["postgres"] } 17 | diesel_migrations = { version = "1.4.0", features = ["postgres"] } 18 | serde = "1.0" 19 | serde_derive = "1.0" 20 | envy = "0.4" 21 | indexmap = "1.6" 22 | 23 | 24 | [dependencies.sqlx] 25 | features = [ 26 | "runtime-tokio-native-tls", 27 | "postgres", 28 | "chrono", 29 | ] 30 | version = "0.5" 31 | 32 | [dependencies.serenity] 33 | default-features = false 34 | features = [ 35 | "builder", 36 | "cache", 37 | "client", 38 | "gateway", 39 | "model", 40 | "utils", 41 | "rustls_backend", 42 | "chrono", 43 | ] 44 | version = "0.11.5" 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile is composed of two steps: the first one builds the release 2 | # binary, and then the binary is copied inside another, empty image. 3 | 4 | ################# 5 | # Build image # 6 | ################# 7 | 8 | FROM ubuntu:jammy AS build 9 | 10 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 11 | ca-certificates \ 12 | curl \ 13 | build-essential \ 14 | pkg-config \ 15 | libpq-dev \ 16 | libssl-dev 17 | 18 | # Install the currently pinned toolchain with rustup 19 | RUN curl https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init >/tmp/rustup-init && \ 20 | chmod +x /tmp/rustup-init && \ 21 | /tmp/rustup-init -y --no-modify-path --default-toolchain stable 22 | ENV PATH=/root/.cargo/bin:$PATH 23 | 24 | # Build the dependencies in a separate step to avoid rebuilding all of them 25 | # every time the source code changes. This takes advantage of Docker's layer 26 | # caching, and it works by copying the Cargo.{toml,lock} with dummy source code 27 | # and doing a full build with it. 28 | WORKDIR /tmp/source 29 | COPY Cargo.lock Cargo.toml /tmp/source/ 30 | RUN mkdir -p /tmp/source/src && \ 31 | echo "fn main() {}" > /tmp/source/src/main.rs 32 | RUN cargo fetch 33 | RUN cargo build --release 34 | 35 | # Dependencies are now cached, copy the actual source code and do another full 36 | # build. The touch on all the .rs files is needed, otherwise cargo assumes the 37 | # source code didn't change thanks to mtime weirdness. 38 | RUN rm -rf /tmp/source/src 39 | COPY src /tmp/source/src 40 | COPY migrations /tmp/source/migrations 41 | RUN find src -name "*.rs" -exec touch {} \; && cargo build --release 42 | 43 | ################## 44 | # Output image # 45 | ################## 46 | 47 | FROM ubuntu:jammy AS binary 48 | 49 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 50 | libpq-dev \ 51 | ca-certificates 52 | 53 | COPY --from=build /tmp/source/target/release/rustlang_discord_mod_bot /usr/local/bin/ 54 | 55 | ENV RUST_LOG=info 56 | CMD rustlang_discord_mod_bot 57 | -------------------------------------------------------------------------------- /GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Setup Postgres 4 | This repo ships with a `Dockerfile` for a postgres image you can use in 5 | `postgres-docker/Dockerfile`. 6 | 7 | Build the docker image 8 | ```sh 9 | docker build -t postgresql postgres-docker/ 10 | ``` 11 | 12 | Run the docker image 13 | ```sh 14 | docker run --rm -P --name database postgresql 15 | ``` 16 | 17 | Find out the port the postgres instance is running on 18 | ```sh 19 | docker ps 20 | ``` 21 | ## Setup Discord 22 | In order to test the bot, you need to have a dedicated testing server. 23 | 24 | 1) [Click Here](https://discord.new/vkaVjTnf4aDc) to use our template. This is a server template functionality provided by Discord. 25 | 2) Create a bot account ([guide](https://discordpy.readthedocs.io/en/latest/discord.html#creating-a-bot-account)). 26 | 3) Get your bot's Client ID, replace `{ID}` with it, and then go to the URL in order to add the bot to your testing server. 27 | 28 | ``` 29 | https://discordapp.com/api/oauth2/authorize?client_id={ID}&permissions=8&scope=bot 30 | ``` 31 | ## Get the bot running 32 | 33 | Build the docker image for the bot 34 | ```sh 35 | docker build -t discordbot . 36 | ``` 37 | A number of environment variables are required to run the bot. Many of these 38 | environment variables come from discord. 39 | 40 | + `MOD_ID` is the id of the mod role 41 | + `TALK_ID` is the id of the talk role 42 | + `WG_AND_TEAMS_ID` is the id of the working groups and teams role 43 | + `DISCORD_TOKEN` is the token used to connect to discord 44 | + `DATABASE_URL` is the url where the database is running, you will need to 45 | update the port to the port your instance of postgres is running on 46 | 47 | Once you have your guild setup, you can run the bot 48 | ```sh 49 | docker run -e "MOD_ID=" -e "TALK_ID=" -e "DISCORD_TOKEN=" -e "DATABASE_URL=postgres://docker:docker@172.17.0.1:32768" --add-host=database:172.17.0.2 --rm -it discordbot 50 | ``` 51 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Joshua Gould 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Mods Bot 2 | A discord bot written in rust. 3 | 4 | # [Getting Started](GETTING_STARTED.md) 5 | # [Commands](COMMANDS.md) 6 | 7 | # Features 8 | The following commands are currently supported by the bot 9 | 10 | ## Tags 11 | Tags are a simple key value store. 12 | 13 | Lookup a tag 14 | ``` 15 | ?tag {key} 16 | ``` 17 | Create a tag 18 | ``` 19 | ?tags create {key} value... 20 | ``` 21 | Delete a tag 22 | ``` 23 | ?tags delete {key} 24 | ``` 25 | Get all tags 26 | ``` 27 | ?tags 28 | ``` 29 | 30 | ### Crates 31 | Search for a crate on crates.io 32 | ``` 33 | ?crate query... 34 | ``` 35 | Retreive documentation for a crate 36 | ``` 37 | ?docs query... 38 | ``` 39 | 40 | ### Ban 41 | Ban a user 42 | ``` 43 | ?ban {user} 44 | 45 | ``` 46 | ### Kick 47 | Kick a user 48 | ``` 49 | ?kick {user} 50 | ``` 51 | ### Slowmode 52 | Set slowmode for a channel. 0 seconds disables slowmode. 53 | ``` 54 | ?slowmode {channel} {seconds} 55 | ``` 56 | 57 | ### Code of conduct welcome message 58 | Sets up the code of conduct message with reaction in the specified channel. 59 | Used for assigning talk roles. 60 | ``` 61 | ?CoC {channel} 62 | ``` 63 | -------------------------------------------------------------------------------- /diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/schema.rs" 6 | -------------------------------------------------------------------------------- /migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-lang/discord-mods-bot/2bbb725b8c8041dfe5207e14335e3b8d5d1b24e6/migrations/.gitkeep -------------------------------------------------------------------------------- /migrations/00000000000000_diesel_initial_setup/down.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); 6 | DROP FUNCTION IF EXISTS diesel_set_updated_at(); 7 | -------------------------------------------------------------------------------- /migrations/00000000000000_diesel_initial_setup/up.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | 6 | 7 | 8 | -- Sets up a trigger for the given table to automatically set a column called 9 | -- `updated_at` whenever the row is modified (unless `updated_at` was included 10 | -- in the modified columns) 11 | -- 12 | -- # Example 13 | -- 14 | -- ```sql 15 | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); 16 | -- 17 | -- SELECT diesel_manage_updated_at('users'); 18 | -- ``` 19 | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ 20 | BEGIN 21 | EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s 22 | FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); 23 | END; 24 | $$ LANGUAGE plpgsql; 25 | 26 | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ 27 | BEGIN 28 | IF ( 29 | NEW IS DISTINCT FROM OLD AND 30 | NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at 31 | ) THEN 32 | NEW.updated_at := current_timestamp; 33 | END IF; 34 | RETURN NEW; 35 | END; 36 | $$ LANGUAGE plpgsql; 37 | -------------------------------------------------------------------------------- /migrations/2019-05-27-225104_tags/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE tags; 2 | -------------------------------------------------------------------------------- /migrations/2019-05-27-225104_tags/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS tags ( 2 | id SERIAL PRIMARY KEY, 3 | key TEXT UNIQUE NOT NULL, 4 | value TEXT NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /migrations/2019-08-19-210929_roles/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` 2 | DROP TABLE roles; 3 | -------------------------------------------------------------------------------- /migrations/2019-08-19-210929_roles/up.sql: -------------------------------------------------------------------------------- 1 | -- Your SQL goes here 2 | CREATE TABLE IF NOT EXISTS roles ( 3 | id SERIAL PRIMARY KEY, 4 | role TEXT NOT NULL, 5 | name TEXT NOT NULL UNIQUE 6 | ); 7 | -------------------------------------------------------------------------------- /migrations/2019-08-19-211002_messages/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` 2 | DROP TABLE messages; 3 | -------------------------------------------------------------------------------- /migrations/2019-08-19-211002_messages/up.sql: -------------------------------------------------------------------------------- 1 | -- Your SQL goes here 2 | CREATE TABLE IF NOT EXISTS messages ( 3 | id SERIAL PRIMARY KEY, 4 | name TEXT NOT NULL UNIQUE, 5 | message TEXT NOT NULL, 6 | channel TEXT NOT NULL 7 | ); 8 | -------------------------------------------------------------------------------- /migrations/2019-08-22-231258_users/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` 2 | drop table users; 3 | -------------------------------------------------------------------------------- /migrations/2019-08-22-231258_users/up.sql: -------------------------------------------------------------------------------- 1 | -- Your SQL goes here 2 | CREATE TABLE IF NOT EXISTS users ( 3 | id SERIAL PRIMARY KEY, 4 | name TEXT NOT NULL UNIQUE, 5 | user_id TEXT NOT NULL UNIQUE 6 | ); 7 | -------------------------------------------------------------------------------- /migrations/2020-08-25-224210_bans/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` 2 | DROP TABLE IF EXISTS bans; 3 | -------------------------------------------------------------------------------- /migrations/2020-08-25-224210_bans/up.sql: -------------------------------------------------------------------------------- 1 | -- Your SQL goes here 2 | CREATE TABLE IF NOT EXISTS bans ( 3 | id SERIAL PRIMARY KEY, 4 | user_id TEXT NOT NULL, 5 | guild_id TEXT NOT NULL, 6 | unbanned BOOLEAN NOT NULL DEFAULT false, 7 | start_time TIMESTAMP NOT NULL, 8 | end_time TIMESTAMP NOT NULL 9 | ); 10 | -------------------------------------------------------------------------------- /postgres-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | # Get the ability to receive gpg keys from hkps:// 4 | RUN apt-get update && apt-get install -y gnupg-curl 5 | 6 | # Add the PostgreSQL PGP key to verify their Debian packages. 7 | # It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc 8 | RUN apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 9 | 10 | # Add PostgreSQL's repository. It contains the most recent stable release 11 | # of PostgreSQL, ``9.5``. 12 | #RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list 13 | RUN echo "deb http://apt.postgresql.org/pub/repos/apt/lsb_release -cs-pgdg main" /etc/apt/sources.list.d/pgdg.list 14 | 15 | # Install ``python-software-properties``, ``software-properties-common`` and PostgreSQL 9.5 16 | # There are some warnings (in red) that show up during the build. You can hide 17 | # them by prefixing each apt-get statement with DEBIAN_FRONTEND=noninteractive 18 | RUN apt-get update && apt-get install -y python-software-properties software-properties-common postgresql postgresql-client postgresql-contrib 19 | 20 | # Note: The official Debian and Ubuntu images automatically ``apt-get clean`` 21 | # after each ``apt-get`` 22 | 23 | # Run the rest of the commands as the ``postgres`` user created by the ``postgres-9.5`` package when it was ``apt-get installed`` 24 | USER postgres 25 | 26 | # Create a PostgreSQL role named ``docker`` with ``docker`` as the password and 27 | # then create a database `docker` owned by the ``docker`` role. 28 | # Note: here we use ``&&\`` to run commands one after the other - the ``\`` 29 | # allows the RUN command to span multiple lines. 30 | RUN /etc/init.d/postgresql start &&\ 31 | psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\ 32 | createdb -O docker docker 33 | 34 | # Adjust PostgreSQL configuration so that remote connections to the 35 | # database are possible. 36 | RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.5/main/pg_hba.conf 37 | 38 | # And add ``listen_addresses`` to ``/etc/postgresql/9.5/main/postgresql.conf`` 39 | RUN echo "listen_addresses='*'" >> /etc/postgresql/9.5/main/postgresql.conf 40 | 41 | # Expose the PostgreSQL port 42 | EXPOSE 5432 43 | 44 | # Add VOLUMEs to allow backup of config, logs and databases 45 | VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"] 46 | 47 | # Set the default command to run when starting the container 48 | CMD ["/usr/lib/postgresql/9.5/bin/postgres", "-D", "/var/lib/postgresql/9.5/main", "-c", "config_file=/etc/postgresql/9.5/main/postgresql.conf"] 49 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | command_history::CommandHistory, 3 | commands::{Args, Auth}, 4 | Error, 5 | }; 6 | use indexmap::IndexMap; 7 | use serenity::{model::prelude::*, utils::parse_username}; 8 | use std::sync::Arc; 9 | use tracing::info; 10 | 11 | /// Send a reply to the channel the message was received on. 12 | pub async fn send_reply(args: Arc, message: &str) -> Result<(), Error> { 13 | if let Some(response_id) = response_exists(args.clone()).await { 14 | info!("editing message: {:?}", response_id); 15 | args.msg 16 | .channel_id 17 | .edit_message(&args.clone().cx, response_id, |msg| msg.content(message)) 18 | .await?; 19 | } else { 20 | let command_id = args.msg.id; 21 | let response = args.clone().msg.channel_id.say(&args.cx, message).await?; 22 | 23 | let mut data = args.cx.data.write().await; 24 | let history = data.get_mut::().unwrap(); 25 | history.insert(command_id, response.id); 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | async fn response_exists(args: Arc) -> Option { 32 | let data = args.cx.data.read().await; 33 | let history = data.get::().unwrap(); 34 | history.get(&args.msg.id).cloned() 35 | } 36 | 37 | /// Determine if a member sending a message has the `Role`. 38 | pub fn has_role(args: Arc, role: &RoleId) -> Result { 39 | Ok(args 40 | .msg 41 | .member 42 | .as_ref() 43 | .ok_or("Unable to fetch member")? 44 | .roles 45 | .contains(role)) 46 | } 47 | 48 | fn check_permission(args: Arc, role: Option) -> Result { 49 | use std::str::FromStr; 50 | if let Some(role_id) = role { 51 | Ok(has_role( 52 | args.clone(), 53 | &RoleId::from(u64::from_str(&role_id)?), 54 | )?) 55 | } else { 56 | Ok(false) 57 | } 58 | } 59 | 60 | /// Return whether or not the user is a mod. 61 | pub async fn is_mod(args: Arc) -> Result { 62 | let role: Option<(i32, String, String)> = 63 | sqlx::query_as("select * from roles where name = 'mod'") 64 | .fetch_optional(&*args.db) 65 | .await?; 66 | 67 | check_permission(args.clone(), role.map(|(_, role_id, _)| role_id)) 68 | } 69 | 70 | pub async fn is_wg_and_teams(args: Arc) -> Result { 71 | let role: Option<(i32, String, String)> = 72 | sqlx::query_as("select * from roles where name = 'wg_and_teams'") 73 | .fetch_optional(&*args.db) 74 | .await?; 75 | 76 | check_permission(args.clone(), role.map(|(_, role_id, _)| role_id)) 77 | } 78 | 79 | pub async fn main_menu( 80 | args: Arc, 81 | commands: &IndexMap<&'static str, (&'static str, &'static Auth)>, 82 | ) -> String { 83 | use futures::stream::{self, StreamExt}; 84 | 85 | let mut menu = format!("Commands:\n"); 86 | 87 | menu = stream::iter(commands) 88 | .fold(menu, |mut menu, (base_cmd, (description, auth))| { 89 | let args_clone = args.clone(); 90 | async move { 91 | if let Ok(true) = auth.call(args_clone).await { 92 | menu += &format!("\t{cmd:<12}{desc}\n", cmd = base_cmd, desc = description); 93 | } 94 | menu 95 | } 96 | }) 97 | .await; 98 | 99 | menu += &format!("\t{help:<12}This menu\n", help = "?help"); 100 | menu += "\nType ?help command for more info on a command."; 101 | menu += "\n\nAdditional Info:\n"; 102 | menu += "\tYou can edit your message to the bot and the bot will edit its response."; 103 | menu 104 | } 105 | 106 | /// Set slow mode for a channel. 107 | /// 108 | /// A `seconds` value of 0 will disable slowmode 109 | pub async fn slow_mode(args: Arc) -> Result<(), Error> { 110 | use std::str::FromStr; 111 | 112 | if is_mod(args.clone()).await? { 113 | let seconds = &args 114 | .params 115 | .get("seconds") 116 | .ok_or("unable to retrieve seconds param")? 117 | .parse::()?; 118 | 119 | let channel_name = &args 120 | .params 121 | .get("channel") 122 | .ok_or("unable to retrieve channel param")?; 123 | 124 | info!("Applying slowmode to channel {}", &channel_name); 125 | ChannelId::from_str(channel_name)? 126 | .edit(&args.cx, |c| c.rate_limit_per_user(*seconds)) 127 | .await?; 128 | } 129 | Ok(()) 130 | } 131 | 132 | pub async fn slow_mode_help(args: Arc) -> Result<(), Error> { 133 | let help_string = " 134 | Set slowmode on a channel 135 | ``` 136 | ?slowmode {channel} {seconds} 137 | ``` 138 | **Example:** 139 | ``` 140 | ?slowmode #bot-usage 10 141 | ``` 142 | will set slowmode on the `#bot-usage` channel with a delay of 10 seconds. 143 | 144 | **Disable slowmode:** 145 | ``` 146 | ?slowmode #bot-usage 0 147 | ``` 148 | will disable slowmode on the `#bot-usage` channel."; 149 | send_reply(args.clone(), &help_string).await?; 150 | Ok(()) 151 | } 152 | 153 | /// Kick a user from the guild. 154 | /// 155 | /// Requires the kick members permission 156 | pub async fn kick(args: Arc) -> Result<(), Error> { 157 | if is_mod(args.clone()).await? { 158 | let user_id = parse_username( 159 | &args 160 | .params 161 | .get("user") 162 | .ok_or("unable to retrieve user param")?, 163 | ) 164 | .ok_or("unable to retrieve user id")?; 165 | 166 | if let Some(guild) = args.msg.guild(&args.cx) { 167 | info!("Kicking user from guild"); 168 | guild.kick(&args.cx, UserId::from(user_id)).await? 169 | } 170 | } 171 | Ok(()) 172 | } 173 | 174 | pub async fn kick_help(args: Arc) -> Result<(), Error> { 175 | let help_string = " 176 | Kick a user from the guild 177 | ``` 178 | ?kick {user} 179 | ``` 180 | **Example:** 181 | ``` 182 | ?kick @someuser 183 | ``` 184 | will kick a user from the guild."; 185 | send_reply(args.clone(), &help_string).await?; 186 | Ok(()) 187 | } 188 | -------------------------------------------------------------------------------- /src/ban.rs: -------------------------------------------------------------------------------- 1 | use crate::{api, commands::Args, text::ban_message, Error, HOUR}; 2 | use serenity::{model::prelude::*, prelude::*, utils::parse_username}; 3 | use sqlx::{ 4 | postgres::PgPool, 5 | types::chrono::{DateTime, Utc}, 6 | }; 7 | use std::{ 8 | sync::Arc, 9 | time::{Duration, SystemTime}, 10 | }; 11 | use tracing::info; 12 | 13 | pub async fn save_ban( 14 | user_id: String, 15 | guild_id: String, 16 | hours: u64, 17 | db: Arc, 18 | ) -> Result<(), Error> { 19 | info!("Recording ban for user {}", &user_id); 20 | sqlx::query( 21 | "insert into bans(user_id, guild_id, start_time, end_time) values ($1, $2, $3, $4)", 22 | ) 23 | .bind(user_id) 24 | .bind(guild_id) 25 | .bind(DateTime::::from(SystemTime::now())) 26 | .bind(DateTime::::from( 27 | SystemTime::now() 28 | .checked_add(Duration::new(hours * HOUR, 0)) 29 | .ok_or("out of range Duration for ban end_time")?, 30 | )) 31 | .execute(&*db) 32 | .await?; 33 | 34 | Ok(()) 35 | } 36 | 37 | pub async fn save_unban(user_id: String, guild_id: String, db: Arc) -> Result<(), Error> { 38 | info!("Recording unban for user {}", &user_id); 39 | sqlx::query( 40 | "update bans set unbanned = true where user_id = $1 and guild_id = $2 and unbanned = false", 41 | ) 42 | .bind(user_id) 43 | .bind(guild_id) 44 | .execute(&*db) 45 | .await?; 46 | 47 | Ok(()) 48 | } 49 | 50 | pub async fn unban_users(cx: &Context, db: Arc) -> Result<(), Error> { 51 | use std::str::FromStr; 52 | 53 | let to_unban: Vec<(i32, String, String, bool, DateTime, DateTime)> = 54 | sqlx::query_as("select * from bans where unbanned = false and end_time < $1") 55 | .bind(DateTime::::from(SystemTime::now())) 56 | .fetch_all(&*db) 57 | .await?; 58 | 59 | for row in &to_unban { 60 | let guild_id = GuildId::from(u64::from_str(&row.2)?); 61 | info!("Unbanning user {}", &row.1); 62 | guild_id.unban(&cx, u64::from_str(&row.1)?).await?; 63 | } 64 | 65 | Ok(()) 66 | } 67 | 68 | /// Temporarily ban an user from the guild. 69 | /// 70 | /// Requires the ban members permission 71 | pub async fn temp_ban(args: Arc) -> Result<(), Error> { 72 | let user_id = parse_username( 73 | &args 74 | .params 75 | .get("user") 76 | .ok_or("unable to retrieve user param")?, 77 | ) 78 | .ok_or("unable to retrieve user id")?; 79 | 80 | use std::str::FromStr; 81 | 82 | let hours = u64::from_str( 83 | args.params 84 | .get("hours") 85 | .ok_or("unable to retrieve hours param")?, 86 | )?; 87 | 88 | let reason = args 89 | .params 90 | .get("reason") 91 | .ok_or("unable to retrieve reason param")?; 92 | 93 | if let Some(guild) = args.msg.guild(&args.cx) { 94 | info!("Banning user from guild"); 95 | let user = UserId::from(user_id); 96 | 97 | user.create_dm_channel(&args.cx) 98 | .await? 99 | .say(&args.cx, ban_message(reason, hours)) 100 | .await?; 101 | 102 | guild.ban(&args.cx, &user, 7).await?; 103 | 104 | save_ban( 105 | format!("{}", user_id), 106 | format!("{}", guild.id), 107 | hours, 108 | args.db.clone(), 109 | ) 110 | .await?; 111 | } 112 | Ok(()) 113 | } 114 | 115 | pub async fn help(args: Arc) -> Result<(), Error> { 116 | let hours = 24; 117 | let reason = "violating the code of conduct"; 118 | 119 | let help_string = format!( 120 | " 121 | Ban a user for a temporary amount of time 122 | ``` 123 | {command} 124 | ``` 125 | **Example:** 126 | ``` 127 | ?ban @someuser {hours} {reason} 128 | ``` 129 | will ban a user for {hours} hours and send them the following message: 130 | ``` 131 | {user_message} 132 | ``` 133 | ", 134 | command = "?ban {user} {hours} reason...", 135 | user_message = ban_message(reason, hours), 136 | hours = hours, 137 | reason = reason, 138 | ); 139 | 140 | api::send_reply(args.clone(), &help_string).await?; 141 | Ok(()) 142 | } 143 | -------------------------------------------------------------------------------- /src/command_history.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::{Commands, PREFIX}, 3 | Error, HOUR, 4 | }; 5 | use indexmap::IndexMap; 6 | use reqwest::Client as HttpClient; 7 | use serenity::{model::prelude::*, prelude::*, utils::CustomMessage}; 8 | use sqlx::postgres::PgPool; 9 | use std::{sync::Arc, time::Duration}; 10 | use tracing::info; 11 | 12 | const MESSAGE_AGE_MAX: Duration = Duration::from_secs(HOUR); 13 | 14 | pub struct CommandHistory; 15 | 16 | impl TypeMapKey for CommandHistory { 17 | type Value = IndexMap; 18 | } 19 | 20 | pub async fn replay_message( 21 | cx: Context, 22 | ev: MessageUpdateEvent, 23 | cmds: &Commands, 24 | http: Arc, 25 | db: Arc, 26 | ) -> Result<(), Error> { 27 | let age = ev.timestamp.and_then(|create| { 28 | ev.edited_timestamp.and_then(|edit| { 29 | let edit_datetime = *edit; 30 | let create_datetime = *create; 31 | edit_datetime 32 | .signed_duration_since(create_datetime) 33 | .to_std() 34 | .ok() 35 | }) 36 | }); 37 | 38 | if age.is_some() && age.unwrap() < MESSAGE_AGE_MAX { 39 | let mut msg = CustomMessage::new(); 40 | msg.id(ev.id) 41 | .channel_id(ev.channel_id) 42 | .content(ev.content.unwrap_or_default()); 43 | 44 | let msg = msg.build(); 45 | 46 | if msg.content.starts_with(PREFIX) { 47 | info!( 48 | "sending edited message - {:?} {:?}", 49 | msg.content, msg.author 50 | ); 51 | cmds.execute(cx, msg, http, db).await; 52 | } 53 | } 54 | 55 | Ok(()) 56 | } 57 | 58 | pub async fn clear_command_history(cx: &Context) -> Result<(), Error> { 59 | let mut data = cx.data.write().await; 60 | let history = data.get_mut::().unwrap(); 61 | 62 | // always keep the last command in history 63 | if history.len() > 0 { 64 | info!("Clearing command history"); 65 | history.drain(..history.len() - 1); 66 | } 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api, 3 | state_machine::{CharacterSet, StateMachine}, 4 | Error, 5 | }; 6 | use indexmap::IndexMap; 7 | use reqwest::Client as HttpClient; 8 | use serenity::{model::channel::Message, prelude::Context}; 9 | use sqlx::postgres::PgPool; 10 | use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; 11 | use tracing::{error, info}; 12 | 13 | pub const PREFIX: &str = "?"; 14 | 15 | type ResultFuture = Pin> + Send>>; 16 | 17 | pub trait AsyncFn: 'static { 18 | fn call(&self, args: Arc) -> ResultFuture; 19 | } 20 | 21 | impl AsyncFn for F 22 | where 23 | F: Fn(Arc) -> G + 'static, 24 | G: Future> + Send + 'static, 25 | { 26 | fn call(&self, args: Arc) -> ResultFuture { 27 | let fut = (self)(args); 28 | Box::pin(async move { fut.await }) 29 | } 30 | } 31 | 32 | pub type Handler = dyn AsyncFn<()> + Send + Sync; 33 | pub type Auth = dyn AsyncFn + Send + Sync; 34 | 35 | pub enum CommandKind { 36 | Base, 37 | Protected, 38 | Help, 39 | } 40 | 41 | pub struct Command { 42 | pub kind: CommandKind, 43 | pub auth: &'static Auth, 44 | pub handler: &'static Handler, 45 | } 46 | 47 | impl Command { 48 | pub fn new(handler: &'static Handler) -> Self { 49 | Self { 50 | kind: CommandKind::Base, 51 | auth: &|_| async { Ok(true) }, 52 | handler, 53 | } 54 | } 55 | 56 | pub fn new_with_auth(handler: &'static Handler, auth: &'static Auth) -> Self { 57 | Self { 58 | kind: CommandKind::Protected, 59 | auth, 60 | handler, 61 | } 62 | } 63 | 64 | pub fn help() -> Self { 65 | Self { 66 | kind: CommandKind::Help, 67 | auth: &|_| async { Ok(true) }, 68 | handler: &|_| async { Ok(()) }, 69 | } 70 | } 71 | } 72 | 73 | pub struct Args { 74 | pub cx: Context, 75 | pub msg: Message, 76 | pub params: HashMap<&'static str, String>, 77 | pub http: Arc, 78 | pub db: Arc, 79 | } 80 | 81 | async fn execute_command(args: Arc, handler: &'static Handler) { 82 | info!("Executing command"); 83 | if let Err(e) = handler.call(args).await { 84 | error!("{}", e); 85 | } 86 | } 87 | 88 | pub struct Commands { 89 | state_machine: StateMachine, 90 | command_map: HashMap>, 91 | menu: Option>, 92 | } 93 | 94 | impl Commands { 95 | pub fn new() -> Self { 96 | Self { 97 | state_machine: StateMachine::new(), 98 | command_map: HashMap::new(), 99 | menu: Some(IndexMap::new()), 100 | } 101 | } 102 | 103 | pub fn add(&mut self, input: &'static str, command: Command) { 104 | info!("Adding command {}", &input); 105 | let mut state = 0; 106 | 107 | let mut reused_space_state = None; 108 | let mut opt_final_states = vec![]; 109 | 110 | let handler = Arc::new(command); 111 | 112 | input 113 | .split(' ') 114 | .filter(|segment| segment.len() > 0) 115 | .enumerate() 116 | .for_each(|(i, segment)| { 117 | if let Some(name) = key_value_pair(segment) { 118 | if let Some(lambda) = reused_space_state { 119 | state = self.add_key_value(name, lambda); 120 | self.state_machine.add_next_state(state, lambda); 121 | opt_final_states.push(state); 122 | 123 | state = self.add_quoted_key_value(name, lambda); 124 | self.state_machine.add_next_state(state, lambda); 125 | opt_final_states.push(state); 126 | } else { 127 | opt_final_states.push(state); 128 | state = self.add_space(state, i); 129 | reused_space_state = Some(state); 130 | 131 | state = self.add_key_value(name, state); 132 | self.state_machine 133 | .add_next_state(state, reused_space_state.unwrap()); 134 | opt_final_states.push(state); 135 | 136 | state = self.add_quoted_key_value(name, reused_space_state.unwrap()); 137 | self.state_machine 138 | .add_next_state(state, reused_space_state.unwrap()); 139 | opt_final_states.push(state); 140 | } 141 | } else { 142 | reused_space_state = None; 143 | opt_final_states.truncate(0); 144 | let last_state = state; 145 | state = self.add_space(state, i); 146 | 147 | if segment.starts_with("```\n") && segment.ends_with("```") { 148 | state = self.add_code_segment_multi_line(state, segment); 149 | } else if segment.starts_with("```") && segment.ends_with("```") { 150 | state = self.add_code_segment_single_line(state, segment, 3); 151 | } else if segment.starts_with('`') && segment.ends_with('`') { 152 | state = self.add_code_segment_single_line(state, segment, 1); 153 | } else if segment.starts_with('{') && segment.ends_with('}') { 154 | state = self.add_dynamic_segment(state, segment); 155 | } else if segment.ends_with("...") { 156 | if segment == "..." { 157 | self.state_machine.set_final_state(last_state); 158 | self.command_map.insert(last_state, handler.clone()); 159 | state = self.add_unnamed_remaining_segment(last_state); 160 | } else { 161 | state = self.add_remaining_segment(state, segment); 162 | } 163 | } else { 164 | segment.chars().for_each(|ch| { 165 | state = self.state_machine.add(state, CharacterSet::from_char(ch)) 166 | }); 167 | } 168 | } 169 | }); 170 | 171 | if reused_space_state.is_some() { 172 | opt_final_states.iter().for_each(|state| { 173 | self.state_machine.set_final_state(*state); 174 | self.command_map.insert(*state, handler.clone()); 175 | }); 176 | } else { 177 | self.state_machine.set_final_state(state); 178 | self.command_map.insert(state, handler.clone()); 179 | } 180 | } 181 | 182 | pub fn help(&mut self, cmd: &'static str, desc: &'static str, command: Command) { 183 | let base_cmd = &cmd[1..]; 184 | info!("Adding command ?help {}", &base_cmd); 185 | let mut state = 0; 186 | 187 | self.menu.as_mut().map(|menu| { 188 | menu.insert(cmd, (desc, command.auth)); 189 | menu 190 | }); 191 | 192 | state = self.add_help_menu(base_cmd, state); 193 | self.state_machine.set_final_state(state); 194 | self.command_map.insert(state, Arc::new(command)); 195 | } 196 | 197 | pub async fn execute(&self, cx: Context, msg: Message, http: Arc, db: Arc) { 198 | let message = &msg.content; 199 | if !msg.is_own(&cx) && message.starts_with(PREFIX) { 200 | if let Some(matched) = self.state_machine.process(message) { 201 | info!("Processing command: {}", message); 202 | let args = Arc::new(Args { 203 | cx, 204 | msg, 205 | params: matched.params, 206 | http: http.clone(), 207 | db: db.clone(), 208 | }); 209 | 210 | let command = self.command_map.get(&matched.state).unwrap(); 211 | 212 | match command.kind { 213 | CommandKind::Base => { 214 | execute_command(args.clone(), command.handler).await; 215 | } 216 | CommandKind::Protected => match command.auth.call(args.clone()).await { 217 | Ok(true) => { 218 | execute_command(args.clone(), command.handler).await; 219 | } 220 | Ok(false) => { 221 | info!("Not executing command, unauthorized"); 222 | if let Err(e) = api::send_reply( 223 | args.clone(), 224 | "You do not have permission to run this command", 225 | ) 226 | .await 227 | { 228 | error!("{}", e); 229 | } 230 | } 231 | Err(e) => error!("{}", e), 232 | }, 233 | CommandKind::Help => { 234 | let output = 235 | api::main_menu(args.clone(), self.menu.as_ref().unwrap()).await; 236 | if let Err(e) = 237 | api::send_reply(args.clone(), &format!("```{}```", &output)).await 238 | { 239 | error!("{}", e) 240 | } 241 | } 242 | }; 243 | } 244 | } 245 | } 246 | 247 | fn add_space(&mut self, mut state: usize, i: usize) -> usize { 248 | if i > 0 { 249 | let char_set = CharacterSet::from_chars(&[' ', '\n']); 250 | 251 | state = self.state_machine.add(state, char_set); 252 | self.state_machine.add_next_state(state, state); 253 | } 254 | state 255 | } 256 | 257 | fn add_help_menu(&mut self, cmd: &'static str, mut state: usize) -> usize { 258 | "?help".chars().for_each(|ch| { 259 | state = self.state_machine.add(state, CharacterSet::from_char(ch)); 260 | }); 261 | state = self.add_space(state, 1); 262 | cmd.chars().for_each(|ch| { 263 | state = self.state_machine.add(state, CharacterSet::from_char(ch)); 264 | }); 265 | 266 | state 267 | } 268 | 269 | fn add_dynamic_segment(&mut self, mut state: usize, s: &'static str) -> usize { 270 | let name = &s[1..s.len() - 1]; 271 | 272 | let mut char_set = CharacterSet::any(); 273 | char_set.remove(&[' ']); 274 | state = self.state_machine.add(state, char_set); 275 | self.state_machine.add_next_state(state, state); 276 | self.state_machine.start_parse(state, name); 277 | self.state_machine.end_parse(state); 278 | 279 | state 280 | } 281 | 282 | fn add_remaining_segment(&mut self, mut state: usize, s: &'static str) -> usize { 283 | let name = &s[..s.len() - 3]; 284 | 285 | let char_set = CharacterSet::any(); 286 | state = self.state_machine.add(state, char_set); 287 | self.state_machine.add_next_state(state, state); 288 | self.state_machine.start_parse(state, name); 289 | self.state_machine.end_parse(state); 290 | 291 | state 292 | } 293 | 294 | fn add_unnamed_remaining_segment(&mut self, mut state: usize) -> usize { 295 | let char_set = CharacterSet::any(); 296 | state = self.state_machine.add(state, char_set); 297 | self.state_machine.add_next_state(state, state); 298 | 299 | state 300 | } 301 | 302 | fn add_code_segment_multi_line(&mut self, mut state: usize, s: &'static str) -> usize { 303 | let name = &s[4..s.len() - 3]; 304 | 305 | "```".chars().for_each(|ch| { 306 | state = self.state_machine.add(state, CharacterSet::from_char(ch)); 307 | }); 308 | 309 | let lambda = state; 310 | 311 | let mut char_set = CharacterSet::any(); 312 | char_set.remove(&['`', ' ', '\n']); 313 | state = self.state_machine.add(state, char_set); 314 | self.state_machine.add_next_state(state, state); 315 | 316 | state = self.state_machine.add(state, CharacterSet::from_char('\n')); 317 | 318 | self.state_machine.add_next_state(lambda, state); 319 | 320 | state = self.state_machine.add(state, CharacterSet::any()); 321 | self.state_machine.add_next_state(state, state); 322 | self.state_machine.start_parse(state, name); 323 | self.state_machine.end_parse(state); 324 | 325 | "```".chars().for_each(|ch| { 326 | state = self.state_machine.add(state, CharacterSet::from_char(ch)); 327 | }); 328 | 329 | state 330 | } 331 | 332 | fn add_code_segment_single_line( 333 | &mut self, 334 | mut state: usize, 335 | s: &'static str, 336 | n_backticks: usize, 337 | ) -> usize { 338 | use std::iter::repeat; 339 | 340 | let name = &s[n_backticks..s.len() - n_backticks]; 341 | 342 | repeat('`').take(n_backticks).for_each(|ch| { 343 | state = self.state_machine.add(state, CharacterSet::from_char(ch)); 344 | }); 345 | state = self.state_machine.add(state, CharacterSet::any()); 346 | self.state_machine.add_next_state(state, state); 347 | self.state_machine.start_parse(state, name); 348 | self.state_machine.end_parse(state); 349 | repeat('`').take(n_backticks).for_each(|ch| { 350 | state = self.state_machine.add(state, CharacterSet::from_char(ch)); 351 | }); 352 | 353 | state 354 | } 355 | 356 | fn add_key_value(&mut self, name: &'static str, mut state: usize) -> usize { 357 | name.chars().for_each(|c| { 358 | state = self.state_machine.add(state, CharacterSet::from_char(c)); 359 | }); 360 | state = self.state_machine.add(state, CharacterSet::from_char('=')); 361 | 362 | let mut char_set = CharacterSet::any(); 363 | char_set.remove(&[' ', '\n', '"']); 364 | state = self.state_machine.add(state, char_set); 365 | self.state_machine.add_next_state(state, state); 366 | self.state_machine.start_parse(state, name); 367 | self.state_machine.end_parse(state); 368 | 369 | state 370 | } 371 | 372 | fn add_quoted_key_value(&mut self, name: &'static str, mut state: usize) -> usize { 373 | name.chars().for_each(|c| { 374 | state = self.state_machine.add(state, CharacterSet::from_char(c)); 375 | }); 376 | state = self.state_machine.add(state, CharacterSet::from_char('=')); 377 | state = self.state_machine.add(state, CharacterSet::from_char('"')); 378 | 379 | let mut char_set = CharacterSet::any(); 380 | char_set.remove(&['"']); 381 | state = self.state_machine.add(state, char_set); 382 | self.state_machine.add_next_state(state, state); 383 | self.state_machine.start_parse(state, name); 384 | self.state_machine.end_parse(state); 385 | 386 | state = self.state_machine.add(state, CharacterSet::from_char('"')); 387 | 388 | state 389 | } 390 | } 391 | 392 | fn key_value_pair(s: &'static str) -> Option<&'static str> { 393 | s.match_indices("={}") 394 | .next() 395 | .map(|pair| { 396 | let name = &s[0..pair.0]; 397 | if name.len() > 0 { 398 | Some(name) 399 | } else { 400 | None 401 | } 402 | }) 403 | .flatten() 404 | } 405 | -------------------------------------------------------------------------------- /src/crates.rs: -------------------------------------------------------------------------------- 1 | use crate::{api, commands::Args, Error}; 2 | use reqwest::header; 3 | use serde::Deserialize; 4 | use std::sync::Arc; 5 | use tracing::info; 6 | 7 | const USER_AGENT: &str = "rust-lang/discord-mods-bot"; 8 | 9 | #[derive(Debug, Deserialize)] 10 | struct Crates { 11 | crates: Vec, 12 | } 13 | #[derive(Debug, Deserialize)] 14 | struct Crate { 15 | id: String, 16 | name: String, 17 | newest_version: String, 18 | max_stable_version: Option, 19 | #[serde(rename = "updated_at")] 20 | updated: String, 21 | downloads: u64, 22 | #[serde(default)] 23 | description: String, 24 | documentation: Option, 25 | } 26 | 27 | async fn get_crate(args: Arc) -> Result, Error> { 28 | let query = args 29 | .params 30 | .get("query") 31 | .ok_or("Unable to retrieve param: query")?; 32 | 33 | info!("searching for crate `{}`", query); 34 | 35 | let crate_list = args 36 | .http 37 | .get("https://crates.io/api/v1/crates") 38 | .header(header::USER_AGENT, USER_AGENT) 39 | .query(&[("q", query)]) 40 | .send() 41 | .await? 42 | .json::() 43 | .await?; 44 | 45 | Ok(crate_list.crates.into_iter().next()) 46 | } 47 | 48 | pub async fn search(args: Arc) -> Result<(), Error> { 49 | if let Some(krate) = get_crate(args.clone()).await? { 50 | args.msg 51 | .channel_id 52 | .send_message(&args.cx, |m| { 53 | m.embed(|e| { 54 | e.title(&krate.name) 55 | .url(format!("https://crates.io/crates/{}", krate.id)) 56 | .description(&krate.description) 57 | .field( 58 | "version", 59 | krate 60 | .max_stable_version 61 | .as_ref() 62 | .unwrap_or(&krate.newest_version), 63 | true, 64 | ) 65 | .field("downloads", &krate.downloads, true) 66 | .timestamp(krate.updated.as_str()) 67 | }); 68 | 69 | m 70 | }) 71 | .await?; 72 | } else { 73 | let message = "No crates found."; 74 | api::send_reply(args.clone(), message).await?; 75 | } 76 | 77 | Ok(()) 78 | } 79 | 80 | fn rustc_crate(crate_name: &str) -> Option<&str> { 81 | match crate_name { 82 | "std" => Some("https://doc.rust-lang.org/stable/std/"), 83 | "core" => Some("https://doc.rust-lang.org/stable/core/"), 84 | "alloc" => Some("https://doc.rust-lang.org/stable/alloc/"), 85 | "proc_macro" => Some("https://doc.rust-lang.org/stable/proc_macro/"), 86 | "beta" => Some("https://doc.rust-lang.org/beta/std/"), 87 | "nightly" => Some("https://doc.rust-lang.org/nightly/std/"), 88 | "rustc" => Some("https://doc.rust-lang.org/nightly/nightly-rustc/"), 89 | _ => None, 90 | } 91 | } 92 | 93 | pub async fn doc_search(args: Arc) -> Result<(), Error> { 94 | let query = args 95 | .params 96 | .get("query") 97 | .ok_or("Unable to retrieve param: query")?; 98 | 99 | let mut query_iter = query.splitn(2, "::"); 100 | let crate_name = query_iter.next().unwrap(); 101 | 102 | let doc_url = if let Some(rustc_crate) = rustc_crate(crate_name) { 103 | Some(rustc_crate.to_string()) 104 | } else if let Some(krate) = get_crate(args.clone()).await? { 105 | let name = krate.name; 106 | krate 107 | .documentation 108 | .or_else(|| Some(format!("https://docs.rs/{}", name))) 109 | } else { 110 | None 111 | }; 112 | 113 | if let Some(mut url) = doc_url { 114 | if let Some(item_path) = query_iter.next() { 115 | url += &format!("?search={}", item_path); 116 | } 117 | 118 | api::send_reply(args.clone(), &url).await?; 119 | } else { 120 | let message = "No crates found."; 121 | api::send_reply(args.clone(), message).await?; 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | /// Print the help message 128 | pub async fn help(args: Arc) -> Result<(), Error> { 129 | let help_string = "search for a crate on crates.io 130 | ``` 131 | ?crate query... 132 | ```"; 133 | api::send_reply(args.clone(), &help_string).await?; 134 | Ok(()) 135 | } 136 | 137 | /// Print the help message 138 | pub async fn doc_help(args: Arc) -> Result<(), Error> { 139 | let help_string = "retrieve documentation for a given crate 140 | ``` 141 | ?docs crate_name... 142 | ```"; 143 | api::send_reply(args.clone(), &help_string).await?; 144 | Ok(()) 145 | } 146 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use diesel::prelude::*; 3 | use tracing::info; 4 | 5 | pub fn run_migrations() -> Result<(), Error> { 6 | let conn = PgConnection::establish(&std::env::var("DATABASE_URL")?)?; 7 | 8 | diesel_migrations::embed_migrations!(); 9 | 10 | info!("Running database migrations"); 11 | let _ = embedded_migrations::run_with_output(&conn, &mut std::io::stdout())?; 12 | 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /src/jobs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ban::unban_users, command_history::clear_command_history, Error, HOUR}; 2 | use serenity::client::Context; 3 | use sqlx::postgres::PgPool; 4 | use std::sync::{ 5 | atomic::{AtomicBool, Ordering}, 6 | Arc, 7 | }; 8 | use tokio::time::{sleep, Duration}; 9 | 10 | static JOBS_THREAD_INITIALIZED: AtomicBool = AtomicBool::new(false); 11 | 12 | pub fn start_jobs(cx: Context, db: Arc) { 13 | if !JOBS_THREAD_INITIALIZED.load(Ordering::SeqCst) { 14 | JOBS_THREAD_INITIALIZED.store(true, Ordering::SeqCst); 15 | tokio::spawn(async move { 16 | loop { 17 | unban_users(&cx, db.clone()).await?; 18 | clear_command_history(&cx).await?; 19 | 20 | sleep(Duration::new(HOUR, 0)).await; 21 | } 22 | 23 | Ok::<_, Error>(()) 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate diesel; 3 | 4 | #[macro_use] 5 | extern crate diesel_migrations; 6 | 7 | mod api; 8 | mod ban; 9 | mod command_history; 10 | mod commands; 11 | mod crates; 12 | mod db; 13 | mod jobs; 14 | mod playground; 15 | mod schema; 16 | mod state_machine; 17 | mod tags; 18 | mod text; 19 | mod welcome; 20 | 21 | pub type Error = Box; 22 | 23 | pub const HOUR: u64 = 3600; 24 | 25 | use crate::commands::{Command, Commands}; 26 | use indexmap::IndexMap; 27 | use reqwest::Client as HttpClient; 28 | use serde::Deserialize; 29 | use serenity::{async_trait, model::prelude::*, prelude::*}; 30 | use sqlx::postgres::{PgPool, PgPoolOptions}; 31 | use std::sync::Arc; 32 | use tracing::{error, info}; 33 | 34 | #[derive(Deserialize)] 35 | struct Config { 36 | tags: bool, 37 | crates: bool, 38 | eval: bool, 39 | discord_token: String, 40 | mod_id: String, 41 | talk_id: String, 42 | wg_and_teams_id: Option, 43 | } 44 | 45 | async fn upsert_role( 46 | name: &str, 47 | role_id: &str, 48 | transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, 49 | ) -> Result<(), Error> { 50 | sqlx::query( 51 | "insert into roles(role, name) values ($1, $2) 52 | on conflict (name) do update set role = $1", 53 | ) 54 | .bind(role_id) 55 | .bind(name) 56 | .execute(transaction) 57 | .await?; 58 | 59 | Ok(()) 60 | } 61 | 62 | async fn init_data(config: &Config, pool: Arc) -> Result<(), Error> { 63 | info!("Loading data into database"); 64 | 65 | let mut transaction = pool.begin().await?; 66 | 67 | upsert_role("mod", &config.mod_id, &mut transaction).await?; 68 | upsert_role("talk", &config.talk_id, &mut transaction).await?; 69 | 70 | if config.tags || config.crates { 71 | let wg_and_teams_role = config 72 | .wg_and_teams_id 73 | .as_ref() 74 | .ok_or(text::WG_AND_TEAMS_MISSING_ENV_VAR)?; 75 | upsert_role("wg_and_teams", &wg_and_teams_role, &mut transaction).await?; 76 | } 77 | 78 | transaction.commit().await?; 79 | 80 | Ok(()) 81 | } 82 | 83 | async fn app() -> Result<(), Error> { 84 | let config = envy::from_env::()?; 85 | 86 | tracing_subscriber::fmt::init(); 87 | 88 | info!("starting..."); 89 | 90 | let pool = Arc::new( 91 | PgPoolOptions::new() 92 | .connect(&std::env::var("DATABASE_URL")?) 93 | .await?, 94 | ); 95 | 96 | let _ = db::run_migrations()?; 97 | 98 | let _ = init_data(&config, pool.clone()).await?; 99 | 100 | let mut cmds = Commands::new(); 101 | 102 | if config.tags { 103 | // Tags 104 | cmds.add( 105 | "?tags delete {key}", 106 | Command::new_with_auth(&tags::delete, &api::is_wg_and_teams), 107 | ); 108 | cmds.add( 109 | "?tags create {key} value...", 110 | Command::new_with_auth(&tags::post, &api::is_wg_and_teams), 111 | ); 112 | cmds.add( 113 | "?tags update {key} value...", 114 | Command::new_with_auth(&tags::update, &api::is_wg_and_teams), 115 | ); 116 | cmds.add("?tag {key}", Command::new(&tags::get)); 117 | cmds.add("?tags", Command::new(&tags::get_all)); 118 | cmds.help("?tags", "A key value store", Command::new(&tags::help)); 119 | } 120 | 121 | if config.crates { 122 | // crates.io 123 | cmds.add("?crate query...", Command::new(&crates::search)); 124 | cmds.help( 125 | "?crate", 126 | "Lookup crates on crates.io", 127 | Command::new(&crates::help), 128 | ); 129 | 130 | // docs.rs 131 | cmds.add("?docs query...", Command::new(&crates::doc_search)); 132 | cmds.help( 133 | "?docs", 134 | "Lookup documentation", 135 | Command::new(&crates::doc_help), 136 | ); 137 | } 138 | 139 | if config.eval { 140 | // rust playground 141 | cmds.add( 142 | "?play mode={} edition={} channel={} warn={} ```\ncode``` ...", 143 | Command::new(&playground::run), 144 | ); 145 | cmds.add("?play code...", Command::new(&playground::err)); 146 | cmds.help( 147 | "?play", 148 | "Compile and run rust code in a playground", 149 | Command::new(&|args| async { playground::help(args, "play").await }), 150 | ); 151 | 152 | cmds.add( 153 | "?eval mode={} edition={} channel={} warn={} ```\ncode``` ...", 154 | Command::new(&playground::eval), 155 | ); 156 | cmds.add( 157 | "?eval mode={} edition={} channel={} warn={} ```code``` ...", 158 | Command::new(&playground::eval), 159 | ); 160 | cmds.add( 161 | "?eval mode={} edition={} channel={} warn={} `code` ...", 162 | Command::new(&playground::eval), 163 | ); 164 | cmds.add("?eval code...", Command::new(&playground::eval_err)); 165 | cmds.help( 166 | "?eval", 167 | "Evaluate a single rust expression", 168 | Command::new(&|args| async { playground::help(args, "eval").await }), 169 | ); 170 | } 171 | 172 | // Slow mode. 173 | // 0 seconds disables slowmode 174 | cmds.add( 175 | "?slowmode {channel} {seconds}", 176 | Command::new_with_auth(&api::slow_mode, &api::is_mod), 177 | ); 178 | cmds.help( 179 | "?slowmode", 180 | "Set slowmode on a channel", 181 | Command::new_with_auth(&api::slow_mode_help, &api::is_mod), 182 | ); 183 | 184 | // Kick 185 | cmds.add( 186 | "?kick {user}", 187 | Command::new_with_auth(&api::kick, &api::is_mod), 188 | ); 189 | cmds.help( 190 | "?kick", 191 | "Kick a user from the guild", 192 | Command::new_with_auth(&api::kick_help, &api::is_mod), 193 | ); 194 | 195 | // Ban 196 | cmds.add( 197 | "?ban {user} {hours} reason...", 198 | Command::new_with_auth(&ban::temp_ban, &api::is_mod), 199 | ); 200 | cmds.help( 201 | "?ban", 202 | "Temporarily ban a user from the guild", 203 | Command::new_with_auth(&ban::help, &api::is_mod), 204 | ); 205 | 206 | // Post the welcome message to the welcome channel. 207 | cmds.add( 208 | "?CoC {channel}", 209 | Command::new_with_auth(&welcome::post_message, &api::is_mod), 210 | ); 211 | cmds.help( 212 | "?CoC", 213 | "Post the code of conduct message to a channel", 214 | Command::new_with_auth(&welcome::help, &api::is_mod), 215 | ); 216 | 217 | cmds.add("?help", Command::help()); 218 | 219 | let mut client = Client::builder(&config.discord_token, GatewayIntents::all()) 220 | .event_handler(Events { 221 | http: Arc::new(HttpClient::new()), 222 | db: pool.clone(), 223 | cmds: Arc::new(cmds), 224 | }) 225 | .await?; 226 | 227 | client.start().await?; 228 | 229 | Ok(()) 230 | } 231 | 232 | #[tokio::main] 233 | async fn main() { 234 | if let Err(e) = app().await { 235 | error!("{}", e); 236 | std::process::exit(1); 237 | } 238 | } 239 | 240 | struct Events { 241 | http: Arc, 242 | db: Arc, 243 | cmds: Arc, 244 | } 245 | 246 | #[async_trait] 247 | impl EventHandler for Events { 248 | async fn ready(&self, cx: Context, ready: Ready) { 249 | info!("{} connected to discord", ready.user.name); 250 | { 251 | let mut data = cx.data.write().await; 252 | data.insert::(IndexMap::new()); 253 | } 254 | 255 | jobs::start_jobs(cx, self.db.clone()); 256 | } 257 | 258 | async fn message(&self, cx: Context, message: Message) { 259 | self.cmds 260 | .execute(cx, message, self.http.clone(), self.db.clone()) 261 | .await; 262 | } 263 | 264 | async fn message_update( 265 | &self, 266 | cx: Context, 267 | _: Option, 268 | _: Option, 269 | ev: MessageUpdateEvent, 270 | ) { 271 | if let Err(e) = 272 | command_history::replay_message(cx, ev, &self.cmds, self.http.clone(), self.db.clone()) 273 | .await 274 | { 275 | error!("{}", e); 276 | } 277 | } 278 | 279 | async fn message_delete( 280 | &self, 281 | cx: Context, 282 | channel_id: ChannelId, 283 | message_id: MessageId, 284 | _guild_id: Option, 285 | ) { 286 | let mut data = cx.data.write().await; 287 | let history = data.get_mut::().unwrap(); 288 | if let Some(response_id) = history.remove(&message_id) { 289 | info!("deleting message: {:?}", response_id); 290 | let _ = channel_id.delete_message(&cx, response_id).await; 291 | } 292 | } 293 | 294 | async fn reaction_add(&self, cx: Context, reaction: Reaction) { 295 | if let Err(e) = welcome::assign_talk_role(&cx, &reaction, self.db.clone()).await { 296 | error!("{}", e); 297 | } 298 | } 299 | 300 | async fn guild_ban_removal(&self, _cx: Context, guild_id: GuildId, user: User) { 301 | if let Err(e) = ban::save_unban( 302 | format!("{}", user.id), 303 | format!("{}", guild_id), 304 | self.db.clone(), 305 | ) 306 | .await 307 | { 308 | error!("{}", e); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/playground.rs: -------------------------------------------------------------------------------- 1 | //! run rust code on the rust-lang playground 2 | 3 | use crate::{api, commands::Args, Error}; 4 | use reqwest::header; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | use std::str::FromStr; 8 | use std::sync::Arc; 9 | use tracing::info; 10 | 11 | const MAX_OUTPUT_LINES: usize = 45; 12 | 13 | #[derive(Debug, Serialize)] 14 | struct PlaygroundCode { 15 | channel: Channel, 16 | edition: Edition, 17 | code: String, 18 | #[serde(rename = "crateType")] 19 | crate_type: CrateType, 20 | mode: Mode, 21 | tests: bool, 22 | } 23 | 24 | impl PlaygroundCode { 25 | fn new(code: String) -> Self { 26 | PlaygroundCode { 27 | channel: Channel::Nightly, 28 | edition: Edition::E2018, 29 | code, 30 | crate_type: CrateType::Binary, 31 | mode: Mode::Debug, 32 | tests: false, 33 | } 34 | } 35 | 36 | fn url_from_gist(&self, gist: &str) -> String { 37 | let version = match self.channel { 38 | Channel::Nightly => "nightly", 39 | Channel::Beta => "beta", 40 | Channel::Stable => "stable", 41 | }; 42 | 43 | let edition = match self.edition { 44 | Edition::E2015 => "2015", 45 | Edition::E2018 => "2018", 46 | Edition::E2021 => "2021", 47 | Edition::E2024 => "2024", 48 | }; 49 | 50 | let mode = match self.mode { 51 | Mode::Debug => "debug", 52 | Mode::Release => "release", 53 | }; 54 | 55 | format!( 56 | "https://play.rust-lang.org/?version={}&mode={}&edition={}&gist={}", 57 | version, mode, edition, gist 58 | ) 59 | } 60 | } 61 | 62 | #[derive(Debug, Serialize)] 63 | #[serde(rename_all = "snake_case")] 64 | enum Channel { 65 | Stable, 66 | Beta, 67 | Nightly, 68 | } 69 | 70 | impl FromStr for Channel { 71 | type Err = Box; 72 | 73 | fn from_str(s: &str) -> Result { 74 | match s { 75 | "stable" => Ok(Channel::Stable), 76 | "beta" => Ok(Channel::Beta), 77 | "nightly" => Ok(Channel::Nightly), 78 | _ => Err(format!("invalid release channel `{}`", s).into()), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Debug, Serialize)] 84 | enum Edition { 85 | #[serde(rename = "2015")] 86 | E2015, 87 | #[serde(rename = "2018")] 88 | E2018, 89 | #[serde(rename = "2021")] 90 | E2021, 91 | #[serde(rename = "2024")] 92 | E2024, 93 | } 94 | 95 | impl FromStr for Edition { 96 | type Err = Box; 97 | 98 | fn from_str(s: &str) -> Result { 99 | match s { 100 | "2015" => Ok(Edition::E2015), 101 | "2018" => Ok(Edition::E2018), 102 | "2021" => Ok(Edition::E2021), 103 | "2024" => Ok(Edition::E2024), 104 | _ => Err(format!("invalid edition `{}`", s).into()), 105 | } 106 | } 107 | } 108 | 109 | #[derive(Debug, Serialize)] 110 | enum CrateType { 111 | #[serde(rename = "bin")] 112 | Binary, 113 | #[serde(rename = "lib")] 114 | Library, 115 | } 116 | 117 | #[derive(Debug, Serialize)] 118 | #[serde(rename_all = "snake_case")] 119 | enum Mode { 120 | Debug, 121 | Release, 122 | } 123 | 124 | impl FromStr for Mode { 125 | type Err = Box; 126 | 127 | fn from_str(s: &str) -> Result { 128 | match s { 129 | "debug" => Ok(Mode::Debug), 130 | "release" => Ok(Mode::Release), 131 | _ => Err(format!("invalid compilation mode `{}`", s).into()), 132 | } 133 | } 134 | } 135 | 136 | #[derive(Debug, Deserialize)] 137 | struct PlayResult { 138 | success: bool, 139 | stdout: String, 140 | stderr: String, 141 | } 142 | 143 | async fn run_code(args: Arc, code: String) -> Result { 144 | let mut errors = String::new(); 145 | 146 | let warnings = args.params.get("warn").map(|s| &s[..]).unwrap_or("false"); 147 | let channel = args 148 | .params 149 | .get("channel") 150 | .map(|s| &s[..]) 151 | .unwrap_or("nightly"); 152 | let mode = args.params.get("mode").map(|s| &s[..]).unwrap_or("debug"); 153 | let edition = args.params.get("edition").map(|s| &s[..]).unwrap_or("2024"); 154 | 155 | let mut request = PlaygroundCode::new(code.clone()); 156 | 157 | match Channel::from_str(&channel) { 158 | Ok(c) => request.channel = c, 159 | Err(e) => errors += &format!("{}\n", e), 160 | } 161 | 162 | match Mode::from_str(&mode) { 163 | Ok(m) => request.mode = m, 164 | Err(e) => errors += &format!("{}\n", e), 165 | } 166 | 167 | match Edition::from_str(&edition) { 168 | Ok(e) => request.edition = e, 169 | Err(e) => errors += &format!("{}\n", e), 170 | } 171 | 172 | if !code.contains("fn main") { 173 | request.crate_type = CrateType::Library; 174 | } 175 | 176 | let message = "*Running code on playground...*"; 177 | api::send_reply(args.clone(), message).await?; 178 | 179 | let resp = args 180 | .http 181 | .post("https://play.rust-lang.org/execute") 182 | .json(&request) 183 | .send() 184 | .await?; 185 | 186 | let result: PlayResult = resp.json().await?; 187 | 188 | let result = if warnings == "true" { 189 | format!("{}\n{}", result.stderr, result.stdout) 190 | } else if result.success { 191 | result.stdout 192 | } else { 193 | result.stderr 194 | }; 195 | 196 | let lines = result.lines().count(); 197 | 198 | Ok( 199 | if result.len() + errors.len() > 1993 || lines > MAX_OUTPUT_LINES { 200 | format!( 201 | "{}Output too large. Playground link: {}", 202 | errors, 203 | get_playground_link(args, code, request).await? 204 | ) 205 | } else if result.len() == 0 { 206 | format!("{}compilation succeeded.", errors) 207 | } else { 208 | format!("{}```\n{}```", errors, result) 209 | }, 210 | ) 211 | } 212 | 213 | async fn get_playground_link( 214 | args: Arc, 215 | code: String, 216 | request: PlaygroundCode, 217 | ) -> Result { 218 | let mut payload = HashMap::new(); 219 | payload.insert("code", code); 220 | 221 | let resp = args 222 | .http 223 | .post("https://play.rust-lang.org/meta/gist/") 224 | .header(header::REFERER, "https://discord.gg/rust-lang") 225 | .json(&payload) 226 | .send() 227 | .await?; 228 | 229 | let resp: HashMap = resp.json().await?; 230 | info!("gist response: {:?}", resp); 231 | 232 | resp.get("id") 233 | .map(|id| request.url_from_gist(id)) 234 | .ok_or_else(|| "no gist found".into()) 235 | } 236 | 237 | pub async fn run(args: Arc) -> Result<(), Error> { 238 | let code = args 239 | .params 240 | .get("code") 241 | .map(String::from) 242 | .ok_or("Unable to retrieve param: query")?; 243 | 244 | let result = run_code(args.clone(), code).await?; 245 | api::send_reply(args.clone(), &result).await?; 246 | Ok(()) 247 | } 248 | 249 | pub async fn help(args: Arc, name: &str) -> Result<(), Error> { 250 | let message = format!( 251 | "Compile and run rust code. All code is executed on https://play.rust-lang.org. 252 | ```?{} mode={{}} channel={{}} edition={{}} warn={{}} ``\u{200B}`code``\u{200B}` ``` 253 | Optional arguments: 254 | \tmode: debug, release (default: debug) 255 | \tchannel: stable, beta, nightly (default: nightly) 256 | \tedition: 2015, 2018, 2021, 2024 (default: 2024) 257 | \twarn: boolean flag to enable compilation warnings 258 | ", 259 | name 260 | ); 261 | 262 | api::send_reply(args.clone(), &message).await?; 263 | Ok(()) 264 | } 265 | 266 | pub async fn err(args: Arc) -> Result<(), Error> { 267 | let message = "Missing code block. Please use the following markdown: 268 | \\`\\`\\`rust 269 | code here 270 | \\`\\`\\` 271 | "; 272 | 273 | api::send_reply(args.clone(), message).await?; 274 | Ok(()) 275 | } 276 | 277 | pub async fn eval(args: Arc) -> Result<(), Error> { 278 | let code = args 279 | .params 280 | .get("code") 281 | .map(String::from) 282 | .ok_or("Unable to retrieve param: query")?; 283 | 284 | if code.contains("fn main") { 285 | api::send_reply( 286 | args.clone(), 287 | "code passed to ?eval should not contain `fn main`", 288 | ) 289 | .await?; 290 | } else { 291 | let code = format!("fn main(){{ println!(\"{{:?}}\",{{ {} \n}}); }}", code); 292 | 293 | let result = run_code(args.clone(), code).await?; 294 | api::send_reply(args.clone(), &result).await?; 295 | } 296 | 297 | Ok(()) 298 | } 299 | 300 | pub async fn eval_err(args: Arc) -> Result<(), Error> { 301 | let message = "Missing code block. Please use the following markdown: 302 | \\`code here\\` 303 | or 304 | \\`\\`\\`rust 305 | code here 306 | \\`\\`\\` 307 | "; 308 | 309 | api::send_reply(args.clone(), message).await?; 310 | Ok(()) 311 | } 312 | -------------------------------------------------------------------------------- /src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | bans (id) { 3 | id -> Int4, 4 | user_id -> Text, 5 | guild_id -> Text, 6 | unbanned -> Bool, 7 | start_time -> Timestamp, 8 | end_time -> Timestamp, 9 | } 10 | } 11 | 12 | table! { 13 | messages (id) { 14 | id -> Int4, 15 | name -> Text, 16 | message -> Text, 17 | channel -> Text, 18 | } 19 | } 20 | 21 | table! { 22 | roles (id) { 23 | id -> Int4, 24 | role -> Text, 25 | name -> Text, 26 | } 27 | } 28 | 29 | table! { 30 | tags (id) { 31 | id -> Int4, 32 | key -> Text, 33 | value -> Text, 34 | } 35 | } 36 | 37 | table! { 38 | users (id) { 39 | id -> Int4, 40 | name -> Text, 41 | user_id -> Text, 42 | } 43 | } 44 | 45 | allow_tables_to_appear_in_same_query!(bans, messages, roles, tags, users,); 46 | -------------------------------------------------------------------------------- /src/state_machine.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, u64}; 2 | 3 | /// # CharacterSet 4 | /// 5 | /// Stores the characters for a character set 6 | #[derive(Eq, PartialEq, Clone, Debug)] 7 | pub struct CharacterSet { 8 | low_mask: u64, 9 | high_mask: u64, 10 | any: bool, 11 | } 12 | 13 | impl CharacterSet { 14 | pub fn new() -> Self { 15 | Self { 16 | low_mask: 0, 17 | high_mask: 0, 18 | any: false, 19 | } 20 | } 21 | 22 | pub fn any() -> Self { 23 | Self { 24 | low_mask: u64::MAX, 25 | high_mask: u64::MAX, 26 | any: true, 27 | } 28 | } 29 | 30 | /// Add a character to the character set. 31 | pub fn insert(&mut self, ch: char) { 32 | let val = ch as u32 - 1; 33 | 34 | match val { 35 | 0..=63 => { 36 | let bit = 1 << val; 37 | self.low_mask |= bit; 38 | } 39 | 64..=127 => { 40 | let bit = 1 << val - 64; 41 | self.high_mask |= bit; 42 | } 43 | _ => {} 44 | } 45 | } 46 | 47 | /// Remove characters from the character set. 48 | pub fn remove(&mut self, chs: &[char]) { 49 | chs.iter().for_each(|ch| { 50 | let val = *ch as u32 - 1; 51 | 52 | match val { 53 | 0..=63 => { 54 | let bit = 1 << val; 55 | self.low_mask &= !bit; 56 | } 57 | 64..=127 => { 58 | let bit = 1 << val - 64; 59 | self.high_mask &= !bit; 60 | } 61 | _ => {} 62 | } 63 | }); 64 | } 65 | 66 | /// Check if the character `ch` is a member of the character set. 67 | pub fn contains(&self, ch: char) -> bool { 68 | let val = ch as u32 - 1; 69 | 70 | match val { 71 | 0..=63 => { 72 | let bit = 1 << val; 73 | self.low_mask & bit != 0 74 | } 75 | 64..=127 => { 76 | // flip a bit within 0 - 63 77 | let bit = 1 << val - 64; 78 | self.high_mask & bit != 0 79 | } 80 | _ => self.any, 81 | } 82 | } 83 | 84 | /// Insert the character `ch` into the character set. 85 | pub fn from_char(ch: char) -> Self { 86 | let mut chars = Self::new(); 87 | chars.insert(ch); 88 | chars 89 | } 90 | 91 | /// Insert the characters `chs` into the character set. 92 | pub fn from_chars(chs: &[char]) -> Self { 93 | let mut chars = Self::new(); 94 | chs.iter().for_each(|ch| chars.insert(*ch)); 95 | chars 96 | } 97 | } 98 | 99 | pub struct State { 100 | index: usize, 101 | expected: CharacterSet, 102 | next_states: Vec, 103 | is_final_state: bool, 104 | } 105 | 106 | impl PartialEq for State { 107 | fn eq(&self, other: &State) -> bool { 108 | self.index == other.index 109 | } 110 | } 111 | 112 | impl State { 113 | pub fn new(index: usize, expected: CharacterSet) -> Self { 114 | Self { 115 | index, 116 | expected, 117 | next_states: Vec::new(), 118 | is_final_state: false, 119 | } 120 | } 121 | } 122 | 123 | /// # Traversal 124 | #[derive(Debug, Clone)] 125 | pub struct Traversal { 126 | current_state: usize, 127 | positions: Vec<(usize, usize, Option<&'static str>)>, 128 | segment_start: Option, 129 | segment_name: Option<&'static str>, 130 | } 131 | 132 | impl Traversal { 133 | /// Create a new traversal. 134 | pub fn new() -> Self { 135 | Self { 136 | current_state: 0, 137 | positions: Vec::new(), 138 | segment_start: None, 139 | segment_name: None, 140 | } 141 | } 142 | 143 | /// Mark the position in the input where a dynamic segment begins. 144 | pub fn set_segment_start(&mut self, pos: usize, name: &'static str) { 145 | self.segment_start = Some(pos); 146 | self.segment_name = Some(name); 147 | } 148 | 149 | /// Mark the position in the input where a dynamic segment ends. 150 | pub fn set_segment_end(&mut self, pos: usize) { 151 | self.positions 152 | .push((self.segment_start.unwrap(), pos, self.segment_name.take())); 153 | self.segment_start = None; 154 | } 155 | 156 | /// Returns a `HashMap` containing the dynamic segments parsed from the input. 157 | pub fn extract<'a>(&self, input: &'a str) -> HashMap<&'static str, String> { 158 | self.positions 159 | .iter() 160 | .fold(HashMap::new(), |mut hash_map, (start, end, name)| { 161 | hash_map.insert(name.unwrap(), input[*start..*end].to_string()); 162 | hash_map 163 | }) 164 | } 165 | } 166 | 167 | pub struct Match { 168 | pub state: usize, 169 | pub params: HashMap<&'static str, String>, 170 | } 171 | 172 | pub struct StateMachine { 173 | states: Vec, 174 | start_parse: Vec>, 175 | end_parse: Vec, 176 | } 177 | 178 | impl StateMachine { 179 | pub fn new() -> Self { 180 | Self { 181 | states: vec![State::new(0, CharacterSet::new())], 182 | start_parse: vec![None], 183 | end_parse: vec![false], 184 | } 185 | } 186 | 187 | /// Add a state to the state machine. 188 | pub fn add(&mut self, index: usize, expected: CharacterSet) -> usize { 189 | for &next_index in &self.states[index].next_states { 190 | let state = &self.states[next_index]; 191 | if state.expected == expected { 192 | return next_index; 193 | } 194 | } 195 | 196 | let state = self.new_state(expected); 197 | self.states[index].next_states.push(state); 198 | state 199 | } 200 | 201 | /// Add a next state to the next_states of an existing state in the state machine. 202 | pub fn add_next_state(&mut self, index: usize, next_index: usize) { 203 | let next_states = &mut self.states[index].next_states; 204 | 205 | if !next_states.contains(&next_index) { 206 | next_states.push(next_index); 207 | } 208 | } 209 | 210 | fn new_state(&mut self, expected: CharacterSet) -> usize { 211 | let index = self.states.len(); 212 | let state = State::new(index, expected); 213 | 214 | self.states.push(state); 215 | self.start_parse.push(None); 216 | self.end_parse.push(false); 217 | index 218 | } 219 | 220 | /// Set the `is_final_state` flag on a state to true. 221 | pub fn set_final_state(&mut self, index: usize) { 222 | self.states[index].is_final_state = true; 223 | } 224 | 225 | /// Mark that the index in the state machine is a state to start parsing a dynamic 226 | /// segment. 227 | pub fn start_parse(&mut self, index: usize, name: &'static str) { 228 | self.start_parse[index] = Some(name); 229 | } 230 | 231 | /// Mark that the index in the state machine is a state to stop parsing a dynamic 232 | /// segment. 233 | pub fn end_parse(&mut self, index: usize) { 234 | self.end_parse[index] = true; 235 | } 236 | 237 | /// Run the input through the state machine, optionally returning a handler and params. 238 | pub fn process<'m>(&'m self, input: &'m str) -> Option { 239 | let mut traversals = vec![Traversal::new()]; 240 | 241 | for (i, ch) in input.char_indices() { 242 | let next_traversals = self.process_char(traversals, ch, i); 243 | traversals = next_traversals; 244 | 245 | if traversals.is_empty() { 246 | return None; 247 | } 248 | } 249 | 250 | let traversals = traversals 251 | .into_iter() 252 | .filter(|traversal| self.states[traversal.current_state].is_final_state) 253 | .map(|mut traversal| { 254 | if traversal.segment_start.is_some() { 255 | traversal.set_segment_end(input.len()); 256 | } 257 | traversal 258 | }) 259 | .collect::>(); 260 | 261 | if traversals.is_empty() { 262 | None 263 | } else { 264 | let traversal = &traversals[0]; 265 | 266 | Some({ 267 | Match { 268 | state: traversal.current_state, 269 | params: traversal.extract(input), 270 | } 271 | }) 272 | } 273 | } 274 | 275 | fn process_char(&self, traversals: Vec, ch: char, pos: usize) -> Vec { 276 | let mut ret = Vec::with_capacity(traversals.len()); 277 | 278 | for mut traversal in traversals.into_iter() { 279 | let current_state = &self.states[traversal.current_state]; 280 | 281 | let mut count = 0; 282 | let mut state_index = 0; 283 | 284 | current_state.next_states.iter().for_each(|index| { 285 | let next_state = &self.states[*index]; 286 | 287 | if next_state.expected.contains(ch) { 288 | count += 1; 289 | state_index = *index; 290 | } 291 | }); 292 | 293 | if count == 1 { 294 | traversal.current_state = state_index; 295 | self.extract_parse_info(&mut traversal, current_state.index, state_index, pos); 296 | ret.push(traversal); 297 | continue; 298 | } 299 | 300 | current_state.next_states.iter().for_each(|index| { 301 | let next_state = &self.states[*index]; 302 | 303 | if next_state.expected.contains(ch) { 304 | let mut copy = traversal.clone(); 305 | copy.current_state = next_state.index; 306 | self.extract_parse_info(&mut copy, current_state.index, *index, pos); 307 | ret.push(copy); 308 | } 309 | }); 310 | } 311 | ret 312 | } 313 | 314 | fn extract_parse_info( 315 | &self, 316 | traversal: &mut Traversal, 317 | current_state: usize, 318 | next_state: usize, 319 | pos: usize, 320 | ) { 321 | let start_parse = &self.start_parse[next_state]; 322 | 323 | if traversal.segment_start.is_none() && start_parse.is_some() { 324 | traversal.set_segment_start(pos, start_parse.unwrap()); 325 | } 326 | if traversal.segment_start.is_some() 327 | && self.end_parse[current_state] 328 | && current_state != next_state 329 | { 330 | traversal.set_segment_end(pos); 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/tags.rs: -------------------------------------------------------------------------------- 1 | use crate::{api, commands::Args, Error}; 2 | 3 | use std::sync::Arc; 4 | 5 | /// Remove a key value pair from the tags. 6 | pub async fn delete(args: Arc) -> Result<(), Error> { 7 | let key = args 8 | .params 9 | .get("key") 10 | .ok_or("Unable to retrieve param: key")?; 11 | 12 | let query = sqlx::query("delete from tags where key = $1") 13 | .bind(key) 14 | .execute(&*args.clone().db) 15 | .await?; 16 | 17 | match query.rows_affected() { 18 | 0 => { 19 | api::send_reply( 20 | args.clone(), 21 | "A database error occurred when deleting the tag.", 22 | ) 23 | .await?; 24 | } 25 | _ => { 26 | args.msg.react(&args.cx, '✅').await?; 27 | } 28 | } 29 | 30 | Ok(()) 31 | } 32 | 33 | /// Add a key value pair to the tags. 34 | pub async fn post(args: Arc) -> Result<(), Error> { 35 | let key = args 36 | .params 37 | .get("key") 38 | .ok_or("Unable to retrieve param: key")?; 39 | 40 | let value = args 41 | .params 42 | .get("value") 43 | .ok_or("Unable to retrieve param: value")?; 44 | 45 | let query = sqlx::query("insert into tags(key, value) values ($1, $2)") 46 | .bind(key) 47 | .bind(value) 48 | .execute(&*args.clone().db) 49 | .await?; 50 | 51 | match query.rows_affected() { 52 | 0 => { 53 | api::send_reply( 54 | args.clone(), 55 | "A database error occurred when creating the tag.", 56 | ) 57 | .await? 58 | } 59 | _ => { 60 | args.msg.react(&args.cx, '✅').await?; 61 | } 62 | } 63 | Ok(()) 64 | } 65 | 66 | /// Update an existing tag. 67 | pub async fn update(args: Arc) -> Result<(), Error> { 68 | let key = args 69 | .params 70 | .get("key") 71 | .ok_or("Unable to retrieve param: key")?; 72 | 73 | let value = args 74 | .params 75 | .get("value") 76 | .ok_or("Unable to retrieve param: value")?; 77 | 78 | let query = sqlx::query("update tags set value = $1 where key = $2") 79 | .bind(value) 80 | .bind(key) 81 | .execute(&*args.clone().db) 82 | .await?; 83 | 84 | match query.rows_affected() { 85 | 0 => { 86 | api::send_reply( 87 | args.clone(), 88 | "A database error occurred when updating the tag.", 89 | ) 90 | .await? 91 | } 92 | _ => { 93 | args.msg.react(&args.cx, '✅').await?; 94 | } 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | /// Retrieve a value by key from the tags. 101 | pub async fn get(args: Arc) -> Result<(), Error> { 102 | let key = args.params.get("key").ok_or("unable to read params")?; 103 | 104 | let results: Option<(i32, String, String)> = 105 | sqlx::query_as("select * from tags where key = $1 limit 1") 106 | .bind(key) 107 | .fetch_optional(&*args.db) 108 | .await?; 109 | 110 | if let Some(query_result) = results { 111 | api::send_reply(args.clone(), &query_result.2).await?; 112 | } else { 113 | api::send_reply(args.clone(), &format!("Tag not found for `{}`", key)).await?; 114 | } 115 | 116 | Ok(()) 117 | } 118 | 119 | /// Retrieve all tags 120 | pub async fn get_all(args: Arc) -> Result<(), Error> { 121 | let results: Vec<(i32, String, String)> = sqlx::query_as("select * from tags") 122 | .fetch_all(&*args.db) 123 | .await?; 124 | 125 | if results.is_empty() { 126 | api::send_reply(args.clone(), "No tags found").await?; 127 | } else { 128 | let tags = &results.iter().fold(String::new(), |prev, row| { 129 | if prev.len() < 1980 { 130 | prev + &row.1 + "\n" 131 | } else { 132 | prev 133 | } 134 | }); 135 | 136 | api::send_reply(args.clone(), &format!("All tags: ```\n{}```", &tags)).await?; 137 | } 138 | 139 | Ok(()) 140 | } 141 | 142 | /// Print the help message 143 | pub async fn help(args: Arc) -> Result<(), Error> { 144 | let help_string = "``` 145 | ?tags create {key} value... Create a tag. Limited to WG & Teams. 146 | ?tags update {key} value... Update a tag. Limited to WG & Teams. 147 | ?tags delete {key} Delete a tag. Limited to WG & Teams. 148 | ?tags help This menu. 149 | ?tags Get all the tags. 150 | ?tag {key} Get a specific tag. 151 | ```"; 152 | api::send_reply(args.clone(), &help_string).await?; 153 | Ok(()) 154 | } 155 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | pub const WELCOME_BILLBOARD: &str = "By participating in this community, you agree to follow the Rust Code of Conduct, as linked below. Please click the :white_check_mark: below to acknowledge and gain access to the channels. 2 | 3 | https://www.rust-lang.org/policies/code-of-conduct 4 | 5 | If you see someone behaving inappropriately, or otherwise against the Code of Conduct, please contact the mods using `@mods` or by DM'ing a mod from the sidebar. "; 6 | 7 | pub fn ban_message(reason: &str, hours: u64) -> String { 8 | format!("You have been banned from The Rust Programming Language discord server for {}. The ban will expire in {} hours. If you feel this action was taken unfairly, you can reach the Rust moderation team at discord-mods@rust-lang.org", reason, hours) 9 | } 10 | 11 | pub const WG_AND_TEAMS_MISSING_ENV_VAR: &str = "missing value for field wg_and_teams_id.\n\nIf you enabled tags or crates then you need the WG_AND_TEAMS_ID env var."; 12 | -------------------------------------------------------------------------------- /src/welcome.rs: -------------------------------------------------------------------------------- 1 | use crate::{api, commands::Args, text::WELCOME_BILLBOARD, Error}; 2 | use serenity::{model::prelude::*, prelude::*}; 3 | use sqlx::postgres::PgPool; 4 | use std::sync::Arc; 5 | use tracing::info; 6 | 7 | /// Write the welcome message to the welcome channel. 8 | pub async fn post_message(args: Arc) -> Result<(), Error> { 9 | use std::str::FromStr; 10 | 11 | if api::is_mod(args.clone()).await? { 12 | let channel_name = &args 13 | .params 14 | .get("channel") 15 | .ok_or("unable to retrieve channel param")?; 16 | 17 | let channel_id = ChannelId::from_str(channel_name)?; 18 | 19 | info!("Posting welcome message"); 20 | let message = channel_id.say(&args.cx, WELCOME_BILLBOARD).await?; 21 | 22 | let message_id = message.id.0.to_string(); 23 | let bot_id = message.author.id.to_string(); 24 | let channel_id = channel_id.0.to_string(); 25 | 26 | let mut transaction = args.db.begin().await?; 27 | 28 | let save_message = 29 | "insert into messages (name, message, channel) values ('welcome', $1, $2) 30 | on conflict (name) do update set message = $1, channel = $2"; 31 | sqlx::query(save_message) 32 | .bind(message_id) 33 | .bind(channel_id) 34 | .execute(&mut transaction) 35 | .await?; 36 | 37 | let user_id = bot_id; 38 | 39 | let save_user = "insert into users (user_id, name) values ($1, 'me') 40 | on conflict (name) do update set user_id = $1, name = 'me'"; 41 | sqlx::query(save_user) 42 | .bind(user_id) 43 | .execute(&mut transaction) 44 | .await?; 45 | 46 | transaction.commit().await?; 47 | 48 | let white_check_mark = ReactionType::from_str("✅")?; 49 | message.react(&args.cx, white_check_mark).await?; 50 | } 51 | Ok(()) 52 | } 53 | 54 | pub async fn assign_talk_role( 55 | cx: &Context, 56 | reaction: &Reaction, 57 | db: Arc, 58 | ) -> Result<(), Error> { 59 | let channel = reaction.channel(cx).await?; 60 | let channel_id = ChannelId::from(&channel); 61 | let message = reaction.message(cx).await?; 62 | 63 | let mut transaction = db.begin().await?; 64 | 65 | let msg: Option<(i32, String, String, String)> = 66 | sqlx::query_as("select * from messages where name = 'welcome' limit 1") 67 | .fetch_optional(&mut transaction) 68 | .await?; 69 | 70 | let talk_role: Option<(i32, String, String)> = 71 | sqlx::query_as("select * from roles where name = 'talk' limit 1") 72 | .fetch_optional(&mut transaction) 73 | .await?; 74 | 75 | let me: Option<(i32, String, String)> = 76 | sqlx::query_as("select * from users where name = 'me' limit 1") 77 | .fetch_optional(&mut transaction) 78 | .await?; 79 | 80 | transaction.commit().await?; 81 | 82 | if let Some((_, _, cached_message_id, cached_channel_id)) = msg { 83 | if message.id.0.to_string() == cached_message_id 84 | && channel_id.0.to_string() == *cached_channel_id 85 | { 86 | if reaction.emoji == ReactionType::from('✅') { 87 | if let Some((_, role_id, _)) = talk_role { 88 | if let Some(user_id) = reaction.user_id { 89 | let guild = channel 90 | .guild() 91 | .ok_or("Unable to retrieve guild from channel")?; 92 | 93 | let mut member = guild.guild_id.member(cx, user_id).await?; 94 | 95 | use std::str::FromStr; 96 | info!("Assigning talk role to {}", &member.user.id); 97 | member 98 | .add_role(&cx, RoleId::from(u64::from_str(&role_id)?)) 99 | .await?; 100 | 101 | // Requires ManageMessage permission 102 | if let Some((_, _, bot_id)) = me { 103 | if user_id.to_string() != bot_id { 104 | reaction.delete(cx).await?; 105 | } 106 | } 107 | } 108 | } 109 | } else { 110 | reaction.delete(cx).await?; 111 | } 112 | } 113 | } 114 | Ok(()) 115 | } 116 | 117 | pub async fn help(args: Arc) -> Result<(), Error> { 118 | let help_string = format!( 119 | " 120 | Post the welcome message to `channel` 121 | ``` 122 | {command} 123 | ``` 124 | **Example:** 125 | ``` 126 | ?CoC #welcome 127 | 128 | ``` 129 | will post the welcome message to the `channel` specified. 130 | ", 131 | command = "?CoC {channel}" 132 | ); 133 | 134 | api::send_reply(args.clone(), &help_string).await?; 135 | Ok(()) 136 | } 137 | --------------------------------------------------------------------------------