This is the Rust version of the discord bot created for FBT Heaven
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

init

BuyMyMojo ebdc182e

+8090
+26
.gitignore
··· 1 + # Generated by Cargo 2 + # will have compiled files and executables 3 + debug/ 4 + target/ 5 + builds/ 6 + 7 + # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 + # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 + # Cargo.lock 10 + 11 + # These are backup files generated by rustfmt 12 + **/*.rs.bk 13 + 14 + # MSVC Windows builds of rustc generate these, which store debugging information 15 + *.pdb 16 + 17 + # IDE stuff 18 + */.idea 19 + */.dccache 20 + 21 + # Added by cargo 22 + /target 23 + 24 + # My trace log files 25 + *_rusted-fbt.verbose.log 26 + *_rusted-fbt.info.log
+3218
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "addr2line" 7 + version = "0.21.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 + dependencies = [ 11 + "gimli", 12 + ] 13 + 14 + [[package]] 15 + name = "adler" 16 + version = "1.0.2" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 + 20 + [[package]] 21 + name = "ahash" 22 + version = "0.7.6" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 25 + dependencies = [ 26 + "getrandom", 27 + "once_cell", 28 + "version_check", 29 + ] 30 + 31 + [[package]] 32 + name = "aho-corasick" 33 + version = "1.1.1" 34 + source = "registry+https://github.com/rust-lang/crates.io-index" 35 + checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" 36 + dependencies = [ 37 + "memchr", 38 + ] 39 + 40 + [[package]] 41 + name = "android-tzdata" 42 + version = "0.1.1" 43 + source = "registry+https://github.com/rust-lang/crates.io-index" 44 + checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 45 + 46 + [[package]] 47 + name = "android_system_properties" 48 + version = "0.1.5" 49 + source = "registry+https://github.com/rust-lang/crates.io-index" 50 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 51 + dependencies = [ 52 + "libc", 53 + ] 54 + 55 + [[package]] 56 + name = "ansi_term" 57 + version = "0.12.1" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 60 + dependencies = [ 61 + "winapi", 62 + ] 63 + 64 + [[package]] 65 + name = "anstream" 66 + version = "0.5.0" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" 69 + dependencies = [ 70 + "anstyle", 71 + "anstyle-parse", 72 + "anstyle-query", 73 + "anstyle-wincon", 74 + "colorchoice", 75 + "utf8parse", 76 + ] 77 + 78 + [[package]] 79 + name = "anstyle" 80 + version = "1.0.3" 81 + source = "registry+https://github.com/rust-lang/crates.io-index" 82 + checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" 83 + 84 + [[package]] 85 + name = "anstyle-parse" 86 + version = "0.2.1" 87 + source = "registry+https://github.com/rust-lang/crates.io-index" 88 + checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 89 + dependencies = [ 90 + "utf8parse", 91 + ] 92 + 93 + [[package]] 94 + name = "anstyle-query" 95 + version = "1.0.0" 96 + source = "registry+https://github.com/rust-lang/crates.io-index" 97 + checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 98 + dependencies = [ 99 + "windows-sys 0.48.0", 100 + ] 101 + 102 + [[package]] 103 + name = "anstyle-wincon" 104 + version = "2.1.0" 105 + source = "registry+https://github.com/rust-lang/crates.io-index" 106 + checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" 107 + dependencies = [ 108 + "anstyle", 109 + "windows-sys 0.48.0", 110 + ] 111 + 112 + [[package]] 113 + name = "anyhow" 114 + version = "1.0.75" 115 + source = "registry+https://github.com/rust-lang/crates.io-index" 116 + checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 117 + 118 + [[package]] 119 + name = "arrayvec" 120 + version = "0.5.2" 121 + source = "registry+https://github.com/rust-lang/crates.io-index" 122 + checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 123 + 124 + [[package]] 125 + name = "async-channel" 126 + version = "1.7.1" 127 + source = "registry+https://github.com/rust-lang/crates.io-index" 128 + checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" 129 + dependencies = [ 130 + "concurrent-queue", 131 + "event-listener", 132 + "futures-core", 133 + ] 134 + 135 + [[package]] 136 + name = "async-trait" 137 + version = "0.1.58" 138 + source = "registry+https://github.com/rust-lang/crates.io-index" 139 + checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" 140 + dependencies = [ 141 + "proc-macro2", 142 + "quote", 143 + "syn 1.0.103", 144 + ] 145 + 146 + [[package]] 147 + name = "async-tungstenite" 148 + version = "0.17.2" 149 + source = "registry+https://github.com/rust-lang/crates.io-index" 150 + checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" 151 + dependencies = [ 152 + "futures-io", 153 + "futures-util", 154 + "log", 155 + "pin-project-lite", 156 + "tokio", 157 + "tokio-rustls 0.23.4", 158 + "tungstenite", 159 + "webpki-roots 0.22.5", 160 + ] 161 + 162 + [[package]] 163 + name = "atty" 164 + version = "0.2.14" 165 + source = "registry+https://github.com/rust-lang/crates.io-index" 166 + checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 167 + dependencies = [ 168 + "hermit-abi 0.1.19", 169 + "libc", 170 + "winapi", 171 + ] 172 + 173 + [[package]] 174 + name = "autocfg" 175 + version = "1.1.0" 176 + source = "registry+https://github.com/rust-lang/crates.io-index" 177 + checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 178 + 179 + [[package]] 180 + name = "backtrace" 181 + version = "0.3.69" 182 + source = "registry+https://github.com/rust-lang/crates.io-index" 183 + checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 184 + dependencies = [ 185 + "addr2line", 186 + "cc", 187 + "cfg-if", 188 + "libc", 189 + "miniz_oxide 0.7.1", 190 + "object", 191 + "rustc-demangle", 192 + ] 193 + 194 + [[package]] 195 + name = "base64" 196 + version = "0.13.1" 197 + source = "registry+https://github.com/rust-lang/crates.io-index" 198 + checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 199 + 200 + [[package]] 201 + name = "base64" 202 + version = "0.21.2" 203 + source = "registry+https://github.com/rust-lang/crates.io-index" 204 + checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" 205 + 206 + [[package]] 207 + name = "bitflags" 208 + version = "1.3.2" 209 + source = "registry+https://github.com/rust-lang/crates.io-index" 210 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 211 + 212 + [[package]] 213 + name = "bitflags" 214 + version = "2.4.0" 215 + source = "registry+https://github.com/rust-lang/crates.io-index" 216 + checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 217 + 218 + [[package]] 219 + name = "block-buffer" 220 + version = "0.10.3" 221 + source = "registry+https://github.com/rust-lang/crates.io-index" 222 + checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 223 + dependencies = [ 224 + "generic-array", 225 + ] 226 + 227 + [[package]] 228 + name = "bumpalo" 229 + version = "3.11.1" 230 + source = "registry+https://github.com/rust-lang/crates.io-index" 231 + checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 232 + 233 + [[package]] 234 + name = "bytecheck" 235 + version = "0.6.9" 236 + source = "registry+https://github.com/rust-lang/crates.io-index" 237 + checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" 238 + dependencies = [ 239 + "bytecheck_derive", 240 + "ptr_meta", 241 + ] 242 + 243 + [[package]] 244 + name = "bytecheck_derive" 245 + version = "0.6.9" 246 + source = "registry+https://github.com/rust-lang/crates.io-index" 247 + checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" 248 + dependencies = [ 249 + "proc-macro2", 250 + "quote", 251 + "syn 1.0.103", 252 + ] 253 + 254 + [[package]] 255 + name = "byteorder" 256 + version = "1.4.3" 257 + source = "registry+https://github.com/rust-lang/crates.io-index" 258 + checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 259 + 260 + [[package]] 261 + name = "bytes" 262 + version = "1.3.0" 263 + source = "registry+https://github.com/rust-lang/crates.io-index" 264 + checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 265 + 266 + [[package]] 267 + name = "cache-padded" 268 + version = "1.2.0" 269 + source = "registry+https://github.com/rust-lang/crates.io-index" 270 + checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" 271 + 272 + [[package]] 273 + name = "castaway" 274 + version = "0.1.2" 275 + source = "registry+https://github.com/rust-lang/crates.io-index" 276 + checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" 277 + 278 + [[package]] 279 + name = "cc" 280 + version = "1.0.77" 281 + source = "registry+https://github.com/rust-lang/crates.io-index" 282 + checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" 283 + 284 + [[package]] 285 + name = "cfg-if" 286 + version = "1.0.0" 287 + source = "registry+https://github.com/rust-lang/crates.io-index" 288 + checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 289 + 290 + [[package]] 291 + name = "chrono" 292 + version = "0.4.31" 293 + source = "registry+https://github.com/rust-lang/crates.io-index" 294 + checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 295 + dependencies = [ 296 + "android-tzdata", 297 + "iana-time-zone", 298 + "js-sys", 299 + "num-traits", 300 + "serde", 301 + "wasm-bindgen", 302 + "windows-targets", 303 + ] 304 + 305 + [[package]] 306 + name = "chrono-tz" 307 + version = "0.8.3" 308 + source = "registry+https://github.com/rust-lang/crates.io-index" 309 + checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" 310 + dependencies = [ 311 + "chrono", 312 + "chrono-tz-build", 313 + "phf", 314 + ] 315 + 316 + [[package]] 317 + name = "chrono-tz-build" 318 + version = "0.2.0" 319 + source = "registry+https://github.com/rust-lang/crates.io-index" 320 + checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" 321 + dependencies = [ 322 + "parse-zoneinfo", 323 + "phf", 324 + "phf_codegen", 325 + ] 326 + 327 + [[package]] 328 + name = "clap" 329 + version = "2.34.0" 330 + source = "registry+https://github.com/rust-lang/crates.io-index" 331 + checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 332 + dependencies = [ 333 + "ansi_term", 334 + "atty", 335 + "bitflags 1.3.2", 336 + "strsim 0.8.0", 337 + "textwrap", 338 + "unicode-width", 339 + "vec_map", 340 + ] 341 + 342 + [[package]] 343 + name = "clap" 344 + version = "4.4.4" 345 + source = "registry+https://github.com/rust-lang/crates.io-index" 346 + checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" 347 + dependencies = [ 348 + "clap_builder", 349 + "clap_derive", 350 + ] 351 + 352 + [[package]] 353 + name = "clap_builder" 354 + version = "4.4.4" 355 + source = "registry+https://github.com/rust-lang/crates.io-index" 356 + checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" 357 + dependencies = [ 358 + "anstream", 359 + "anstyle", 360 + "clap_lex", 361 + "strsim 0.10.0", 362 + ] 363 + 364 + [[package]] 365 + name = "clap_derive" 366 + version = "4.4.2" 367 + source = "registry+https://github.com/rust-lang/crates.io-index" 368 + checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" 369 + dependencies = [ 370 + "heck", 371 + "proc-macro2", 372 + "quote", 373 + "syn 2.0.37", 374 + ] 375 + 376 + [[package]] 377 + name = "clap_lex" 378 + version = "0.5.1" 379 + source = "registry+https://github.com/rust-lang/crates.io-index" 380 + checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" 381 + 382 + [[package]] 383 + name = "codespan-reporting" 384 + version = "0.11.1" 385 + source = "registry+https://github.com/rust-lang/crates.io-index" 386 + checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 387 + dependencies = [ 388 + "termcolor", 389 + "unicode-width", 390 + ] 391 + 392 + [[package]] 393 + name = "colorchoice" 394 + version = "1.0.0" 395 + source = "registry+https://github.com/rust-lang/crates.io-index" 396 + checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 397 + 398 + [[package]] 399 + name = "colored" 400 + version = "2.0.4" 401 + source = "registry+https://github.com/rust-lang/crates.io-index" 402 + checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" 403 + dependencies = [ 404 + "is-terminal", 405 + "lazy_static", 406 + "windows-sys 0.48.0", 407 + ] 408 + 409 + [[package]] 410 + name = "combine" 411 + version = "4.6.6" 412 + source = "registry+https://github.com/rust-lang/crates.io-index" 413 + checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 414 + dependencies = [ 415 + "bytes", 416 + "futures-core", 417 + "memchr", 418 + "pin-project-lite", 419 + "tokio", 420 + "tokio-util", 421 + ] 422 + 423 + [[package]] 424 + name = "concurrent-queue" 425 + version = "1.2.4" 426 + source = "registry+https://github.com/rust-lang/crates.io-index" 427 + checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" 428 + dependencies = [ 429 + "cache-padded", 430 + ] 431 + 432 + [[package]] 433 + name = "core-foundation" 434 + version = "0.9.3" 435 + source = "registry+https://github.com/rust-lang/crates.io-index" 436 + checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 437 + dependencies = [ 438 + "core-foundation-sys", 439 + "libc", 440 + ] 441 + 442 + [[package]] 443 + name = "core-foundation-sys" 444 + version = "0.8.3" 445 + source = "registry+https://github.com/rust-lang/crates.io-index" 446 + checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 447 + 448 + [[package]] 449 + name = "cpufeatures" 450 + version = "0.2.5" 451 + source = "registry+https://github.com/rust-lang/crates.io-index" 452 + checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 453 + dependencies = [ 454 + "libc", 455 + ] 456 + 457 + [[package]] 458 + name = "crc32fast" 459 + version = "1.3.2" 460 + source = "registry+https://github.com/rust-lang/crates.io-index" 461 + checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 462 + dependencies = [ 463 + "cfg-if", 464 + ] 465 + 466 + [[package]] 467 + name = "crossbeam-utils" 468 + version = "0.8.14" 469 + source = "registry+https://github.com/rust-lang/crates.io-index" 470 + checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 471 + dependencies = [ 472 + "cfg-if", 473 + ] 474 + 475 + [[package]] 476 + name = "crypto-common" 477 + version = "0.1.6" 478 + source = "registry+https://github.com/rust-lang/crates.io-index" 479 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 480 + dependencies = [ 481 + "generic-array", 482 + "typenum", 483 + ] 484 + 485 + [[package]] 486 + name = "csv" 487 + version = "1.2.2" 488 + source = "registry+https://github.com/rust-lang/crates.io-index" 489 + checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" 490 + dependencies = [ 491 + "csv-core", 492 + "itoa", 493 + "ryu", 494 + "serde", 495 + ] 496 + 497 + [[package]] 498 + name = "csv-core" 499 + version = "0.1.10" 500 + source = "registry+https://github.com/rust-lang/crates.io-index" 501 + checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 502 + dependencies = [ 503 + "memchr", 504 + ] 505 + 506 + [[package]] 507 + name = "curl" 508 + version = "0.4.44" 509 + source = "registry+https://github.com/rust-lang/crates.io-index" 510 + checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" 511 + dependencies = [ 512 + "curl-sys", 513 + "libc", 514 + "openssl-probe", 515 + "openssl-sys", 516 + "schannel", 517 + "socket2 0.4.7", 518 + "winapi", 519 + ] 520 + 521 + [[package]] 522 + name = "curl-sys" 523 + version = "0.4.59+curl-7.86.0" 524 + source = "registry+https://github.com/rust-lang/crates.io-index" 525 + checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" 526 + dependencies = [ 527 + "cc", 528 + "libc", 529 + "libnghttp2-sys", 530 + "libz-sys", 531 + "openssl-sys", 532 + "pkg-config", 533 + "vcpkg", 534 + "winapi", 535 + ] 536 + 537 + [[package]] 538 + name = "cxx" 539 + version = "1.0.82" 540 + source = "registry+https://github.com/rust-lang/crates.io-index" 541 + checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" 542 + dependencies = [ 543 + "cc", 544 + "cxxbridge-flags", 545 + "cxxbridge-macro", 546 + "link-cplusplus", 547 + ] 548 + 549 + [[package]] 550 + name = "cxx-build" 551 + version = "1.0.82" 552 + source = "registry+https://github.com/rust-lang/crates.io-index" 553 + checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" 554 + dependencies = [ 555 + "cc", 556 + "codespan-reporting", 557 + "once_cell", 558 + "proc-macro2", 559 + "quote", 560 + "scratch", 561 + "syn 1.0.103", 562 + ] 563 + 564 + [[package]] 565 + name = "cxxbridge-flags" 566 + version = "1.0.82" 567 + source = "registry+https://github.com/rust-lang/crates.io-index" 568 + checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" 569 + 570 + [[package]] 571 + name = "cxxbridge-macro" 572 + version = "1.0.82" 573 + source = "registry+https://github.com/rust-lang/crates.io-index" 574 + checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" 575 + dependencies = [ 576 + "proc-macro2", 577 + "quote", 578 + "syn 1.0.103", 579 + ] 580 + 581 + [[package]] 582 + name = "darling" 583 + version = "0.14.2" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" 586 + dependencies = [ 587 + "darling_core 0.14.2", 588 + "darling_macro 0.14.2", 589 + ] 590 + 591 + [[package]] 592 + name = "darling" 593 + version = "0.20.1" 594 + source = "registry+https://github.com/rust-lang/crates.io-index" 595 + checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" 596 + dependencies = [ 597 + "darling_core 0.20.1", 598 + "darling_macro 0.20.1", 599 + ] 600 + 601 + [[package]] 602 + name = "darling_core" 603 + version = "0.14.2" 604 + source = "registry+https://github.com/rust-lang/crates.io-index" 605 + checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" 606 + dependencies = [ 607 + "fnv", 608 + "ident_case", 609 + "proc-macro2", 610 + "quote", 611 + "strsim 0.10.0", 612 + "syn 1.0.103", 613 + ] 614 + 615 + [[package]] 616 + name = "darling_core" 617 + version = "0.20.1" 618 + source = "registry+https://github.com/rust-lang/crates.io-index" 619 + checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" 620 + dependencies = [ 621 + "fnv", 622 + "ident_case", 623 + "proc-macro2", 624 + "quote", 625 + "strsim 0.10.0", 626 + "syn 2.0.37", 627 + ] 628 + 629 + [[package]] 630 + name = "darling_macro" 631 + version = "0.14.2" 632 + source = "registry+https://github.com/rust-lang/crates.io-index" 633 + checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" 634 + dependencies = [ 635 + "darling_core 0.14.2", 636 + "quote", 637 + "syn 1.0.103", 638 + ] 639 + 640 + [[package]] 641 + name = "darling_macro" 642 + version = "0.20.1" 643 + source = "registry+https://github.com/rust-lang/crates.io-index" 644 + checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" 645 + dependencies = [ 646 + "darling_core 0.20.1", 647 + "quote", 648 + "syn 2.0.37", 649 + ] 650 + 651 + [[package]] 652 + name = "dashmap" 653 + version = "5.4.0" 654 + source = "registry+https://github.com/rust-lang/crates.io-index" 655 + checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" 656 + dependencies = [ 657 + "cfg-if", 658 + "hashbrown 0.12.3", 659 + "lock_api", 660 + "once_cell", 661 + "parking_lot_core 0.9.4", 662 + "serde", 663 + ] 664 + 665 + [[package]] 666 + name = "derivative" 667 + version = "2.2.0" 668 + source = "registry+https://github.com/rust-lang/crates.io-index" 669 + checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 670 + dependencies = [ 671 + "proc-macro2", 672 + "quote", 673 + "syn 1.0.103", 674 + ] 675 + 676 + [[package]] 677 + name = "digest" 678 + version = "0.10.6" 679 + source = "registry+https://github.com/rust-lang/crates.io-index" 680 + checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 681 + dependencies = [ 682 + "block-buffer", 683 + "crypto-common", 684 + ] 685 + 686 + [[package]] 687 + name = "encoding_rs" 688 + version = "0.8.31" 689 + source = "registry+https://github.com/rust-lang/crates.io-index" 690 + checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 691 + dependencies = [ 692 + "cfg-if", 693 + ] 694 + 695 + [[package]] 696 + name = "equivalent" 697 + version = "1.0.1" 698 + source = "registry+https://github.com/rust-lang/crates.io-index" 699 + checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 700 + 701 + [[package]] 702 + name = "errno" 703 + version = "0.3.3" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" 706 + dependencies = [ 707 + "errno-dragonfly", 708 + "libc", 709 + "windows-sys 0.48.0", 710 + ] 711 + 712 + [[package]] 713 + name = "errno-dragonfly" 714 + version = "0.1.2" 715 + source = "registry+https://github.com/rust-lang/crates.io-index" 716 + checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 717 + dependencies = [ 718 + "cc", 719 + "libc", 720 + ] 721 + 722 + [[package]] 723 + name = "event-listener" 724 + version = "2.5.3" 725 + source = "registry+https://github.com/rust-lang/crates.io-index" 726 + checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 727 + 728 + [[package]] 729 + name = "fastrand" 730 + version = "1.8.0" 731 + source = "registry+https://github.com/rust-lang/crates.io-index" 732 + checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 733 + dependencies = [ 734 + "instant", 735 + ] 736 + 737 + [[package]] 738 + name = "flate2" 739 + version = "1.0.24" 740 + source = "registry+https://github.com/rust-lang/crates.io-index" 741 + checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" 742 + dependencies = [ 743 + "crc32fast", 744 + "miniz_oxide 0.5.4", 745 + ] 746 + 747 + [[package]] 748 + name = "fnv" 749 + version = "1.0.7" 750 + source = "registry+https://github.com/rust-lang/crates.io-index" 751 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 752 + 753 + [[package]] 754 + name = "foreign-types" 755 + version = "0.3.2" 756 + source = "registry+https://github.com/rust-lang/crates.io-index" 757 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 758 + dependencies = [ 759 + "foreign-types-shared", 760 + ] 761 + 762 + [[package]] 763 + name = "foreign-types-shared" 764 + version = "0.1.1" 765 + source = "registry+https://github.com/rust-lang/crates.io-index" 766 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 767 + 768 + [[package]] 769 + name = "form_urlencoded" 770 + version = "1.1.0" 771 + source = "registry+https://github.com/rust-lang/crates.io-index" 772 + checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 773 + dependencies = [ 774 + "percent-encoding", 775 + ] 776 + 777 + [[package]] 778 + name = "futures" 779 + version = "0.3.28" 780 + source = "registry+https://github.com/rust-lang/crates.io-index" 781 + checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 782 + dependencies = [ 783 + "futures-channel", 784 + "futures-core", 785 + "futures-executor", 786 + "futures-io", 787 + "futures-sink", 788 + "futures-task", 789 + "futures-util", 790 + ] 791 + 792 + [[package]] 793 + name = "futures-channel" 794 + version = "0.3.28" 795 + source = "registry+https://github.com/rust-lang/crates.io-index" 796 + checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 797 + dependencies = [ 798 + "futures-core", 799 + "futures-sink", 800 + ] 801 + 802 + [[package]] 803 + name = "futures-core" 804 + version = "0.3.28" 805 + source = "registry+https://github.com/rust-lang/crates.io-index" 806 + checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 807 + 808 + [[package]] 809 + name = "futures-executor" 810 + version = "0.3.28" 811 + source = "registry+https://github.com/rust-lang/crates.io-index" 812 + checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 813 + dependencies = [ 814 + "futures-core", 815 + "futures-task", 816 + "futures-util", 817 + ] 818 + 819 + [[package]] 820 + name = "futures-io" 821 + version = "0.3.28" 822 + source = "registry+https://github.com/rust-lang/crates.io-index" 823 + checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 824 + 825 + [[package]] 826 + name = "futures-lite" 827 + version = "1.12.0" 828 + source = "registry+https://github.com/rust-lang/crates.io-index" 829 + checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" 830 + dependencies = [ 831 + "fastrand", 832 + "futures-core", 833 + "futures-io", 834 + "memchr", 835 + "parking", 836 + "pin-project-lite", 837 + "waker-fn", 838 + ] 839 + 840 + [[package]] 841 + name = "futures-macro" 842 + version = "0.3.28" 843 + source = "registry+https://github.com/rust-lang/crates.io-index" 844 + checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 845 + dependencies = [ 846 + "proc-macro2", 847 + "quote", 848 + "syn 2.0.37", 849 + ] 850 + 851 + [[package]] 852 + name = "futures-sink" 853 + version = "0.3.28" 854 + source = "registry+https://github.com/rust-lang/crates.io-index" 855 + checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 856 + 857 + [[package]] 858 + name = "futures-task" 859 + version = "0.3.28" 860 + source = "registry+https://github.com/rust-lang/crates.io-index" 861 + checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 862 + 863 + [[package]] 864 + name = "futures-util" 865 + version = "0.3.28" 866 + source = "registry+https://github.com/rust-lang/crates.io-index" 867 + checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 868 + dependencies = [ 869 + "futures-channel", 870 + "futures-core", 871 + "futures-io", 872 + "futures-macro", 873 + "futures-sink", 874 + "futures-task", 875 + "memchr", 876 + "pin-project-lite", 877 + "pin-utils", 878 + "slab", 879 + ] 880 + 881 + [[package]] 882 + name = "generic-array" 883 + version = "0.14.6" 884 + source = "registry+https://github.com/rust-lang/crates.io-index" 885 + checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 886 + dependencies = [ 887 + "typenum", 888 + "version_check", 889 + ] 890 + 891 + [[package]] 892 + name = "getopts" 893 + version = "0.2.21" 894 + source = "registry+https://github.com/rust-lang/crates.io-index" 895 + checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 896 + dependencies = [ 897 + "unicode-width", 898 + ] 899 + 900 + [[package]] 901 + name = "getrandom" 902 + version = "0.2.8" 903 + source = "registry+https://github.com/rust-lang/crates.io-index" 904 + checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 905 + dependencies = [ 906 + "cfg-if", 907 + "libc", 908 + "wasi", 909 + ] 910 + 911 + [[package]] 912 + name = "gimli" 913 + version = "0.28.0" 914 + source = "registry+https://github.com/rust-lang/crates.io-index" 915 + checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 916 + 917 + [[package]] 918 + name = "h2" 919 + version = "0.3.15" 920 + source = "registry+https://github.com/rust-lang/crates.io-index" 921 + checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" 922 + dependencies = [ 923 + "bytes", 924 + "fnv", 925 + "futures-core", 926 + "futures-sink", 927 + "futures-util", 928 + "http", 929 + "indexmap 1.9.2", 930 + "slab", 931 + "tokio", 932 + "tokio-util", 933 + "tracing", 934 + ] 935 + 936 + [[package]] 937 + name = "hashbrown" 938 + version = "0.12.3" 939 + source = "registry+https://github.com/rust-lang/crates.io-index" 940 + checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 941 + dependencies = [ 942 + "ahash", 943 + ] 944 + 945 + [[package]] 946 + name = "hashbrown" 947 + version = "0.14.1" 948 + source = "registry+https://github.com/rust-lang/crates.io-index" 949 + checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" 950 + 951 + [[package]] 952 + name = "heck" 953 + version = "0.4.0" 954 + source = "registry+https://github.com/rust-lang/crates.io-index" 955 + checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 956 + 957 + [[package]] 958 + name = "hermit-abi" 959 + version = "0.1.19" 960 + source = "registry+https://github.com/rust-lang/crates.io-index" 961 + checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 962 + dependencies = [ 963 + "libc", 964 + ] 965 + 966 + [[package]] 967 + name = "hermit-abi" 968 + version = "0.3.3" 969 + source = "registry+https://github.com/rust-lang/crates.io-index" 970 + checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 971 + 972 + [[package]] 973 + name = "hex" 974 + version = "0.4.3" 975 + source = "registry+https://github.com/rust-lang/crates.io-index" 976 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 977 + 978 + [[package]] 979 + name = "http" 980 + version = "0.2.8" 981 + source = "registry+https://github.com/rust-lang/crates.io-index" 982 + checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 983 + dependencies = [ 984 + "bytes", 985 + "fnv", 986 + "itoa", 987 + ] 988 + 989 + [[package]] 990 + name = "http-body" 991 + version = "0.4.5" 992 + source = "registry+https://github.com/rust-lang/crates.io-index" 993 + checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 994 + dependencies = [ 995 + "bytes", 996 + "http", 997 + "pin-project-lite", 998 + ] 999 + 1000 + [[package]] 1001 + name = "httparse" 1002 + version = "1.8.0" 1003 + source = "registry+https://github.com/rust-lang/crates.io-index" 1004 + checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 1005 + 1006 + [[package]] 1007 + name = "httpdate" 1008 + version = "1.0.2" 1009 + source = "registry+https://github.com/rust-lang/crates.io-index" 1010 + checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 1011 + 1012 + [[package]] 1013 + name = "hyper" 1014 + version = "0.14.23" 1015 + source = "registry+https://github.com/rust-lang/crates.io-index" 1016 + checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" 1017 + dependencies = [ 1018 + "bytes", 1019 + "futures-channel", 1020 + "futures-core", 1021 + "futures-util", 1022 + "h2", 1023 + "http", 1024 + "http-body", 1025 + "httparse", 1026 + "httpdate", 1027 + "itoa", 1028 + "pin-project-lite", 1029 + "socket2 0.4.7", 1030 + "tokio", 1031 + "tower-service", 1032 + "tracing", 1033 + "want", 1034 + ] 1035 + 1036 + [[package]] 1037 + name = "hyper-rustls" 1038 + version = "0.24.1" 1039 + source = "registry+https://github.com/rust-lang/crates.io-index" 1040 + checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" 1041 + dependencies = [ 1042 + "futures-util", 1043 + "http", 1044 + "hyper", 1045 + "rustls 0.21.7", 1046 + "tokio", 1047 + "tokio-rustls 0.24.1", 1048 + ] 1049 + 1050 + [[package]] 1051 + name = "hyper-tls" 1052 + version = "0.5.0" 1053 + source = "registry+https://github.com/rust-lang/crates.io-index" 1054 + checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 1055 + dependencies = [ 1056 + "bytes", 1057 + "hyper", 1058 + "native-tls", 1059 + "tokio", 1060 + "tokio-native-tls", 1061 + ] 1062 + 1063 + [[package]] 1064 + name = "iana-time-zone" 1065 + version = "0.1.53" 1066 + source = "registry+https://github.com/rust-lang/crates.io-index" 1067 + checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 1068 + dependencies = [ 1069 + "android_system_properties", 1070 + "core-foundation-sys", 1071 + "iana-time-zone-haiku", 1072 + "js-sys", 1073 + "wasm-bindgen", 1074 + "winapi", 1075 + ] 1076 + 1077 + [[package]] 1078 + name = "iana-time-zone-haiku" 1079 + version = "0.1.1" 1080 + source = "registry+https://github.com/rust-lang/crates.io-index" 1081 + checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 1082 + dependencies = [ 1083 + "cxx", 1084 + "cxx-build", 1085 + ] 1086 + 1087 + [[package]] 1088 + name = "ident_case" 1089 + version = "1.0.1" 1090 + source = "registry+https://github.com/rust-lang/crates.io-index" 1091 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1092 + 1093 + [[package]] 1094 + name = "idna" 1095 + version = "0.3.0" 1096 + source = "registry+https://github.com/rust-lang/crates.io-index" 1097 + checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 1098 + dependencies = [ 1099 + "unicode-bidi", 1100 + "unicode-normalization", 1101 + ] 1102 + 1103 + [[package]] 1104 + name = "indexmap" 1105 + version = "1.9.2" 1106 + source = "registry+https://github.com/rust-lang/crates.io-index" 1107 + checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 1108 + dependencies = [ 1109 + "autocfg", 1110 + "hashbrown 0.12.3", 1111 + "serde", 1112 + ] 1113 + 1114 + [[package]] 1115 + name = "indexmap" 1116 + version = "2.0.1" 1117 + source = "registry+https://github.com/rust-lang/crates.io-index" 1118 + checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" 1119 + dependencies = [ 1120 + "equivalent", 1121 + "hashbrown 0.14.1", 1122 + "serde", 1123 + ] 1124 + 1125 + [[package]] 1126 + name = "instant" 1127 + version = "0.1.12" 1128 + source = "registry+https://github.com/rust-lang/crates.io-index" 1129 + checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 1130 + dependencies = [ 1131 + "cfg-if", 1132 + ] 1133 + 1134 + [[package]] 1135 + name = "ipnet" 1136 + version = "2.5.1" 1137 + source = "registry+https://github.com/rust-lang/crates.io-index" 1138 + checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" 1139 + 1140 + [[package]] 1141 + name = "is-terminal" 1142 + version = "0.4.9" 1143 + source = "registry+https://github.com/rust-lang/crates.io-index" 1144 + checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 1145 + dependencies = [ 1146 + "hermit-abi 0.3.3", 1147 + "rustix", 1148 + "windows-sys 0.48.0", 1149 + ] 1150 + 1151 + [[package]] 1152 + name = "isahc" 1153 + version = "1.7.2" 1154 + source = "registry+https://github.com/rust-lang/crates.io-index" 1155 + checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" 1156 + dependencies = [ 1157 + "async-channel", 1158 + "castaway", 1159 + "crossbeam-utils", 1160 + "curl", 1161 + "curl-sys", 1162 + "encoding_rs", 1163 + "event-listener", 1164 + "futures-lite", 1165 + "http", 1166 + "log", 1167 + "mime", 1168 + "once_cell", 1169 + "polling", 1170 + "slab", 1171 + "sluice", 1172 + "tracing", 1173 + "tracing-futures", 1174 + "url", 1175 + "waker-fn", 1176 + ] 1177 + 1178 + [[package]] 1179 + name = "iso8601-duration" 1180 + version = "0.1.0" 1181 + source = "registry+https://github.com/rust-lang/crates.io-index" 1182 + checksum = "60b51dd97fa24074214b9eb14da518957573f4dec3189112610ae1ccec9ac464" 1183 + dependencies = [ 1184 + "nom", 1185 + ] 1186 + 1187 + [[package]] 1188 + name = "itoa" 1189 + version = "1.0.4" 1190 + source = "registry+https://github.com/rust-lang/crates.io-index" 1191 + checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 1192 + 1193 + [[package]] 1194 + name = "js-sys" 1195 + version = "0.3.60" 1196 + source = "registry+https://github.com/rust-lang/crates.io-index" 1197 + checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 1198 + dependencies = [ 1199 + "wasm-bindgen", 1200 + ] 1201 + 1202 + [[package]] 1203 + name = "jsonwebtoken" 1204 + version = "8.1.1" 1205 + source = "registry+https://github.com/rust-lang/crates.io-index" 1206 + checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c" 1207 + dependencies = [ 1208 + "base64 0.13.1", 1209 + "ring", 1210 + "serde", 1211 + "serde_json", 1212 + ] 1213 + 1214 + [[package]] 1215 + name = "lazy_static" 1216 + version = "1.4.0" 1217 + source = "registry+https://github.com/rust-lang/crates.io-index" 1218 + checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1219 + 1220 + [[package]] 1221 + name = "lexical-core" 1222 + version = "0.7.6" 1223 + source = "registry+https://github.com/rust-lang/crates.io-index" 1224 + checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 1225 + dependencies = [ 1226 + "arrayvec", 1227 + "bitflags 1.3.2", 1228 + "cfg-if", 1229 + "ryu", 1230 + "static_assertions", 1231 + ] 1232 + 1233 + [[package]] 1234 + name = "libc" 1235 + version = "0.2.148" 1236 + source = "registry+https://github.com/rust-lang/crates.io-index" 1237 + checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" 1238 + 1239 + [[package]] 1240 + name = "libnghttp2-sys" 1241 + version = "0.1.7+1.45.0" 1242 + source = "registry+https://github.com/rust-lang/crates.io-index" 1243 + checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" 1244 + dependencies = [ 1245 + "cc", 1246 + "libc", 1247 + ] 1248 + 1249 + [[package]] 1250 + name = "libz-sys" 1251 + version = "1.1.8" 1252 + source = "registry+https://github.com/rust-lang/crates.io-index" 1253 + checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" 1254 + dependencies = [ 1255 + "cc", 1256 + "libc", 1257 + "pkg-config", 1258 + "vcpkg", 1259 + ] 1260 + 1261 + [[package]] 1262 + name = "link-cplusplus" 1263 + version = "1.0.7" 1264 + source = "registry+https://github.com/rust-lang/crates.io-index" 1265 + checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" 1266 + dependencies = [ 1267 + "cc", 1268 + ] 1269 + 1270 + [[package]] 1271 + name = "linkify" 1272 + version = "0.10.0" 1273 + source = "registry+https://github.com/rust-lang/crates.io-index" 1274 + checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" 1275 + dependencies = [ 1276 + "memchr", 1277 + ] 1278 + 1279 + [[package]] 1280 + name = "linux-raw-sys" 1281 + version = "0.4.7" 1282 + source = "registry+https://github.com/rust-lang/crates.io-index" 1283 + checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" 1284 + 1285 + [[package]] 1286 + name = "lock_api" 1287 + version = "0.4.9" 1288 + source = "registry+https://github.com/rust-lang/crates.io-index" 1289 + checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 1290 + dependencies = [ 1291 + "autocfg", 1292 + "scopeguard", 1293 + ] 1294 + 1295 + [[package]] 1296 + name = "log" 1297 + version = "0.4.17" 1298 + source = "registry+https://github.com/rust-lang/crates.io-index" 1299 + checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 1300 + dependencies = [ 1301 + "cfg-if", 1302 + ] 1303 + 1304 + [[package]] 1305 + name = "maplit" 1306 + version = "1.0.2" 1307 + source = "registry+https://github.com/rust-lang/crates.io-index" 1308 + checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 1309 + 1310 + [[package]] 1311 + name = "meilisearch-sdk" 1312 + version = "0.17.0" 1313 + source = "registry+https://github.com/rust-lang/crates.io-index" 1314 + checksum = "e980fc979f653877693451f5b31d065a6f607c45988ac117a8316c90f73c7cdc" 1315 + dependencies = [ 1316 + "async-trait", 1317 + "futures", 1318 + "isahc", 1319 + "iso8601-duration", 1320 + "js-sys", 1321 + "jsonwebtoken", 1322 + "log", 1323 + "serde", 1324 + "serde_json", 1325 + "time", 1326 + "wasm-bindgen", 1327 + "wasm-bindgen-futures", 1328 + "web-sys", 1329 + ] 1330 + 1331 + [[package]] 1332 + name = "memchr" 1333 + version = "2.6.3" 1334 + source = "registry+https://github.com/rust-lang/crates.io-index" 1335 + checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 1336 + 1337 + [[package]] 1338 + name = "merge" 1339 + version = "0.1.0" 1340 + source = "registry+https://github.com/rust-lang/crates.io-index" 1341 + checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9" 1342 + dependencies = [ 1343 + "merge_derive", 1344 + "num-traits", 1345 + ] 1346 + 1347 + [[package]] 1348 + name = "merge_derive" 1349 + version = "0.1.0" 1350 + source = "registry+https://github.com/rust-lang/crates.io-index" 1351 + checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07" 1352 + dependencies = [ 1353 + "proc-macro-error", 1354 + "proc-macro2", 1355 + "quote", 1356 + "syn 1.0.103", 1357 + ] 1358 + 1359 + [[package]] 1360 + name = "mime" 1361 + version = "0.3.16" 1362 + source = "registry+https://github.com/rust-lang/crates.io-index" 1363 + checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1364 + 1365 + [[package]] 1366 + name = "mime_guess" 1367 + version = "2.0.4" 1368 + source = "registry+https://github.com/rust-lang/crates.io-index" 1369 + checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 1370 + dependencies = [ 1371 + "mime", 1372 + "unicase", 1373 + ] 1374 + 1375 + [[package]] 1376 + name = "miniz_oxide" 1377 + version = "0.5.4" 1378 + source = "registry+https://github.com/rust-lang/crates.io-index" 1379 + checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" 1380 + dependencies = [ 1381 + "adler", 1382 + ] 1383 + 1384 + [[package]] 1385 + name = "miniz_oxide" 1386 + version = "0.7.1" 1387 + source = "registry+https://github.com/rust-lang/crates.io-index" 1388 + checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 1389 + dependencies = [ 1390 + "adler", 1391 + ] 1392 + 1393 + [[package]] 1394 + name = "mio" 1395 + version = "0.8.8" 1396 + source = "registry+https://github.com/rust-lang/crates.io-index" 1397 + checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 1398 + dependencies = [ 1399 + "libc", 1400 + "wasi", 1401 + "windows-sys 0.48.0", 1402 + ] 1403 + 1404 + [[package]] 1405 + name = "native-tls" 1406 + version = "0.2.11" 1407 + source = "registry+https://github.com/rust-lang/crates.io-index" 1408 + checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 1409 + dependencies = [ 1410 + "lazy_static", 1411 + "libc", 1412 + "log", 1413 + "openssl", 1414 + "openssl-probe", 1415 + "openssl-sys", 1416 + "schannel", 1417 + "security-framework", 1418 + "security-framework-sys", 1419 + "tempfile", 1420 + ] 1421 + 1422 + [[package]] 1423 + name = "nom" 1424 + version = "5.1.3" 1425 + source = "registry+https://github.com/rust-lang/crates.io-index" 1426 + checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" 1427 + dependencies = [ 1428 + "lexical-core", 1429 + "memchr", 1430 + "version_check", 1431 + ] 1432 + 1433 + [[package]] 1434 + name = "nu-ansi-term" 1435 + version = "0.46.0" 1436 + source = "registry+https://github.com/rust-lang/crates.io-index" 1437 + checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1438 + dependencies = [ 1439 + "overload", 1440 + "winapi", 1441 + ] 1442 + 1443 + [[package]] 1444 + name = "num-traits" 1445 + version = "0.2.15" 1446 + source = "registry+https://github.com/rust-lang/crates.io-index" 1447 + checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 1448 + dependencies = [ 1449 + "autocfg", 1450 + ] 1451 + 1452 + [[package]] 1453 + name = "num_cpus" 1454 + version = "1.14.0" 1455 + source = "registry+https://github.com/rust-lang/crates.io-index" 1456 + checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 1457 + dependencies = [ 1458 + "hermit-abi 0.1.19", 1459 + "libc", 1460 + ] 1461 + 1462 + [[package]] 1463 + name = "object" 1464 + version = "0.32.1" 1465 + source = "registry+https://github.com/rust-lang/crates.io-index" 1466 + checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 1467 + dependencies = [ 1468 + "memchr", 1469 + ] 1470 + 1471 + [[package]] 1472 + name = "once_cell" 1473 + version = "1.18.0" 1474 + source = "registry+https://github.com/rust-lang/crates.io-index" 1475 + checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 1476 + 1477 + [[package]] 1478 + name = "openssl" 1479 + version = "0.10.42" 1480 + source = "registry+https://github.com/rust-lang/crates.io-index" 1481 + checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" 1482 + dependencies = [ 1483 + "bitflags 1.3.2", 1484 + "cfg-if", 1485 + "foreign-types", 1486 + "libc", 1487 + "once_cell", 1488 + "openssl-macros", 1489 + "openssl-sys", 1490 + ] 1491 + 1492 + [[package]] 1493 + name = "openssl-macros" 1494 + version = "0.1.0" 1495 + source = "registry+https://github.com/rust-lang/crates.io-index" 1496 + checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 1497 + dependencies = [ 1498 + "proc-macro2", 1499 + "quote", 1500 + "syn 1.0.103", 1501 + ] 1502 + 1503 + [[package]] 1504 + name = "openssl-probe" 1505 + version = "0.1.5" 1506 + source = "registry+https://github.com/rust-lang/crates.io-index" 1507 + checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1508 + 1509 + [[package]] 1510 + name = "openssl-sys" 1511 + version = "0.9.77" 1512 + source = "registry+https://github.com/rust-lang/crates.io-index" 1513 + checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" 1514 + dependencies = [ 1515 + "autocfg", 1516 + "cc", 1517 + "libc", 1518 + "pkg-config", 1519 + "vcpkg", 1520 + ] 1521 + 1522 + [[package]] 1523 + name = "ordered-float" 1524 + version = "2.10.0" 1525 + source = "registry+https://github.com/rust-lang/crates.io-index" 1526 + checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" 1527 + dependencies = [ 1528 + "num-traits", 1529 + ] 1530 + 1531 + [[package]] 1532 + name = "overload" 1533 + version = "0.1.1" 1534 + source = "registry+https://github.com/rust-lang/crates.io-index" 1535 + checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1536 + 1537 + [[package]] 1538 + name = "owo-colors" 1539 + version = "1.3.0" 1540 + source = "registry+https://github.com/rust-lang/crates.io-index" 1541 + checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" 1542 + 1543 + [[package]] 1544 + name = "parking" 1545 + version = "2.0.0" 1546 + source = "registry+https://github.com/rust-lang/crates.io-index" 1547 + checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 1548 + 1549 + [[package]] 1550 + name = "parking_lot" 1551 + version = "0.11.2" 1552 + source = "registry+https://github.com/rust-lang/crates.io-index" 1553 + checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1554 + dependencies = [ 1555 + "instant", 1556 + "lock_api", 1557 + "parking_lot_core 0.8.5", 1558 + ] 1559 + 1560 + [[package]] 1561 + name = "parking_lot" 1562 + version = "0.12.1" 1563 + source = "registry+https://github.com/rust-lang/crates.io-index" 1564 + checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1565 + dependencies = [ 1566 + "lock_api", 1567 + "parking_lot_core 0.9.4", 1568 + ] 1569 + 1570 + [[package]] 1571 + name = "parking_lot_core" 1572 + version = "0.8.5" 1573 + source = "registry+https://github.com/rust-lang/crates.io-index" 1574 + checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 1575 + dependencies = [ 1576 + "cfg-if", 1577 + "instant", 1578 + "libc", 1579 + "redox_syscall", 1580 + "smallvec", 1581 + "winapi", 1582 + ] 1583 + 1584 + [[package]] 1585 + name = "parking_lot_core" 1586 + version = "0.9.4" 1587 + source = "registry+https://github.com/rust-lang/crates.io-index" 1588 + checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" 1589 + dependencies = [ 1590 + "cfg-if", 1591 + "libc", 1592 + "redox_syscall", 1593 + "smallvec", 1594 + "windows-sys 0.42.0", 1595 + ] 1596 + 1597 + [[package]] 1598 + name = "parse-zoneinfo" 1599 + version = "0.3.0" 1600 + source = "registry+https://github.com/rust-lang/crates.io-index" 1601 + checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" 1602 + dependencies = [ 1603 + "regex", 1604 + ] 1605 + 1606 + [[package]] 1607 + name = "pastemyst" 1608 + version = "1.0.0" 1609 + source = "registry+https://github.com/rust-lang/crates.io-index" 1610 + checksum = "508b2e93089ce1ea43b8659570c1c65d459bfae0a2779a44d6193e60a6983b5f" 1611 + dependencies = [ 1612 + "reqwest", 1613 + "serde", 1614 + "serde_json", 1615 + "tokio", 1616 + ] 1617 + 1618 + [[package]] 1619 + name = "percent-encoding" 1620 + version = "2.2.0" 1621 + source = "registry+https://github.com/rust-lang/crates.io-index" 1622 + checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 1623 + 1624 + [[package]] 1625 + name = "phf" 1626 + version = "0.11.1" 1627 + source = "registry+https://github.com/rust-lang/crates.io-index" 1628 + checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" 1629 + dependencies = [ 1630 + "phf_shared", 1631 + ] 1632 + 1633 + [[package]] 1634 + name = "phf_codegen" 1635 + version = "0.11.1" 1636 + source = "registry+https://github.com/rust-lang/crates.io-index" 1637 + checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" 1638 + dependencies = [ 1639 + "phf_generator", 1640 + "phf_shared", 1641 + ] 1642 + 1643 + [[package]] 1644 + name = "phf_generator" 1645 + version = "0.11.1" 1646 + source = "registry+https://github.com/rust-lang/crates.io-index" 1647 + checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" 1648 + dependencies = [ 1649 + "phf_shared", 1650 + "rand", 1651 + ] 1652 + 1653 + [[package]] 1654 + name = "phf_shared" 1655 + version = "0.11.1" 1656 + source = "registry+https://github.com/rust-lang/crates.io-index" 1657 + checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" 1658 + dependencies = [ 1659 + "siphasher", 1660 + ] 1661 + 1662 + [[package]] 1663 + name = "pin-project" 1664 + version = "1.0.12" 1665 + source = "registry+https://github.com/rust-lang/crates.io-index" 1666 + checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 1667 + dependencies = [ 1668 + "pin-project-internal", 1669 + ] 1670 + 1671 + [[package]] 1672 + name = "pin-project-internal" 1673 + version = "1.0.12" 1674 + source = "registry+https://github.com/rust-lang/crates.io-index" 1675 + checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 1676 + dependencies = [ 1677 + "proc-macro2", 1678 + "quote", 1679 + "syn 1.0.103", 1680 + ] 1681 + 1682 + [[package]] 1683 + name = "pin-project-lite" 1684 + version = "0.2.13" 1685 + source = "registry+https://github.com/rust-lang/crates.io-index" 1686 + checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 1687 + 1688 + [[package]] 1689 + name = "pin-utils" 1690 + version = "0.1.0" 1691 + source = "registry+https://github.com/rust-lang/crates.io-index" 1692 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1693 + 1694 + [[package]] 1695 + name = "pkg-config" 1696 + version = "0.3.26" 1697 + source = "registry+https://github.com/rust-lang/crates.io-index" 1698 + checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 1699 + 1700 + [[package]] 1701 + name = "poise" 1702 + version = "0.5.6" 1703 + source = "registry+https://github.com/rust-lang/crates.io-index" 1704 + checksum = "649248bcf202ebd1fe3202a7eaf2f705466c92f8d32a644ef565d9221e3ff6f1" 1705 + dependencies = [ 1706 + "async-trait", 1707 + "derivative", 1708 + "futures-core", 1709 + "futures-util", 1710 + "log", 1711 + "once_cell", 1712 + "parking_lot 0.12.1", 1713 + "poise_macros", 1714 + "regex", 1715 + "serenity", 1716 + "tokio", 1717 + ] 1718 + 1719 + [[package]] 1720 + name = "poise_macros" 1721 + version = "0.5.6" 1722 + source = "registry+https://github.com/rust-lang/crates.io-index" 1723 + checksum = "05960fe87728cef0533d13f8ead89da228f5eb7840bee5602eeaa8db150084a0" 1724 + dependencies = [ 1725 + "darling 0.14.2", 1726 + "proc-macro2", 1727 + "quote", 1728 + "syn 1.0.103", 1729 + ] 1730 + 1731 + [[package]] 1732 + name = "polling" 1733 + version = "2.4.0" 1734 + source = "registry+https://github.com/rust-lang/crates.io-index" 1735 + checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" 1736 + dependencies = [ 1737 + "autocfg", 1738 + "cfg-if", 1739 + "libc", 1740 + "log", 1741 + "wepoll-ffi", 1742 + "winapi", 1743 + ] 1744 + 1745 + [[package]] 1746 + name = "ppv-lite86" 1747 + version = "0.2.17" 1748 + source = "registry+https://github.com/rust-lang/crates.io-index" 1749 + checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1750 + 1751 + [[package]] 1752 + name = "proc-macro-error" 1753 + version = "1.0.4" 1754 + source = "registry+https://github.com/rust-lang/crates.io-index" 1755 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1756 + dependencies = [ 1757 + "proc-macro-error-attr", 1758 + "proc-macro2", 1759 + "quote", 1760 + "syn 1.0.103", 1761 + "version_check", 1762 + ] 1763 + 1764 + [[package]] 1765 + name = "proc-macro-error-attr" 1766 + version = "1.0.4" 1767 + source = "registry+https://github.com/rust-lang/crates.io-index" 1768 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1769 + dependencies = [ 1770 + "proc-macro2", 1771 + "quote", 1772 + "version_check", 1773 + ] 1774 + 1775 + [[package]] 1776 + name = "proc-macro2" 1777 + version = "1.0.67" 1778 + source = "registry+https://github.com/rust-lang/crates.io-index" 1779 + checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 1780 + dependencies = [ 1781 + "unicode-ident", 1782 + ] 1783 + 1784 + [[package]] 1785 + name = "ptr_meta" 1786 + version = "0.1.4" 1787 + source = "registry+https://github.com/rust-lang/crates.io-index" 1788 + checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 1789 + dependencies = [ 1790 + "ptr_meta_derive", 1791 + ] 1792 + 1793 + [[package]] 1794 + name = "ptr_meta_derive" 1795 + version = "0.1.4" 1796 + source = "registry+https://github.com/rust-lang/crates.io-index" 1797 + checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 1798 + dependencies = [ 1799 + "proc-macro2", 1800 + "quote", 1801 + "syn 1.0.103", 1802 + ] 1803 + 1804 + [[package]] 1805 + name = "pulldown-cmark" 1806 + version = "0.7.2" 1807 + source = "registry+https://github.com/rust-lang/crates.io-index" 1808 + checksum = "ca36dea94d187597e104a5c8e4b07576a8a45aa5db48a65e12940d3eb7461f55" 1809 + dependencies = [ 1810 + "bitflags 1.3.2", 1811 + "getopts", 1812 + "memchr", 1813 + "unicase", 1814 + ] 1815 + 1816 + [[package]] 1817 + name = "quote" 1818 + version = "1.0.33" 1819 + source = "registry+https://github.com/rust-lang/crates.io-index" 1820 + checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 1821 + dependencies = [ 1822 + "proc-macro2", 1823 + ] 1824 + 1825 + [[package]] 1826 + name = "rand" 1827 + version = "0.8.5" 1828 + source = "registry+https://github.com/rust-lang/crates.io-index" 1829 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1830 + dependencies = [ 1831 + "libc", 1832 + "rand_chacha", 1833 + "rand_core", 1834 + ] 1835 + 1836 + [[package]] 1837 + name = "rand_chacha" 1838 + version = "0.3.1" 1839 + source = "registry+https://github.com/rust-lang/crates.io-index" 1840 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1841 + dependencies = [ 1842 + "ppv-lite86", 1843 + "rand_core", 1844 + ] 1845 + 1846 + [[package]] 1847 + name = "rand_core" 1848 + version = "0.6.4" 1849 + source = "registry+https://github.com/rust-lang/crates.io-index" 1850 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1851 + dependencies = [ 1852 + "getrandom", 1853 + ] 1854 + 1855 + [[package]] 1856 + name = "redis" 1857 + version = "0.23.3" 1858 + source = "registry+https://github.com/rust-lang/crates.io-index" 1859 + checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" 1860 + dependencies = [ 1861 + "async-trait", 1862 + "bytes", 1863 + "combine", 1864 + "futures-util", 1865 + "itoa", 1866 + "percent-encoding", 1867 + "pin-project-lite", 1868 + "ryu", 1869 + "sha1_smol", 1870 + "socket2 0.4.7", 1871 + "tokio", 1872 + "tokio-util", 1873 + "url", 1874 + ] 1875 + 1876 + [[package]] 1877 + name = "redox_syscall" 1878 + version = "0.2.16" 1879 + source = "registry+https://github.com/rust-lang/crates.io-index" 1880 + checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1881 + dependencies = [ 1882 + "bitflags 1.3.2", 1883 + ] 1884 + 1885 + [[package]] 1886 + name = "regex" 1887 + version = "1.9.5" 1888 + source = "registry+https://github.com/rust-lang/crates.io-index" 1889 + checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" 1890 + dependencies = [ 1891 + "aho-corasick", 1892 + "memchr", 1893 + "regex-automata", 1894 + "regex-syntax", 1895 + ] 1896 + 1897 + [[package]] 1898 + name = "regex-automata" 1899 + version = "0.3.8" 1900 + source = "registry+https://github.com/rust-lang/crates.io-index" 1901 + checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" 1902 + dependencies = [ 1903 + "aho-corasick", 1904 + "memchr", 1905 + "regex-syntax", 1906 + ] 1907 + 1908 + [[package]] 1909 + name = "regex-syntax" 1910 + version = "0.7.5" 1911 + source = "registry+https://github.com/rust-lang/crates.io-index" 1912 + checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 1913 + 1914 + [[package]] 1915 + name = "remove_dir_all" 1916 + version = "0.5.3" 1917 + source = "registry+https://github.com/rust-lang/crates.io-index" 1918 + checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1919 + dependencies = [ 1920 + "winapi", 1921 + ] 1922 + 1923 + [[package]] 1924 + name = "rend" 1925 + version = "0.3.6" 1926 + source = "registry+https://github.com/rust-lang/crates.io-index" 1927 + checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" 1928 + dependencies = [ 1929 + "bytecheck", 1930 + ] 1931 + 1932 + [[package]] 1933 + name = "reqwest" 1934 + version = "0.11.20" 1935 + source = "registry+https://github.com/rust-lang/crates.io-index" 1936 + checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" 1937 + dependencies = [ 1938 + "base64 0.21.2", 1939 + "bytes", 1940 + "encoding_rs", 1941 + "futures-core", 1942 + "futures-util", 1943 + "h2", 1944 + "http", 1945 + "http-body", 1946 + "hyper", 1947 + "hyper-rustls", 1948 + "hyper-tls", 1949 + "ipnet", 1950 + "js-sys", 1951 + "log", 1952 + "mime", 1953 + "mime_guess", 1954 + "native-tls", 1955 + "once_cell", 1956 + "percent-encoding", 1957 + "pin-project-lite", 1958 + "rustls 0.21.7", 1959 + "rustls-pemfile", 1960 + "serde", 1961 + "serde_json", 1962 + "serde_urlencoded", 1963 + "tokio", 1964 + "tokio-native-tls", 1965 + "tokio-rustls 0.24.1", 1966 + "tokio-util", 1967 + "tower-service", 1968 + "url", 1969 + "wasm-bindgen", 1970 + "wasm-bindgen-futures", 1971 + "wasm-streams", 1972 + "web-sys", 1973 + "webpki-roots 0.25.2", 1974 + "winreg", 1975 + ] 1976 + 1977 + [[package]] 1978 + name = "ring" 1979 + version = "0.16.20" 1980 + source = "registry+https://github.com/rust-lang/crates.io-index" 1981 + checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1982 + dependencies = [ 1983 + "cc", 1984 + "libc", 1985 + "once_cell", 1986 + "spin", 1987 + "untrusted", 1988 + "web-sys", 1989 + "winapi", 1990 + ] 1991 + 1992 + [[package]] 1993 + name = "rkyv" 1994 + version = "0.7.39" 1995 + source = "registry+https://github.com/rust-lang/crates.io-index" 1996 + checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" 1997 + dependencies = [ 1998 + "bytecheck", 1999 + "hashbrown 0.12.3", 2000 + "ptr_meta", 2001 + "rend", 2002 + "rkyv_derive", 2003 + "seahash", 2004 + ] 2005 + 2006 + [[package]] 2007 + name = "rkyv_derive" 2008 + version = "0.7.39" 2009 + source = "registry+https://github.com/rust-lang/crates.io-index" 2010 + checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" 2011 + dependencies = [ 2012 + "proc-macro2", 2013 + "quote", 2014 + "syn 1.0.103", 2015 + ] 2016 + 2017 + [[package]] 2018 + name = "rustc-demangle" 2019 + version = "0.1.23" 2020 + source = "registry+https://github.com/rust-lang/crates.io-index" 2021 + checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 2022 + 2023 + [[package]] 2024 + name = "rusted-fbt" 2025 + version = "2.7.2" 2026 + dependencies = [ 2027 + "anyhow", 2028 + "chrono", 2029 + "chrono-tz", 2030 + "clap 4.4.4", 2031 + "colored", 2032 + "csv", 2033 + "derivative", 2034 + "futures", 2035 + "linkify", 2036 + "maplit", 2037 + "meilisearch-sdk", 2038 + "merge", 2039 + "once_cell", 2040 + "pastemyst", 2041 + "poise", 2042 + "rand", 2043 + "redis", 2044 + "regex", 2045 + "reqwest", 2046 + "serde", 2047 + "serde_json", 2048 + "serde_with", 2049 + "strip_markdown", 2050 + "thiserror", 2051 + "tokio", 2052 + "tracing", 2053 + "tracing-subscriber", 2054 + "unix-time", 2055 + "uwuify", 2056 + "winres", 2057 + ] 2058 + 2059 + [[package]] 2060 + name = "rustix" 2061 + version = "0.38.14" 2062 + source = "registry+https://github.com/rust-lang/crates.io-index" 2063 + checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" 2064 + dependencies = [ 2065 + "bitflags 2.4.0", 2066 + "errno", 2067 + "libc", 2068 + "linux-raw-sys", 2069 + "windows-sys 0.48.0", 2070 + ] 2071 + 2072 + [[package]] 2073 + name = "rustls" 2074 + version = "0.20.7" 2075 + source = "registry+https://github.com/rust-lang/crates.io-index" 2076 + checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" 2077 + dependencies = [ 2078 + "log", 2079 + "ring", 2080 + "sct", 2081 + "webpki", 2082 + ] 2083 + 2084 + [[package]] 2085 + name = "rustls" 2086 + version = "0.21.7" 2087 + source = "registry+https://github.com/rust-lang/crates.io-index" 2088 + checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" 2089 + dependencies = [ 2090 + "log", 2091 + "ring", 2092 + "rustls-webpki", 2093 + "sct", 2094 + ] 2095 + 2096 + [[package]] 2097 + name = "rustls-pemfile" 2098 + version = "1.0.1" 2099 + source = "registry+https://github.com/rust-lang/crates.io-index" 2100 + checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" 2101 + dependencies = [ 2102 + "base64 0.13.1", 2103 + ] 2104 + 2105 + [[package]] 2106 + name = "rustls-webpki" 2107 + version = "0.101.6" 2108 + source = "registry+https://github.com/rust-lang/crates.io-index" 2109 + checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" 2110 + dependencies = [ 2111 + "ring", 2112 + "untrusted", 2113 + ] 2114 + 2115 + [[package]] 2116 + name = "rustversion" 2117 + version = "1.0.9" 2118 + source = "registry+https://github.com/rust-lang/crates.io-index" 2119 + checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 2120 + 2121 + [[package]] 2122 + name = "ryu" 2123 + version = "1.0.11" 2124 + source = "registry+https://github.com/rust-lang/crates.io-index" 2125 + checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 2126 + 2127 + [[package]] 2128 + name = "schannel" 2129 + version = "0.1.20" 2130 + source = "registry+https://github.com/rust-lang/crates.io-index" 2131 + checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 2132 + dependencies = [ 2133 + "lazy_static", 2134 + "windows-sys 0.36.1", 2135 + ] 2136 + 2137 + [[package]] 2138 + name = "scopeguard" 2139 + version = "1.1.0" 2140 + source = "registry+https://github.com/rust-lang/crates.io-index" 2141 + checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 2142 + 2143 + [[package]] 2144 + name = "scratch" 2145 + version = "1.0.2" 2146 + source = "registry+https://github.com/rust-lang/crates.io-index" 2147 + checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" 2148 + 2149 + [[package]] 2150 + name = "sct" 2151 + version = "0.7.0" 2152 + source = "registry+https://github.com/rust-lang/crates.io-index" 2153 + checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 2154 + dependencies = [ 2155 + "ring", 2156 + "untrusted", 2157 + ] 2158 + 2159 + [[package]] 2160 + name = "seahash" 2161 + version = "4.1.0" 2162 + source = "registry+https://github.com/rust-lang/crates.io-index" 2163 + checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 2164 + 2165 + [[package]] 2166 + name = "security-framework" 2167 + version = "2.7.0" 2168 + source = "registry+https://github.com/rust-lang/crates.io-index" 2169 + checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" 2170 + dependencies = [ 2171 + "bitflags 1.3.2", 2172 + "core-foundation", 2173 + "core-foundation-sys", 2174 + "libc", 2175 + "security-framework-sys", 2176 + ] 2177 + 2178 + [[package]] 2179 + name = "security-framework-sys" 2180 + version = "2.6.1" 2181 + source = "registry+https://github.com/rust-lang/crates.io-index" 2182 + checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 2183 + dependencies = [ 2184 + "core-foundation-sys", 2185 + "libc", 2186 + ] 2187 + 2188 + [[package]] 2189 + name = "serde" 2190 + version = "1.0.188" 2191 + source = "registry+https://github.com/rust-lang/crates.io-index" 2192 + checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 2193 + dependencies = [ 2194 + "serde_derive", 2195 + ] 2196 + 2197 + [[package]] 2198 + name = "serde-value" 2199 + version = "0.7.0" 2200 + source = "registry+https://github.com/rust-lang/crates.io-index" 2201 + checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 2202 + dependencies = [ 2203 + "ordered-float", 2204 + "serde", 2205 + ] 2206 + 2207 + [[package]] 2208 + name = "serde_derive" 2209 + version = "1.0.188" 2210 + source = "registry+https://github.com/rust-lang/crates.io-index" 2211 + checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 2212 + dependencies = [ 2213 + "proc-macro2", 2214 + "quote", 2215 + "syn 2.0.37", 2216 + ] 2217 + 2218 + [[package]] 2219 + name = "serde_json" 2220 + version = "1.0.107" 2221 + source = "registry+https://github.com/rust-lang/crates.io-index" 2222 + checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 2223 + dependencies = [ 2224 + "itoa", 2225 + "ryu", 2226 + "serde", 2227 + ] 2228 + 2229 + [[package]] 2230 + name = "serde_urlencoded" 2231 + version = "0.7.1" 2232 + source = "registry+https://github.com/rust-lang/crates.io-index" 2233 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2234 + dependencies = [ 2235 + "form_urlencoded", 2236 + "itoa", 2237 + "ryu", 2238 + "serde", 2239 + ] 2240 + 2241 + [[package]] 2242 + name = "serde_with" 2243 + version = "3.3.0" 2244 + source = "registry+https://github.com/rust-lang/crates.io-index" 2245 + checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" 2246 + dependencies = [ 2247 + "base64 0.21.2", 2248 + "chrono", 2249 + "hex", 2250 + "indexmap 1.9.2", 2251 + "indexmap 2.0.1", 2252 + "serde", 2253 + "serde_json", 2254 + "serde_with_macros", 2255 + "time", 2256 + ] 2257 + 2258 + [[package]] 2259 + name = "serde_with_macros" 2260 + version = "3.3.0" 2261 + source = "registry+https://github.com/rust-lang/crates.io-index" 2262 + checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" 2263 + dependencies = [ 2264 + "darling 0.20.1", 2265 + "proc-macro2", 2266 + "quote", 2267 + "syn 2.0.37", 2268 + ] 2269 + 2270 + [[package]] 2271 + name = "serenity" 2272 + version = "0.11.5" 2273 + source = "registry+https://github.com/rust-lang/crates.io-index" 2274 + checksum = "82fd5e7b5858ad96e99d440138f34f5b98e1b959ebcd3a1036203b30e78eb788" 2275 + dependencies = [ 2276 + "async-trait", 2277 + "async-tungstenite", 2278 + "base64 0.13.1", 2279 + "bitflags 1.3.2", 2280 + "bytes", 2281 + "cfg-if", 2282 + "chrono", 2283 + "dashmap", 2284 + "flate2", 2285 + "futures", 2286 + "mime", 2287 + "mime_guess", 2288 + "parking_lot 0.12.1", 2289 + "percent-encoding", 2290 + "reqwest", 2291 + "rustversion", 2292 + "serde", 2293 + "serde-value", 2294 + "serde_json", 2295 + "time", 2296 + "tokio", 2297 + "tracing", 2298 + "typemap_rev", 2299 + "url", 2300 + ] 2301 + 2302 + [[package]] 2303 + name = "sha-1" 2304 + version = "0.10.0" 2305 + source = "registry+https://github.com/rust-lang/crates.io-index" 2306 + checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" 2307 + dependencies = [ 2308 + "cfg-if", 2309 + "cpufeatures", 2310 + "digest", 2311 + ] 2312 + 2313 + [[package]] 2314 + name = "sha1_smol" 2315 + version = "1.0.0" 2316 + source = "registry+https://github.com/rust-lang/crates.io-index" 2317 + checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 2318 + 2319 + [[package]] 2320 + name = "sharded-slab" 2321 + version = "0.1.4" 2322 + source = "registry+https://github.com/rust-lang/crates.io-index" 2323 + checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 2324 + dependencies = [ 2325 + "lazy_static", 2326 + ] 2327 + 2328 + [[package]] 2329 + name = "signal-hook-registry" 2330 + version = "1.4.0" 2331 + source = "registry+https://github.com/rust-lang/crates.io-index" 2332 + checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 2333 + dependencies = [ 2334 + "libc", 2335 + ] 2336 + 2337 + [[package]] 2338 + name = "siphasher" 2339 + version = "0.3.10" 2340 + source = "registry+https://github.com/rust-lang/crates.io-index" 2341 + checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 2342 + 2343 + [[package]] 2344 + name = "slab" 2345 + version = "0.4.7" 2346 + source = "registry+https://github.com/rust-lang/crates.io-index" 2347 + checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 2348 + dependencies = [ 2349 + "autocfg", 2350 + ] 2351 + 2352 + [[package]] 2353 + name = "sluice" 2354 + version = "0.5.5" 2355 + source = "registry+https://github.com/rust-lang/crates.io-index" 2356 + checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" 2357 + dependencies = [ 2358 + "async-channel", 2359 + "futures-core", 2360 + "futures-io", 2361 + ] 2362 + 2363 + [[package]] 2364 + name = "smallvec" 2365 + version = "1.10.0" 2366 + source = "registry+https://github.com/rust-lang/crates.io-index" 2367 + checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 2368 + 2369 + [[package]] 2370 + name = "socket2" 2371 + version = "0.4.7" 2372 + source = "registry+https://github.com/rust-lang/crates.io-index" 2373 + checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 2374 + dependencies = [ 2375 + "libc", 2376 + "winapi", 2377 + ] 2378 + 2379 + [[package]] 2380 + name = "socket2" 2381 + version = "0.5.4" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" 2384 + dependencies = [ 2385 + "libc", 2386 + "windows-sys 0.48.0", 2387 + ] 2388 + 2389 + [[package]] 2390 + name = "spin" 2391 + version = "0.5.2" 2392 + source = "registry+https://github.com/rust-lang/crates.io-index" 2393 + checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 2394 + 2395 + [[package]] 2396 + name = "static_assertions" 2397 + version = "1.1.0" 2398 + source = "registry+https://github.com/rust-lang/crates.io-index" 2399 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 2400 + 2401 + [[package]] 2402 + name = "strip_markdown" 2403 + version = "0.2.0" 2404 + source = "registry+https://github.com/rust-lang/crates.io-index" 2405 + checksum = "32c754386109f9adc8ca62513c51cf81cc2d8502588064103aa8e9f69b4276da" 2406 + dependencies = [ 2407 + "log", 2408 + "pulldown-cmark", 2409 + ] 2410 + 2411 + [[package]] 2412 + name = "strsim" 2413 + version = "0.8.0" 2414 + source = "registry+https://github.com/rust-lang/crates.io-index" 2415 + checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 2416 + 2417 + [[package]] 2418 + name = "strsim" 2419 + version = "0.10.0" 2420 + source = "registry+https://github.com/rust-lang/crates.io-index" 2421 + checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 2422 + 2423 + [[package]] 2424 + name = "syn" 2425 + version = "1.0.103" 2426 + source = "registry+https://github.com/rust-lang/crates.io-index" 2427 + checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 2428 + dependencies = [ 2429 + "proc-macro2", 2430 + "quote", 2431 + "unicode-ident", 2432 + ] 2433 + 2434 + [[package]] 2435 + name = "syn" 2436 + version = "2.0.37" 2437 + source = "registry+https://github.com/rust-lang/crates.io-index" 2438 + checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 2439 + dependencies = [ 2440 + "proc-macro2", 2441 + "quote", 2442 + "unicode-ident", 2443 + ] 2444 + 2445 + [[package]] 2446 + name = "tempfile" 2447 + version = "3.3.0" 2448 + source = "registry+https://github.com/rust-lang/crates.io-index" 2449 + checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 2450 + dependencies = [ 2451 + "cfg-if", 2452 + "fastrand", 2453 + "libc", 2454 + "redox_syscall", 2455 + "remove_dir_all", 2456 + "winapi", 2457 + ] 2458 + 2459 + [[package]] 2460 + name = "termcolor" 2461 + version = "1.1.3" 2462 + source = "registry+https://github.com/rust-lang/crates.io-index" 2463 + checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 2464 + dependencies = [ 2465 + "winapi-util", 2466 + ] 2467 + 2468 + [[package]] 2469 + name = "textwrap" 2470 + version = "0.11.0" 2471 + source = "registry+https://github.com/rust-lang/crates.io-index" 2472 + checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 2473 + dependencies = [ 2474 + "unicode-width", 2475 + ] 2476 + 2477 + [[package]] 2478 + name = "thiserror" 2479 + version = "1.0.49" 2480 + source = "registry+https://github.com/rust-lang/crates.io-index" 2481 + checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" 2482 + dependencies = [ 2483 + "thiserror-impl", 2484 + ] 2485 + 2486 + [[package]] 2487 + name = "thiserror-impl" 2488 + version = "1.0.49" 2489 + source = "registry+https://github.com/rust-lang/crates.io-index" 2490 + checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" 2491 + dependencies = [ 2492 + "proc-macro2", 2493 + "quote", 2494 + "syn 2.0.37", 2495 + ] 2496 + 2497 + [[package]] 2498 + name = "thread_local" 2499 + version = "1.1.4" 2500 + source = "registry+https://github.com/rust-lang/crates.io-index" 2501 + checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 2502 + dependencies = [ 2503 + "once_cell", 2504 + ] 2505 + 2506 + [[package]] 2507 + name = "time" 2508 + version = "0.3.17" 2509 + source = "registry+https://github.com/rust-lang/crates.io-index" 2510 + checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 2511 + dependencies = [ 2512 + "itoa", 2513 + "serde", 2514 + "time-core", 2515 + "time-macros", 2516 + ] 2517 + 2518 + [[package]] 2519 + name = "time-core" 2520 + version = "0.1.0" 2521 + source = "registry+https://github.com/rust-lang/crates.io-index" 2522 + checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 2523 + 2524 + [[package]] 2525 + name = "time-macros" 2526 + version = "0.2.6" 2527 + source = "registry+https://github.com/rust-lang/crates.io-index" 2528 + checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 2529 + dependencies = [ 2530 + "time-core", 2531 + ] 2532 + 2533 + [[package]] 2534 + name = "tinyvec" 2535 + version = "1.6.0" 2536 + source = "registry+https://github.com/rust-lang/crates.io-index" 2537 + checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 2538 + dependencies = [ 2539 + "tinyvec_macros", 2540 + ] 2541 + 2542 + [[package]] 2543 + name = "tinyvec_macros" 2544 + version = "0.1.0" 2545 + source = "registry+https://github.com/rust-lang/crates.io-index" 2546 + checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 2547 + 2548 + [[package]] 2549 + name = "tokio" 2550 + version = "1.32.0" 2551 + source = "registry+https://github.com/rust-lang/crates.io-index" 2552 + checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 2553 + dependencies = [ 2554 + "backtrace", 2555 + "bytes", 2556 + "libc", 2557 + "mio", 2558 + "num_cpus", 2559 + "parking_lot 0.12.1", 2560 + "pin-project-lite", 2561 + "signal-hook-registry", 2562 + "socket2 0.5.4", 2563 + "tokio-macros", 2564 + "windows-sys 0.48.0", 2565 + ] 2566 + 2567 + [[package]] 2568 + name = "tokio-macros" 2569 + version = "2.1.0" 2570 + source = "registry+https://github.com/rust-lang/crates.io-index" 2571 + checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 2572 + dependencies = [ 2573 + "proc-macro2", 2574 + "quote", 2575 + "syn 2.0.37", 2576 + ] 2577 + 2578 + [[package]] 2579 + name = "tokio-native-tls" 2580 + version = "0.3.0" 2581 + source = "registry+https://github.com/rust-lang/crates.io-index" 2582 + checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 2583 + dependencies = [ 2584 + "native-tls", 2585 + "tokio", 2586 + ] 2587 + 2588 + [[package]] 2589 + name = "tokio-rustls" 2590 + version = "0.23.4" 2591 + source = "registry+https://github.com/rust-lang/crates.io-index" 2592 + checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 2593 + dependencies = [ 2594 + "rustls 0.20.7", 2595 + "tokio", 2596 + "webpki", 2597 + ] 2598 + 2599 + [[package]] 2600 + name = "tokio-rustls" 2601 + version = "0.24.1" 2602 + source = "registry+https://github.com/rust-lang/crates.io-index" 2603 + checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 2604 + dependencies = [ 2605 + "rustls 0.21.7", 2606 + "tokio", 2607 + ] 2608 + 2609 + [[package]] 2610 + name = "tokio-util" 2611 + version = "0.7.4" 2612 + source = "registry+https://github.com/rust-lang/crates.io-index" 2613 + checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 2614 + dependencies = [ 2615 + "bytes", 2616 + "futures-core", 2617 + "futures-sink", 2618 + "pin-project-lite", 2619 + "tokio", 2620 + "tracing", 2621 + ] 2622 + 2623 + [[package]] 2624 + name = "toml" 2625 + version = "0.5.9" 2626 + source = "registry+https://github.com/rust-lang/crates.io-index" 2627 + checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 2628 + dependencies = [ 2629 + "serde", 2630 + ] 2631 + 2632 + [[package]] 2633 + name = "tower-service" 2634 + version = "0.3.2" 2635 + source = "registry+https://github.com/rust-lang/crates.io-index" 2636 + checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 2637 + 2638 + [[package]] 2639 + name = "tracing" 2640 + version = "0.1.37" 2641 + source = "registry+https://github.com/rust-lang/crates.io-index" 2642 + checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 2643 + dependencies = [ 2644 + "cfg-if", 2645 + "log", 2646 + "pin-project-lite", 2647 + "tracing-attributes", 2648 + "tracing-core", 2649 + ] 2650 + 2651 + [[package]] 2652 + name = "tracing-attributes" 2653 + version = "0.1.23" 2654 + source = "registry+https://github.com/rust-lang/crates.io-index" 2655 + checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 2656 + dependencies = [ 2657 + "proc-macro2", 2658 + "quote", 2659 + "syn 1.0.103", 2660 + ] 2661 + 2662 + [[package]] 2663 + name = "tracing-core" 2664 + version = "0.1.30" 2665 + source = "registry+https://github.com/rust-lang/crates.io-index" 2666 + checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 2667 + dependencies = [ 2668 + "once_cell", 2669 + "valuable", 2670 + ] 2671 + 2672 + [[package]] 2673 + name = "tracing-futures" 2674 + version = "0.2.5" 2675 + source = "registry+https://github.com/rust-lang/crates.io-index" 2676 + checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 2677 + dependencies = [ 2678 + "pin-project", 2679 + "tracing", 2680 + ] 2681 + 2682 + [[package]] 2683 + name = "tracing-log" 2684 + version = "0.1.3" 2685 + source = "registry+https://github.com/rust-lang/crates.io-index" 2686 + checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 2687 + dependencies = [ 2688 + "lazy_static", 2689 + "log", 2690 + "tracing-core", 2691 + ] 2692 + 2693 + [[package]] 2694 + name = "tracing-subscriber" 2695 + version = "0.3.17" 2696 + source = "registry+https://github.com/rust-lang/crates.io-index" 2697 + checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 2698 + dependencies = [ 2699 + "nu-ansi-term", 2700 + "parking_lot 0.12.1", 2701 + "sharded-slab", 2702 + "smallvec", 2703 + "thread_local", 2704 + "tracing-core", 2705 + "tracing-log", 2706 + ] 2707 + 2708 + [[package]] 2709 + name = "try-lock" 2710 + version = "0.2.3" 2711 + source = "registry+https://github.com/rust-lang/crates.io-index" 2712 + checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 2713 + 2714 + [[package]] 2715 + name = "tungstenite" 2716 + version = "0.17.3" 2717 + source = "registry+https://github.com/rust-lang/crates.io-index" 2718 + checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" 2719 + dependencies = [ 2720 + "base64 0.13.1", 2721 + "byteorder", 2722 + "bytes", 2723 + "http", 2724 + "httparse", 2725 + "log", 2726 + "rand", 2727 + "rustls 0.20.7", 2728 + "sha-1", 2729 + "thiserror", 2730 + "url", 2731 + "utf-8", 2732 + "webpki", 2733 + ] 2734 + 2735 + [[package]] 2736 + name = "typemap_rev" 2737 + version = "0.1.5" 2738 + source = "registry+https://github.com/rust-lang/crates.io-index" 2739 + checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" 2740 + 2741 + [[package]] 2742 + name = "typenum" 2743 + version = "1.15.0" 2744 + source = "registry+https://github.com/rust-lang/crates.io-index" 2745 + checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 2746 + 2747 + [[package]] 2748 + name = "unicase" 2749 + version = "2.6.0" 2750 + source = "registry+https://github.com/rust-lang/crates.io-index" 2751 + checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 2752 + dependencies = [ 2753 + "version_check", 2754 + ] 2755 + 2756 + [[package]] 2757 + name = "unicode-bidi" 2758 + version = "0.3.8" 2759 + source = "registry+https://github.com/rust-lang/crates.io-index" 2760 + checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 2761 + 2762 + [[package]] 2763 + name = "unicode-ident" 2764 + version = "1.0.5" 2765 + source = "registry+https://github.com/rust-lang/crates.io-index" 2766 + checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 2767 + 2768 + [[package]] 2769 + name = "unicode-normalization" 2770 + version = "0.1.22" 2771 + source = "registry+https://github.com/rust-lang/crates.io-index" 2772 + checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 2773 + dependencies = [ 2774 + "tinyvec", 2775 + ] 2776 + 2777 + [[package]] 2778 + name = "unicode-width" 2779 + version = "0.1.10" 2780 + source = "registry+https://github.com/rust-lang/crates.io-index" 2781 + checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 2782 + 2783 + [[package]] 2784 + name = "unix-time" 2785 + version = "0.1.5" 2786 + source = "registry+https://github.com/rust-lang/crates.io-index" 2787 + checksum = "f9d919c7a852a6cf0fc00baf8114303e7a6d6c8c8049e5c740aa1fc1b86a22b8" 2788 + dependencies = [ 2789 + "rkyv", 2790 + "serde", 2791 + ] 2792 + 2793 + [[package]] 2794 + name = "untrusted" 2795 + version = "0.7.1" 2796 + source = "registry+https://github.com/rust-lang/crates.io-index" 2797 + checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 2798 + 2799 + [[package]] 2800 + name = "url" 2801 + version = "2.3.1" 2802 + source = "registry+https://github.com/rust-lang/crates.io-index" 2803 + checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 2804 + dependencies = [ 2805 + "form_urlencoded", 2806 + "idna", 2807 + "percent-encoding", 2808 + "serde", 2809 + ] 2810 + 2811 + [[package]] 2812 + name = "utf-8" 2813 + version = "0.7.6" 2814 + source = "registry+https://github.com/rust-lang/crates.io-index" 2815 + checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2816 + 2817 + [[package]] 2818 + name = "utf8parse" 2819 + version = "0.2.1" 2820 + source = "registry+https://github.com/rust-lang/crates.io-index" 2821 + checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 2822 + 2823 + [[package]] 2824 + name = "uwuify" 2825 + version = "0.2.2" 2826 + source = "registry+https://github.com/rust-lang/crates.io-index" 2827 + checksum = "3db6840b7adcfd2e866c79157cc890ecdbbc1f739607134039ae64eaa6c07e24" 2828 + dependencies = [ 2829 + "clap 2.34.0", 2830 + "owo-colors", 2831 + "parking_lot 0.11.2", 2832 + "thiserror", 2833 + ] 2834 + 2835 + [[package]] 2836 + name = "valuable" 2837 + version = "0.1.0" 2838 + source = "registry+https://github.com/rust-lang/crates.io-index" 2839 + checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2840 + 2841 + [[package]] 2842 + name = "vcpkg" 2843 + version = "0.2.15" 2844 + source = "registry+https://github.com/rust-lang/crates.io-index" 2845 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2846 + 2847 + [[package]] 2848 + name = "vec_map" 2849 + version = "0.8.2" 2850 + source = "registry+https://github.com/rust-lang/crates.io-index" 2851 + checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 2852 + 2853 + [[package]] 2854 + name = "version_check" 2855 + version = "0.9.4" 2856 + source = "registry+https://github.com/rust-lang/crates.io-index" 2857 + checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2858 + 2859 + [[package]] 2860 + name = "waker-fn" 2861 + version = "1.1.0" 2862 + source = "registry+https://github.com/rust-lang/crates.io-index" 2863 + checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 2864 + 2865 + [[package]] 2866 + name = "want" 2867 + version = "0.3.0" 2868 + source = "registry+https://github.com/rust-lang/crates.io-index" 2869 + checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 2870 + dependencies = [ 2871 + "log", 2872 + "try-lock", 2873 + ] 2874 + 2875 + [[package]] 2876 + name = "wasi" 2877 + version = "0.11.0+wasi-snapshot-preview1" 2878 + source = "registry+https://github.com/rust-lang/crates.io-index" 2879 + checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2880 + 2881 + [[package]] 2882 + name = "wasm-bindgen" 2883 + version = "0.2.83" 2884 + source = "registry+https://github.com/rust-lang/crates.io-index" 2885 + checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 2886 + dependencies = [ 2887 + "cfg-if", 2888 + "wasm-bindgen-macro", 2889 + ] 2890 + 2891 + [[package]] 2892 + name = "wasm-bindgen-backend" 2893 + version = "0.2.83" 2894 + source = "registry+https://github.com/rust-lang/crates.io-index" 2895 + checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 2896 + dependencies = [ 2897 + "bumpalo", 2898 + "log", 2899 + "once_cell", 2900 + "proc-macro2", 2901 + "quote", 2902 + "syn 1.0.103", 2903 + "wasm-bindgen-shared", 2904 + ] 2905 + 2906 + [[package]] 2907 + name = "wasm-bindgen-futures" 2908 + version = "0.4.33" 2909 + source = "registry+https://github.com/rust-lang/crates.io-index" 2910 + checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" 2911 + dependencies = [ 2912 + "cfg-if", 2913 + "js-sys", 2914 + "wasm-bindgen", 2915 + "web-sys", 2916 + ] 2917 + 2918 + [[package]] 2919 + name = "wasm-bindgen-macro" 2920 + version = "0.2.83" 2921 + source = "registry+https://github.com/rust-lang/crates.io-index" 2922 + checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 2923 + dependencies = [ 2924 + "quote", 2925 + "wasm-bindgen-macro-support", 2926 + ] 2927 + 2928 + [[package]] 2929 + name = "wasm-bindgen-macro-support" 2930 + version = "0.2.83" 2931 + source = "registry+https://github.com/rust-lang/crates.io-index" 2932 + checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 2933 + dependencies = [ 2934 + "proc-macro2", 2935 + "quote", 2936 + "syn 1.0.103", 2937 + "wasm-bindgen-backend", 2938 + "wasm-bindgen-shared", 2939 + ] 2940 + 2941 + [[package]] 2942 + name = "wasm-bindgen-shared" 2943 + version = "0.2.83" 2944 + source = "registry+https://github.com/rust-lang/crates.io-index" 2945 + checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 2946 + 2947 + [[package]] 2948 + name = "wasm-streams" 2949 + version = "0.3.0" 2950 + source = "registry+https://github.com/rust-lang/crates.io-index" 2951 + checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" 2952 + dependencies = [ 2953 + "futures-util", 2954 + "js-sys", 2955 + "wasm-bindgen", 2956 + "wasm-bindgen-futures", 2957 + "web-sys", 2958 + ] 2959 + 2960 + [[package]] 2961 + name = "web-sys" 2962 + version = "0.3.60" 2963 + source = "registry+https://github.com/rust-lang/crates.io-index" 2964 + checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 2965 + dependencies = [ 2966 + "js-sys", 2967 + "wasm-bindgen", 2968 + ] 2969 + 2970 + [[package]] 2971 + name = "webpki" 2972 + version = "0.22.0" 2973 + source = "registry+https://github.com/rust-lang/crates.io-index" 2974 + checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 2975 + dependencies = [ 2976 + "ring", 2977 + "untrusted", 2978 + ] 2979 + 2980 + [[package]] 2981 + name = "webpki-roots" 2982 + version = "0.22.5" 2983 + source = "registry+https://github.com/rust-lang/crates.io-index" 2984 + checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" 2985 + dependencies = [ 2986 + "webpki", 2987 + ] 2988 + 2989 + [[package]] 2990 + name = "webpki-roots" 2991 + version = "0.25.2" 2992 + source = "registry+https://github.com/rust-lang/crates.io-index" 2993 + checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" 2994 + 2995 + [[package]] 2996 + name = "wepoll-ffi" 2997 + version = "0.1.2" 2998 + source = "registry+https://github.com/rust-lang/crates.io-index" 2999 + checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 3000 + dependencies = [ 3001 + "cc", 3002 + ] 3003 + 3004 + [[package]] 3005 + name = "winapi" 3006 + version = "0.3.9" 3007 + source = "registry+https://github.com/rust-lang/crates.io-index" 3008 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3009 + dependencies = [ 3010 + "winapi-i686-pc-windows-gnu", 3011 + "winapi-x86_64-pc-windows-gnu", 3012 + ] 3013 + 3014 + [[package]] 3015 + name = "winapi-i686-pc-windows-gnu" 3016 + version = "0.4.0" 3017 + source = "registry+https://github.com/rust-lang/crates.io-index" 3018 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3019 + 3020 + [[package]] 3021 + name = "winapi-util" 3022 + version = "0.1.5" 3023 + source = "registry+https://github.com/rust-lang/crates.io-index" 3024 + checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 3025 + dependencies = [ 3026 + "winapi", 3027 + ] 3028 + 3029 + [[package]] 3030 + name = "winapi-x86_64-pc-windows-gnu" 3031 + version = "0.4.0" 3032 + source = "registry+https://github.com/rust-lang/crates.io-index" 3033 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3034 + 3035 + [[package]] 3036 + name = "windows-sys" 3037 + version = "0.36.1" 3038 + source = "registry+https://github.com/rust-lang/crates.io-index" 3039 + checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 3040 + dependencies = [ 3041 + "windows_aarch64_msvc 0.36.1", 3042 + "windows_i686_gnu 0.36.1", 3043 + "windows_i686_msvc 0.36.1", 3044 + "windows_x86_64_gnu 0.36.1", 3045 + "windows_x86_64_msvc 0.36.1", 3046 + ] 3047 + 3048 + [[package]] 3049 + name = "windows-sys" 3050 + version = "0.42.0" 3051 + source = "registry+https://github.com/rust-lang/crates.io-index" 3052 + checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 3053 + dependencies = [ 3054 + "windows_aarch64_gnullvm 0.42.0", 3055 + "windows_aarch64_msvc 0.42.0", 3056 + "windows_i686_gnu 0.42.0", 3057 + "windows_i686_msvc 0.42.0", 3058 + "windows_x86_64_gnu 0.42.0", 3059 + "windows_x86_64_gnullvm 0.42.0", 3060 + "windows_x86_64_msvc 0.42.0", 3061 + ] 3062 + 3063 + [[package]] 3064 + name = "windows-sys" 3065 + version = "0.48.0" 3066 + source = "registry+https://github.com/rust-lang/crates.io-index" 3067 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3068 + dependencies = [ 3069 + "windows-targets", 3070 + ] 3071 + 3072 + [[package]] 3073 + name = "windows-targets" 3074 + version = "0.48.5" 3075 + source = "registry+https://github.com/rust-lang/crates.io-index" 3076 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3077 + dependencies = [ 3078 + "windows_aarch64_gnullvm 0.48.5", 3079 + "windows_aarch64_msvc 0.48.5", 3080 + "windows_i686_gnu 0.48.5", 3081 + "windows_i686_msvc 0.48.5", 3082 + "windows_x86_64_gnu 0.48.5", 3083 + "windows_x86_64_gnullvm 0.48.5", 3084 + "windows_x86_64_msvc 0.48.5", 3085 + ] 3086 + 3087 + [[package]] 3088 + name = "windows_aarch64_gnullvm" 3089 + version = "0.42.0" 3090 + source = "registry+https://github.com/rust-lang/crates.io-index" 3091 + checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 3092 + 3093 + [[package]] 3094 + name = "windows_aarch64_gnullvm" 3095 + version = "0.48.5" 3096 + source = "registry+https://github.com/rust-lang/crates.io-index" 3097 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3098 + 3099 + [[package]] 3100 + name = "windows_aarch64_msvc" 3101 + version = "0.36.1" 3102 + source = "registry+https://github.com/rust-lang/crates.io-index" 3103 + checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 3104 + 3105 + [[package]] 3106 + name = "windows_aarch64_msvc" 3107 + version = "0.42.0" 3108 + source = "registry+https://github.com/rust-lang/crates.io-index" 3109 + checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 3110 + 3111 + [[package]] 3112 + name = "windows_aarch64_msvc" 3113 + version = "0.48.5" 3114 + source = "registry+https://github.com/rust-lang/crates.io-index" 3115 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3116 + 3117 + [[package]] 3118 + name = "windows_i686_gnu" 3119 + version = "0.36.1" 3120 + source = "registry+https://github.com/rust-lang/crates.io-index" 3121 + checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 3122 + 3123 + [[package]] 3124 + name = "windows_i686_gnu" 3125 + version = "0.42.0" 3126 + source = "registry+https://github.com/rust-lang/crates.io-index" 3127 + checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 3128 + 3129 + [[package]] 3130 + name = "windows_i686_gnu" 3131 + version = "0.48.5" 3132 + source = "registry+https://github.com/rust-lang/crates.io-index" 3133 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3134 + 3135 + [[package]] 3136 + name = "windows_i686_msvc" 3137 + version = "0.36.1" 3138 + source = "registry+https://github.com/rust-lang/crates.io-index" 3139 + checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 3140 + 3141 + [[package]] 3142 + name = "windows_i686_msvc" 3143 + version = "0.42.0" 3144 + source = "registry+https://github.com/rust-lang/crates.io-index" 3145 + checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 3146 + 3147 + [[package]] 3148 + name = "windows_i686_msvc" 3149 + version = "0.48.5" 3150 + source = "registry+https://github.com/rust-lang/crates.io-index" 3151 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3152 + 3153 + [[package]] 3154 + name = "windows_x86_64_gnu" 3155 + version = "0.36.1" 3156 + source = "registry+https://github.com/rust-lang/crates.io-index" 3157 + checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 3158 + 3159 + [[package]] 3160 + name = "windows_x86_64_gnu" 3161 + version = "0.42.0" 3162 + source = "registry+https://github.com/rust-lang/crates.io-index" 3163 + checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 3164 + 3165 + [[package]] 3166 + name = "windows_x86_64_gnu" 3167 + version = "0.48.5" 3168 + source = "registry+https://github.com/rust-lang/crates.io-index" 3169 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3170 + 3171 + [[package]] 3172 + name = "windows_x86_64_gnullvm" 3173 + version = "0.42.0" 3174 + source = "registry+https://github.com/rust-lang/crates.io-index" 3175 + checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 3176 + 3177 + [[package]] 3178 + name = "windows_x86_64_gnullvm" 3179 + version = "0.48.5" 3180 + source = "registry+https://github.com/rust-lang/crates.io-index" 3181 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3182 + 3183 + [[package]] 3184 + name = "windows_x86_64_msvc" 3185 + version = "0.36.1" 3186 + source = "registry+https://github.com/rust-lang/crates.io-index" 3187 + checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 3188 + 3189 + [[package]] 3190 + name = "windows_x86_64_msvc" 3191 + version = "0.42.0" 3192 + source = "registry+https://github.com/rust-lang/crates.io-index" 3193 + checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 3194 + 3195 + [[package]] 3196 + name = "windows_x86_64_msvc" 3197 + version = "0.48.5" 3198 + source = "registry+https://github.com/rust-lang/crates.io-index" 3199 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 3200 + 3201 + [[package]] 3202 + name = "winreg" 3203 + version = "0.50.0" 3204 + source = "registry+https://github.com/rust-lang/crates.io-index" 3205 + checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 3206 + dependencies = [ 3207 + "cfg-if", 3208 + "windows-sys 0.48.0", 3209 + ] 3210 + 3211 + [[package]] 3212 + name = "winres" 3213 + version = "0.1.12" 3214 + source = "registry+https://github.com/rust-lang/crates.io-index" 3215 + checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" 3216 + dependencies = [ 3217 + "toml", 3218 + ]
+74
Cargo.toml
··· 1 + [[bin]] 2 + name = "rusted-fbt" 3 + path = "src/main.rs" 4 + 5 + [lib] 6 + name = "rusted_fbt_lib" 7 + path = "src/lib/lib.rs" 8 + 9 + [package] 10 + name = "rusted-fbt" 11 + version = "2.7.2" 12 + edition = "2021" 13 + publish = false 14 + 15 + [features] 16 + default = [ 17 + "database", 18 + ] # Comment out this line when adding new imports and functions to see what needs a #[cfg(feature = "database")] 19 + database = [] 20 + beta = [] 21 + 22 + [dependencies] 23 + poise = { version = "0.5.6", features = [ 24 + "cache", 25 + "collector", 26 + ] } 27 + tokio = { version = "1.32.0", features = ["full"] } 28 + csv = "1.2.2" 29 + serde = "1.0.188" 30 + serde_json = "1.0.107" 31 + uwuify = "0.2.2" 32 + chrono = "0.4.31" 33 + clap = { version = "4.0.32", features = ["derive"] } 34 + maplit = "1.0.2" 35 + colored = "2.0.4" 36 + strip_markdown = "0.2.0" 37 + reqwest = "0.11.20" 38 + futures = "0.3.28" 39 + redis = { version = "0.23.3", features = ["aio", "tokio-comp"] } 40 + merge = { version = "0.1.0", features = ["std", "num", "derive"] } 41 + derivative = "2.2.0" 42 + rand = "0.8.5" 43 + unix-time = "0.1.5" 44 + regex = "1.9.5" 45 + once_cell = "1.18.0" 46 + chrono-tz = "0.8.3" 47 + meilisearch-sdk = "0.17.0" 48 + serde_with = "3.3.0" 49 + tracing = { version = "0.1.37", features = ["async-await"] } 50 + tracing-subscriber = { version = "0.3.17", features = [ 51 + "parking_lot", 52 + "registry", 53 + ] } 54 + linkify = "0.10.0" 55 + anyhow = "1.0.75" 56 + thiserror = "1.0.49" 57 + pastemyst = "1.0.0" 58 + # oxipng = "5.0.1" 59 + # mozjpeg = "0.9.3" 60 + # lz4_flex = "0.9.3" 61 + 62 + [target.'cfg(windows)'.build-dependencies] 63 + winres = "0.1.12" 64 + 65 + [profile.dev.package."*"] 66 + opt-level = 1 67 + 68 + [profile.release] 69 + opt-level = 3 70 + lto = "thin" 71 + 72 + [profile.release-full] 73 + inherits = "release" 74 + lto = true
+1
README.md
··· 1 + # FBT Security - Rust
assets/DILLIGAF.ogg

This is a binary file and will not be displayed.

assets/Discord_meme.mp4

This is a binary file and will not be displayed.

assets/FBT_Security_Unedited.ogg

This is a binary file and will not be displayed.

assets/PomfPomf.ogg

This is a binary file and will not be displayed.

assets/amogus.gif

This is a binary file and will not be displayed.

assets/astro.ogg

This is a binary file and will not be displayed.

assets/egirls.ogg

This is a binary file and will not be displayed.

assets/erp-tonight.mp3

This is a binary file and will not be displayed.

assets/femboys.mp3

This is a binary file and will not be displayed.

assets/icon.ico

This is a binary file and will not be displayed.

+110
assets/languages.txt
··· 1 + Supported languages and their language codes (case-sensitive!): 2 + (Download this file to see all supported languages) 3 + 4 + Afrikaans : af 5 + Albanian : sq 6 + Amharic : am 7 + Arabic : ar 8 + Armenian : hy 9 + Azerbaijani : az 10 + Basque : eu 11 + Belarusian : be 12 + Bengali : bn 13 + Bosnian : bs 14 + Bulgarian : bg 15 + Catalan : ca 16 + Cebuano : ceb 17 + Chichewa : ny 18 + Chinese (simplified) : zh-cn 19 + Chinese (traditional) : zh-tw 20 + Corsican : co 21 + Croatian : hr 22 + Czech : cs 23 + Danish : da 24 + Dutch : nl 25 + English : en 26 + Esperanto : eo 27 + Estonian : et 28 + Filipino : tl 29 + Finnish : fi 30 + French : fr 31 + Frisian : fy 32 + Galician : gl 33 + Georgian : ka 34 + German : de 35 + Greek : el 36 + Gujarati : gu 37 + Haitian creole : ht 38 + Hausa : ha 39 + Hawaiian : haw 40 + Hebrew : iw 41 + Hebrew : he 42 + Hindi : hi 43 + Hmong : hmn 44 + Hungarian : hu 45 + Icelandic : is 46 + Igbo : ig 47 + Indonesian : id 48 + Irish : ga 49 + Italian : it 50 + Japanese : ja 51 + Javanese : jw 52 + Kannada : kn 53 + Kazakh : kk 54 + Khmer : km 55 + Korean : ko 56 + Kurdish (kurmanji) : ku 57 + Kyrgyz : ky 58 + Lao : lo 59 + Latin : la 60 + Latvian : lv 61 + Lithuanian : lt 62 + Luxembourgish : lb 63 + Macedonian : mk 64 + Malagasy : mg 65 + Malay : ms 66 + Malayalam : ml 67 + Maltese : mt 68 + Maori : mi 69 + Marathi : mr 70 + Mongolian : mn 71 + Myanmar (burmese) : my 72 + Nepali : ne 73 + Norwegian : no 74 + Odia : or 75 + Pashto : ps 76 + Persian : fa 77 + Polish : pl 78 + Portuguese : pt 79 + Punjabi : pa 80 + Romanian : ro 81 + Russian : ru 82 + Samoan : sm 83 + Scots gaelic : gd 84 + Serbian : sr 85 + Sesotho : st 86 + Shona : sn 87 + Sindhi : sd 88 + Sinhala : si 89 + Slovak : sk 90 + Slovenian : sl 91 + Somali : so 92 + Spanish : es 93 + Sundanese : su 94 + Swahili : sw 95 + Swedish : sv 96 + Tajik : tg 97 + Tamil : ta 98 + Telugu : te 99 + Thai : th 100 + Turkish : tr 101 + Ukrainian : uk 102 + Urdu : ur 103 + Uyghur : ug 104 + Uzbek : uz 105 + Vietnamese : vi 106 + Welsh : cy 107 + Xhosa : xh 108 + Yiddish : yi 109 + Yoruba : yo 110 + Zulu : zu
+51
assets/lyrics.txt
··· 1 + Astro-naut 2 + What you know about rollin' down in the deep? 3 + When your brain goes numb, you can call that mental freeze 4 + When these people talk too much, put that shit in slow motion, yeah 5 + I feel like an astronaut in the ocean, ayy 6 + What you know about rollin' down in the deep? 7 + When your brain goes numb, you can call that mental freeze 8 + When these people talk too much, put that shit in slow motion, yeah 9 + I feel like an astronaut in the ocean 10 + She say that I'm cool (damn straight) 11 + I'm like "yeah, that's true" (that's true) 12 + I believe in G-O-D (ayy) 13 + Don't believe in T-H-O-T 14 + She keep playing me dumb (play me) 15 + I'ma play her for fun (uh-huh) 16 + Y'all don't really know my mental 17 + Lemme give you the picture like stencil 18 + Falling out, in a drought 19 + No flow, rain wasn't pouring down (pouring down) 20 + See, that pain was all around 21 + See, my mode was kinda lounged 22 + Didn't know which-which way to turn 23 + Flow was cool but I still felt burnt 24 + Energy up, you can feel my surge 25 + I'ma kill everything like this purge (ayy) 26 + Let's just get this straight for a second, I'ma work 27 + Even if I don't get paid for progression, I'ma get it (get it) 28 + Everything that I do is electric 29 + I'ma keep it in a motion, keep it moving like kinetic, ayy (yeah, yeah, yeah, yeah) 30 + Put this shit in a frame, better know I don't blame 31 + Everything that I say, man I seen you deflate 32 + Let me elevate, this ain't a prank 33 + Have you walkin' on a plank, la-la-la-la-la, like 34 + Both hands together, God, let me pray (now let me pray) 35 + Uh, I've been going right, right around, call that relay (Masked Wolf) 36 + Pass the baton, back and I'm on 37 + Swimming in the pool, Kendrick Lamar, uh 38 + Want a piece of this, a piece of mine, my peace a sign 39 + Can you please read between the lines? 40 + My rhyme's inclined to break your spine 41 + They say that I'm so fine 42 + You could never match my grind 43 + Please do not, not waste my time 44 + What you know about rollin' down in the deep? 45 + When your brain goes numb, you can call that mental freeze 46 + When these people talk too much, put that shit in slow motion, yeah 47 + I feel like an astronaut in the ocean, ayy 48 + What you know about rollin' down in the deep? 49 + When your brain goes numb, you can call that mental freeze 50 + When these people talk too much, put that shit in slow motion, yeah 51 + I feel like an astronaut in the ocean
assets/pog-frog.gif

This is a binary file and will not be displayed.

assets/toxic.gif

This is a binary file and will not be displayed.

assets/user.gif

This is a binary file and will not be displayed.

assets/white.mp4

This is a binary file and will not be displayed.

assets/wrestle.mp4

This is a binary file and will not be displayed.

+1
build.ps1
··· 1 + $Env:RUSTFLAGS='-C target-cpu=native'; cargo build --release
+21
build.rs
··· 1 + use std::io; 2 + #[cfg(windows)] 3 + use winres::WindowsResource; 4 + 5 + #[allow(clippy::unnecessary_wraps)] 6 + // I'm just keeping this as the example had it, no need to warn me about this since it doesn't affect the actual code 7 + /// This code will set the windows program icon to the fbt logo 8 + /// 9 + /// # Errors 10 + /// 11 + /// This function will return an error if the icon file cannot be found at compile time. 12 + fn main() -> io::Result<()> { 13 + #[cfg(windows)] 14 + { 15 + WindowsResource::new() 16 + // This path can be absolute, or relative to your crate root. 17 + .set_icon("assets/icon.ico") 18 + .compile()?; 19 + } 20 + Ok(()) 21 + }
+4
clippy.sh
··· 1 + #!/bin/bash 2 + 3 + cargo +nightly clippy --all-features -Z unstable-options "$@" -- -D clippy::correctness -W clippy::style -W clippy::complexity -W clippy::perf -W clippy::nursery -W clippy::pedantic -W clippy::cargo -A clippy::too_many_lines 4 + # cargo +nightly clippy --all-features -Z unstable-options -- -D clippy::correctness -W clippy::style -W clippy::complexity -W clippy::perf -W clippy::nursery -W clippy::pedantic -W clippy::cargo -A clippy::too_many_lines
+14
haswell.Dockerfile
··· 1 + FROM rust:latest as builder 2 + WORKDIR /usr/src/rusted-fbt 3 + COPY . . 4 + RUN env RUSTFLAGS="-C target-cpu=haswell" cargo install --path . 5 + 6 + FROM debian:buster-slim 7 + # RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/* 8 + COPY --from=builder /usr/local/cargo/bin/rusted-fbt /usr/local/bin/rusted-fbt 9 + run apt update -y 10 + run apt install ca-certificates -y 11 + run apt update -y 12 + # This is the only dependancie missing as far as I can tell which is great 13 + run apt install libssl-dev -y 14 + CMD ["rusted-fbt"]
+1
slow build.ps1
··· 1 + $Env:RUSTFLAGS='-C target-cpu=native'; cargo build --profile=release-full-lto
+224
src/commands/_deprecated.rs
··· 1 + // https://sqlitebrowser.org/ 2 + /// Transfer from sqlite DB, get the json files using "File>Export>Table(s) to json" in sqlitebrowser 3 + #[cfg(feature = "database")] 4 + #[allow(non_snake_case, non_camel_case_types)] // Keeping these badly names variables since that's what they are called in the SQLite DB 5 + #[poise::command(slash_command, category = "Admin", owners_only, hide_in_help)] 6 + async fn sqlite_transfer( 7 + ctx: Context<'_>, 8 + #[description = "vrc_data.json"] vrc_data: Attachment, 9 + // #[description = "no_bot_perms.json"] no_bot_perms: Attachment, 10 + #[description = "guild_channels.json"] guild_channels: Attachment, 11 + #[description = "authorized_users.json"] authorized_users: Attachment, 12 + #[description = "Cleared_IDs.json"] cleard_ids: Attachment, 13 + #[description = "Monitored Guilds.json"] monitored_guilds: Attachment, 14 + ) -> Result<(), Error> { 15 + ctx.defer().await?; 16 + 17 + #[derive(Debug, Deserialize)] 18 + struct AuthorizedUser { 19 + user_id: u64, 20 + guild_id: u64, 21 + } 22 + 23 + #[derive(Debug, Deserialize)] 24 + struct ClearedID { 25 + Discord_ID: u64, 26 + Name: String, 27 + Where_found: String, 28 + Cleared_Reason: String, 29 + } 30 + 31 + #[derive(Debug, Deserialize)] 32 + struct MonitoredGuild { 33 + Guild_Name: String, 34 + Guild_ID: u64, 35 + Invite_link: Option<String>, 36 + Updated: Option<String>, 37 + DMCA_Takedown_Nuked: Option<String>, 38 + } 39 + 40 + #[derive(Debug, Deserialize)] 41 + struct GuildChannel { 42 + guild: u64, 43 + channel_id: u64, 44 + kick_active: u64, 45 + Name: String, 46 + } 47 + 48 + #[derive(Debug, Deserialize)] 49 + struct vrc_data { 50 + vrc_id: Option<String>, 51 + guild_id: Option<u64>, 52 + name: Option<String>, 53 + discord_id: u64, 54 + reason: String, 55 + image: Option<String>, 56 + extra: Option<String>, 57 + } 58 + 59 + let mut con = open_redis_connection().await?; 60 + let mut pipe = redis::pipe(); 61 + 62 + let msg = ctx.say("Downloading files").await?; 63 + 64 + let authorized_users_json = authorized_users.download().await?; 65 + let guild_channel_json = guild_channels.download().await?; 66 + let cleard_ids_json = cleard_ids.download().await?; 67 + let monitored_guilds_json = monitored_guilds.download().await?; 68 + let vrc_data_json = vrc_data.download().await?; 69 + 70 + msg.edit(ctx, |b| b.content("Converting json to structs")) 71 + .await?; 72 + 73 + let auth_user_vec: Vec<AuthorizedUser> = 74 + serde_json::from_str(std::str::from_utf8(&authorized_users_json)?)?; 75 + let guild_channel_vec: Vec<GuildChannel> = 76 + serde_json::from_str(std::str::from_utf8(&guild_channel_json)?)?; 77 + let cleard_id_vec: Vec<ClearedID> = 78 + serde_json::from_str(std::str::from_utf8(&cleard_ids_json)?)?; 79 + let monitored_guilds_vec: Vec<MonitoredGuild> = 80 + serde_json::from_str(std::str::from_utf8(&monitored_guilds_json)?)?; 81 + let vrc_data_vec: Vec<vrc_data> = serde_json::from_str(std::str::from_utf8(&vrc_data_json)?)?; 82 + 83 + msg.edit(ctx, |b| b.content("Preparing authorized_users data")) 84 + .await?; 85 + 86 + for authed_user in auth_user_vec { 87 + pipe.cmd("SADD").arg(&[ 88 + format!("authed-server-users:{}", authed_user.guild_id), 89 + format!("{}", authed_user.user_id), 90 + ]); 91 + } 92 + 93 + msg.edit(ctx, |b| b.content("Preparing guild_channels data")) 94 + .await?; 95 + 96 + for guild_settings in guild_channel_vec { 97 + let formatted = GuildSettings { 98 + channel_id: format!("{}", guild_settings.channel_id), 99 + kick: match guild_settings.kick_active { 100 + 1 => true, 101 + 0 => false, 102 + _ => false, 103 + }, 104 + server_name: guild_settings.Name, 105 + }; 106 + 107 + let json = serde_json::to_string(&formatted).unwrap(); 108 + pipe.cmd("JSON.SET").arg(&[ 109 + format!("guild-settings:{}", guild_settings.guild), 110 + "$".to_string(), 111 + json, 112 + ]); 113 + } 114 + 115 + msg.edit(ctx, |b| b.content("Preparing cleard_ids data")) 116 + .await?; 117 + 118 + for cleared_id in cleard_id_vec { 119 + let formatted: ClearedUser = ClearedUser { 120 + user_id: format!("{}", cleared_id.Discord_ID), 121 + username: cleared_id.Name, 122 + where_found: cleared_id.Where_found, 123 + reason: cleared_id.Cleared_Reason, 124 + }; 125 + 126 + let json = serde_json::to_string(&formatted).unwrap(); 127 + pipe.cmd("JSON.SET").arg(&[ 128 + format!("cleared-user:{}", cleared_id.Discord_ID), 129 + "$".to_string(), 130 + json, 131 + ]); 132 + } 133 + 134 + msg.edit(ctx, |b| b.content("Preparing monitored_guilds data")) 135 + .await?; 136 + 137 + for guild in monitored_guilds_vec { 138 + let formatted: MonitoredGuildInfo = MonitoredGuildInfo { 139 + guild_name: guild.Guild_Name.to_string(), 140 + guild_id: format!("{}", guild.Guild_ID), 141 + invite_link: match guild.Invite_link { 142 + None => "N/A".to_string(), 143 + Some(link) => link.to_string(), 144 + }, 145 + updated: match guild.Updated { 146 + None => "Never".to_string(), 147 + Some(date) => date.to_string(), 148 + }, 149 + status: match guild.DMCA_Takedown_Nuked { 150 + None => "Unknown".to_string(), 151 + Some(status) => status.to_string(), 152 + }, 153 + }; 154 + 155 + let json = serde_json::to_string(&formatted).unwrap(); 156 + pipe.cmd("JSON.SET").arg(&[ 157 + format!("monitored-guild:{}", guild.Guild_ID), 158 + "$".to_string(), 159 + json, 160 + ]); 161 + } 162 + 163 + msg.edit(ctx, |b| b.content("Preparing vrc_data")).await?; 164 + 165 + let mut parsed_ids: HashSet<String> = HashSet::new(); 166 + 167 + for user_data in vrc_data_vec { 168 + match parsed_ids.contains(&format!("{}", user_data.discord_id)) { 169 + false => { 170 + let mut new_user = UserInfo { 171 + vrc_id: user_data.vrc_id, 172 + username: user_data.name, 173 + discord_id: Some(format!("{}", user_data.discord_id)), 174 + offences: Vec::new(), 175 + }; 176 + 177 + let offense = vec![Offense { 178 + guild_id: match user_data.guild_id { 179 + None => "N/A".to_string(), 180 + Some(gid) => format!("{}", gid), 181 + }, 182 + reason: user_data.reason, 183 + image: user_data.image, 184 + extra: user_data.extra, 185 + }]; 186 + new_user.offences = offense; 187 + 188 + let json = serde_json::to_string(&new_user).unwrap(); 189 + pipe.cmd("JSON.SET").arg(&[ 190 + format!("user:{}", user_data.discord_id), 191 + "$".to_string(), 192 + json, 193 + ]); 194 + 195 + parsed_ids.insert(format!("{}", user_data.discord_id)); 196 + } 197 + true => { 198 + let offense = Offense { 199 + guild_id: match user_data.guild_id { 200 + None => "N/A".to_string(), 201 + Some(gid) => format!("{}", gid), 202 + }, 203 + reason: user_data.reason, 204 + image: user_data.image, 205 + extra: user_data.extra, 206 + }; 207 + 208 + let json = serde_json::to_string(&offense).unwrap(); 209 + pipe.cmd("JSON.ARRAPPEND") 210 + .arg(format!("user:{}", user_data.discord_id)) 211 + .arg("$.offences".to_string()) 212 + .arg(json); 213 + } 214 + } 215 + } 216 + 217 + msg.edit(ctx, |b| b.content("Uploading data to DB")).await?; 218 + 219 + pipe.query_async(&mut con).await?; 220 + 221 + msg.edit(ctx, |b| b.content("All done!")).await?; 222 + 223 + Ok(()) 224 + }
+516
src/commands/admin.rs
··· 1 + use clap::Parser; 2 + use core::time; 3 + use poise::serenity_prelude::Attachment; 4 + use poise::serenity_prelude::{self as serenity, Activity, Member, OnlineStatus}; 5 + use poise::serenity_prelude::{ChannelId, Colour}; 6 + use rand::seq::SliceRandom; 7 + use rand::Rng; 8 + use rusted_fbt_lib::checks::guild_auth_check; 9 + use rusted_fbt_lib::structs::GuildSettings; 10 + use rusted_fbt_lib::utils::{auth, open_redis_connection, set_guild_settings}; 11 + use rusted_fbt_lib::{ 12 + args::Args, 13 + checks::bot_admin_check, 14 + types::{Context, Error}, 15 + utils::{inc_execution_count, verbose_mode}, 16 + }; 17 + use std::collections::HashSet; 18 + use std::ops::Add; 19 + use std::process::exit; 20 + use tokio::time::sleep; 21 + use tracing::instrument; 22 + use tracing::{event, Level}; 23 + 24 + #[instrument(skip(ctx))] 25 + #[poise::command( 26 + slash_command, 27 + category = "Admin", 28 + check = "bot_admin_check", 29 + hide_in_help 30 + )] 31 + /// Sends message to specified user ID 32 + pub async fn botmsg( 33 + ctx: Context<'_>, 34 + #[description = "User ID"] user: serenity::User, 35 + #[description = "Message"] msg: String, 36 + ) -> Result<(), Error> { 37 + ctx.defer().await?; 38 + 39 + user.direct_message(ctx, |f| f.content(&msg)).await?; 40 + 41 + ctx.say(format!("Sent message to: {}", user.name)).await?; 42 + ctx.say(format!("Message: {}", &msg)).await?; 43 + 44 + Ok(()) 45 + } 46 + 47 + #[instrument(skip(ctx))] 48 + #[poise::command( 49 + prefix_command, 50 + slash_command, 51 + category = "Admin", 52 + required_permissions = "BAN_MEMBERS", 53 + required_bot_permissions = "BAN_MEMBERS", 54 + guild_only, 55 + ephemeral 56 + )] 57 + /// Explains how to ban a list of users with `ban 58 + pub async fn ban_help(ctx: Context<'_>) -> Result<(), Error> { 59 + let args = Args::parse(); 60 + 61 + ctx.say("To ban a single user the easiest way is with the slash command `/ban ban_user @USER/ID` since you don't need to provide message deletion numbers or a reason.").await?; 62 + ctx.say(format!("In order to ban multiple people please use this command as a a prefix command like so:\n```\n{}ban ban_user \"Reason in qutoation marks\" 0(A number from 0 to 7, how many days worth of messages you want to delet) userID1 userID2 userID3\n```", args.prefix)).await?; 63 + 64 + Ok(()) 65 + } 66 + 67 + #[instrument(skip(ctx))] 68 + #[poise::command( 69 + prefix_command, 70 + slash_command, 71 + category = "Admin", 72 + required_permissions = "BAN_MEMBERS", 73 + required_bot_permissions = "BAN_MEMBERS", 74 + guild_only, 75 + ephemeral 76 + )] 77 + /// Explains how to ban a list of users with `ban 78 + pub async fn ban_user( 79 + ctx: Context<'_>, 80 + #[description = "Ban reason"] reason: Option<String>, 81 + #[description = "How many days of messages to purge (Max of 7)"] 82 + #[min = 0] 83 + #[max = 7] 84 + dmd: Option<u8>, 85 + #[description = "Member(s) to ban"] members: Vec<Member>, 86 + ) -> Result<(), Error> { 87 + let delete_count: u8 = dmd.map_or(0u8, |num| { 88 + let num_check = num; 89 + if num_check.le(&7u8) { 90 + num 91 + } else { 92 + 7u8 93 + } 94 + }); 95 + 96 + let reason_sanitised = reason.map_or_else(|| "Banned via bot ban command".to_string(), |r| r); 97 + 98 + // TODO: Change to your own emojis! 99 + 100 + // Mojo test server emoji version 101 + // let phrase_list = vec!["has been ejected", "banned quietly", "terminated", "thrown out", "<a:Banned1:1000474420864880831><a:Banned2:1000474423683452998><a:Banned3:1000474426447503441>"]; 102 + 103 + // FBT Emoji version 104 + let phrase_list = ["has been ejected", "banned quietly", "terminated", "thrown out", "<a:Banned1:1000474106929631433><a:Banned2:1000474109802725457><a:Banned3:1000474112734531715>"]; 105 + 106 + match ctx.guild() { 107 + Some(guild) => match members.len() { 108 + 0 => { 109 + let args = Args::parse(); 110 + 111 + ctx.say("You must provide at least one user to ban!") 112 + .await?; 113 + ctx.say(format!("In order to ban multiple people please use this command as a a prefix command like so:\n```\n{}ban userID1 userID2 userID3\n```", args.prefix)).await?; 114 + } 115 + 1 => { 116 + let member = guild.member(ctx, members[0].user.id).await?; 117 + if let Err(error) = member 118 + .ban_with_reason(ctx, delete_count, reason_sanitised.clone()) 119 + .await 120 + { 121 + if verbose_mode() { 122 + ctx.say(format!( 123 + "Failed to ban {} because of {:?}", 124 + member.display_name(), 125 + error 126 + )) 127 + .await?; 128 + } else { 129 + ctx.say(format!("Failed to ban {}", member.display_name())) 130 + .await?; 131 + } 132 + // 0u8 133 + } else { 134 + let phrase = phrase_list 135 + .choose(&mut rand::thread_rng()) 136 + .expect("Unable to get meme phrase for ban"); 137 + ctx.say(format!("{} has been {phrase}", member.display_name())) 138 + .await?; 139 + // 0u8 140 + }; 141 + } 142 + _ => { 143 + for member in members { 144 + if let Err(error) = member 145 + .ban_with_reason(ctx, delete_count, reason_sanitised.clone()) 146 + .await 147 + { 148 + if verbose_mode() { 149 + ctx.say(format!( 150 + "Failed to ban {} because of {:?}", 151 + member.display_name(), 152 + error 153 + )) 154 + .await?; 155 + } else { 156 + ctx.say(format!("Failed to ban {}", member.display_name())) 157 + .await?; 158 + } 159 + // 0u8 160 + } else { 161 + let phrase = phrase_list 162 + .choose(&mut rand::thread_rng()) 163 + .expect("Unable to get meme phrase for ban"); 164 + ctx.say(format!("{} has been {phrase}", member.display_name())) 165 + .await?; 166 + // 0u8 167 + }; 168 + } 169 + } 170 + }, 171 + None => { 172 + ctx.say("This must be ran from inside a guild").await?; 173 + } 174 + } 175 + Ok(()) 176 + } 177 + 178 + #[instrument(skip(ctx))] 179 + #[poise::command( 180 + prefix_command, 181 + slash_command, 182 + category = "Admin", 183 + required_permissions = "BAN_MEMBERS", 184 + required_bot_permissions = "BAN_MEMBERS", 185 + guild_only, 186 + subcommands("ban_help", "ban_user") 187 + )] 188 + /// Ban a member or list of members by ID or Mention 189 + pub async fn ban(ctx: Context<'_>) -> Result<(), Error> { 190 + ctx.say("Run `/ban ban_help` to learn how to use this command and then use ") 191 + .await?; 192 + 193 + Ok(()) 194 + } 195 + 196 + // TODO: Change to your own emojis! 197 + #[instrument(skip(ctx))] 198 + #[poise::command(prefix_command, slash_command, category = "Admin", owners_only)] 199 + /// Literally just shoot the bot! 200 + pub async fn shutdown(ctx: Context<'_>) -> Result<(), Error> { 201 + let pewpew = ctx 202 + .say("<:GunPoint:908506214915276851> <:FBT:795660945627676712>") 203 + .await?; 204 + sleep(time::Duration::from_secs(1)).await; 205 + pewpew 206 + .edit(ctx, |b| { 207 + b.content("<:GunPoint:908506214915276851> 💥 <:FBT:795660945627676712>") 208 + }) 209 + .await?; 210 + sleep(time::Duration::from_secs(1)).await; 211 + pewpew 212 + .edit(ctx, |b| { 213 + b.content("<:GunPoint:908506214915276851> <:FBT:795660945627676712>") 214 + }) 215 + .await?; 216 + sleep(time::Duration::from_secs(1)).await; 217 + pewpew 218 + .edit(ctx, |b| { 219 + b.content("<:GunPoint:908506214915276851> 🩸 <:FBT:795660945627676712> 🩸") 220 + }) 221 + .await?; 222 + sleep(time::Duration::from_secs(1)).await; 223 + ctx.say("Exiting now!").await?; 224 + 225 + #[cfg(feature = "database")] 226 + inc_execution_count().await?; 227 + 228 + let activity = Activity::playing("Sleeping"); 229 + let status = OnlineStatus::Offline; 230 + 231 + ctx.serenity_context() 232 + .set_presence(Some(activity), status) 233 + .await; 234 + 235 + exit(0) 236 + } 237 + 238 + #[cfg(feature = "database")] 239 + /// Authorize someone in this guild 240 + #[instrument(skip(ctx))] 241 + #[poise::command( 242 + prefix_command, 243 + slash_command, 244 + category = "Admin", 245 + check = "guild_auth_check", 246 + guild_only 247 + )] 248 + pub async fn authorize( 249 + ctx: Context<'_>, 250 + #[description = "User to authorise in this server"] user: Member, 251 + ) -> Result<(), Error> { 252 + ctx.defer_or_broadcast().await?; 253 + 254 + let uid = format!("{}", user.user.id.as_u64()); 255 + 256 + let mut con = open_redis_connection().await?; 257 + 258 + // * json format: {users:[ID1, ID2, IDect]} 259 + let key_list: Option<HashSet<String>> = redis::cmd("SMEMBERS") 260 + .arg(format!( 261 + "authed-server-users:{}", 262 + ctx.guild_id().unwrap().as_u64() 263 + )) 264 + .clone() 265 + .query_async(&mut con) 266 + .await?; 267 + 268 + if let Some(list) = key_list { 269 + if list.contains(&uid) { 270 + ctx.say("User already authorised in this server!").await?; 271 + } else { 272 + match auth(ctx, &mut con, uid).await { 273 + Ok(()) => { 274 + ctx.say(format!( 275 + "{} is now authorized to use commands in this server!", 276 + user.display_name() 277 + )) 278 + .await?; 279 + } 280 + Err(_error) if !verbose_mode() => { 281 + ctx.say(format!("Failed to auth {}!", user.display_name())) 282 + .await?; 283 + } 284 + Err(error) => { 285 + ctx.say(format!( 286 + "Failed to auth {}! Caused by {:?}", 287 + user.display_name(), 288 + error 289 + )) 290 + .await?; 291 + } 292 + } 293 + } 294 + } else { 295 + auth(ctx, &mut con, uid).await?; 296 + 297 + ctx.say(format!( 298 + "{} is now authorized to use commands in this server!", 299 + user.display_name() 300 + )) 301 + .await?; 302 + } 303 + 304 + Ok(()) 305 + } 306 + 307 + #[cfg(feature = "database")] 308 + #[instrument(skip(ctx))] 309 + #[poise::command( 310 + prefix_command, 311 + slash_command, 312 + category = "Admin", 313 + check = "bot_admin_check" 314 + )] 315 + /// Send annoucement to any server that has been setup 316 + pub async fn announcement( 317 + ctx: Context<'_>, 318 + #[description = "Title of announcement embed"] title: String, 319 + #[description = "Message to send to all servers (As a .txt file!)"] message_file: Attachment, 320 + ) -> Result<(), Error> { 321 + ctx.defer_or_broadcast().await?; 322 + 323 + let message_content = message_file.download().await?; 324 + let message = std::str::from_utf8(&message_content)?; 325 + 326 + let mut con = open_redis_connection().await?; 327 + 328 + let key_list: Vec<String> = redis::cmd("KEYS") 329 + .arg("guild-settings:*") 330 + .clone() 331 + .query_async(&mut con) 332 + .await?; 333 + 334 + let mut key_pipe = redis::pipe(); 335 + 336 + for key in key_list { 337 + key_pipe.cmd("JSON.GET").arg(key); 338 + } 339 + 340 + let setting_entries: Vec<String> = key_pipe.atomic().query_async(&mut con).await?; 341 + 342 + let mut guild_settings_collection = Vec::new(); 343 + for settings in setting_entries { 344 + let gs: GuildSettings = serde_json::from_str(&settings)?; 345 + 346 + guild_settings_collection.push(gs); 347 + } 348 + 349 + // TODO: Change to custom announcement message! 350 + 351 + let mut count: u64 = 0; 352 + for guild in guild_settings_collection.clone() { 353 + let colour = &mut rand::thread_rng().gen_range(0..10_000_000); 354 + match ChannelId(guild.channel_id.parse::<u64>()?).send_message(ctx, |f| { 355 + f.embed(|e| { 356 + e.title(format!("New announcement from FBT Security: {}", title.clone())) 357 + .description(message) 358 + .color(Colour::new(*colour)) 359 + .author(|a| { 360 + a.icon_url("https://cdn.discordapp.com/avatars/743269383438073856/959512463b1559b14818590d8c8a9d2a.webp?size=4096") 361 + .name("FBT Security") 362 + }) 363 + .thumbnail("https://media.giphy.com/media/U4sfHXAALLYBQzPcWk/giphy.gif") 364 + }) 365 + }).await { 366 + Err(e)=>{ 367 + event!( 368 + Level::INFO, 369 + "Failed to send announcement to a server because of" = ?e 370 + ); 371 + }, 372 + Ok(msg) => { 373 + count = count.add(1); 374 + println!("Sent to: {}", msg.link()); 375 + }, 376 + }; 377 + } 378 + 379 + ctx.say(format!( 380 + "Sent annoucement to {}/{} servers!", 381 + count, 382 + guild_settings_collection.len() 383 + )) 384 + .await?; 385 + 386 + Ok(()) 387 + } 388 + 389 + #[cfg(feature = "database")] 390 + #[instrument(skip(ctx))] 391 + #[poise::command( 392 + prefix_command, 393 + slash_command, 394 + category = "Admin", 395 + required_permissions = "ADMINISTRATOR", 396 + guild_only 397 + )] 398 + /// Request an FBT staff member to come and auth your server 399 + pub async fn request_setup( 400 + ctx: Context<'_>, 401 + #[description = "Do you want to kick accounts that are under 90 days old"] alt_protection: bool, 402 + ) -> Result<(), Error> { 403 + ctx.defer().await?; 404 + let link = ChannelId(*ctx.channel_id().as_u64()) 405 + .create_invite(ctx, |f| f.temporary(false).max_age(0).unique(false)) 406 + .await?; 407 + 408 + 409 + // TODO: this channel is where the bot alterts you when a server is requesting use of the bot's moderation stuff 410 + ChannelId(953_435_498_318_286_898).send_message(ctx, |f| { 411 + f.content(format!("{0} is requesting authentication! {1}\n They requested for alt protection to be: `{alt_protection}`", ctx.guild().unwrap().name, link.url())) 412 + }).await?; 413 + 414 + ctx.send(|b| b.content("Request sent, sit tight!\nOnce an administrator joins make sure to give them permissions to acess the channel so they can set it up!").ephemeral(true)).await?; 415 + 416 + Ok(()) 417 + } 418 + 419 + #[cfg(feature = "database")] 420 + #[instrument(skip(ctx))] 421 + #[poise::command( 422 + slash_command, 423 + category = "Admin", 424 + check = "guild_auth_check", 425 + guild_only 426 + )] 427 + /// Setup your server's settings 428 + pub async fn setup( 429 + ctx: Context<'_>, 430 + #[description = "Do you want to kick accounts that are under 90 days old when they join"] 431 + alt_protection: bool, 432 + ) -> Result<(), Error> { 433 + let mut con = open_redis_connection().await?; 434 + 435 + let guild_settings_json_in: Option<String> = redis::cmd("JSON.GET") 436 + .arg(format!( 437 + "guild-settings:{}", 438 + ctx.guild_id().unwrap().as_u64() 439 + )) 440 + .clone() 441 + .query_async(&mut con) 442 + .await?; 443 + 444 + let ch_id = format!("{}", ctx.channel_id().as_u64()); 445 + let g_name = ctx 446 + .partial_guild() 447 + .await 448 + .expect("Unable to get Guild info") 449 + .name; 450 + 451 + if let Some(json_in) = guild_settings_json_in { 452 + let mut settings: GuildSettings = serde_json::from_str(&json_in)?; 453 + settings.channel_id = ch_id.clone(); 454 + settings.kick = alt_protection; 455 + settings.server_name = g_name; 456 + 457 + set_guild_settings(ctx, &mut con, settings).await?; 458 + ctx.say(format!("Settings have been updated for your server!\nChannel for kick messages and bot announcements: <#{0}>.\nAlt protection: {alt_protection:?}.", ch_id.clone())).await?; 459 + } else { 460 + let settings = GuildSettings { 461 + channel_id: ch_id.clone(), 462 + kick: alt_protection, 463 + server_name: g_name, 464 + }; 465 + 466 + set_guild_settings(ctx, &mut con, settings).await?; 467 + ctx.say(format!("Settings have been created for your server!\nChannel for kick messages and bot announcements: <#{0}>.\nAlt protection: {alt_protection:?}.", ch_id.clone())).await?; 468 + } 469 + 470 + Ok(()) 471 + } 472 + 473 + #[cfg(feature = "database")] 474 + #[instrument(skip(ctx))] 475 + #[poise::command( 476 + prefix_command, 477 + slash_command, 478 + category = "Admin", 479 + check = "guild_auth_check", 480 + guild_only 481 + )] 482 + /// Set your server's alt protection policy 483 + pub async fn toggle_kick(ctx: Context<'_>) -> Result<(), Error> { 484 + let mut con = open_redis_connection().await?; 485 + 486 + let guild_settings_json_in: Option<String> = redis::cmd("JSON.GET") 487 + .arg(format!( 488 + "guild-settings:{}", 489 + ctx.guild_id().unwrap().as_u64() 490 + )) 491 + .clone() 492 + .query_async(&mut con) 493 + .await?; 494 + 495 + match guild_settings_json_in { 496 + // Update settings 497 + Some(json_in) => { 498 + let mut settings: GuildSettings = serde_json::from_str(&json_in)?; 499 + settings.kick = !settings.kick; 500 + 501 + set_guild_settings(ctx, &mut con, settings.clone()).await?; 502 + ctx.say(format!( 503 + "Settings have been updated for your server!\nAlt protection: {:?}.", 504 + settings.kick 505 + )) 506 + .await?; 507 + } 508 + // TODO: change to custom message 509 + // This should not be able to trigger because of the auth check but better safe than sorry 510 + None => { 511 + ctx.say("Your server has not been setup by a bot admin yet! Please context a bot admin or azuki to get authorised.").await?; 512 + } 513 + } 514 + 515 + Ok(()) 516 + }
+1216
src/commands/database.rs
··· 1 + use meilisearch_sdk::client::Client; 2 + use merge::Merge; 3 + use poise::serenity_prelude::Attachment; 4 + use poise::serenity_prelude::{colours, AttachmentType, Colour, Member, UserId}; 5 + use rand::Rng; 6 + use rusted_fbt_lib::checks::guild_auth_check; 7 + use rusted_fbt_lib::structs::{BlacklistHit, CsvEntry, Offense, UserInfo}; 8 + use rusted_fbt_lib::utils::{open_redis_connection, verbose_mode}; 9 + use rusted_fbt_lib::vars::BlacklistOutput; 10 + use rusted_fbt_lib::vars::{BOT_ADMINS, BOT_IDS, MEILISEARCH_API_KEY, MEILISEARCH_HOST}; 11 + use rusted_fbt_lib::{ 12 + checks::bot_admin_check, 13 + types::{Context, Error}, 14 + }; 15 + use std::collections::HashSet; 16 + use std::ops::{Add, Mul}; 17 + use tracing::instrument; 18 + use tracing::{event, Level}; 19 + 20 + #[cfg(feature = "database")] 21 + #[instrument(skip(ctx))] 22 + #[poise::command( 23 + prefix_command, 24 + slash_command, 25 + category = "DB", 26 + member_cooldown = 1, 27 + required_permissions = "BAN_MEMBERS", 28 + check = "guild_auth_check", 29 + guild_only 30 + )] 31 + /// Add an ID to the bot 32 + pub async fn add( 33 + ctx: Context<'_>, 34 + #[description = "Person to add to DB. Must be a user ID."] id: String, 35 + #[description = "Reason for being added to the DB."] reason: String, 36 + #[description = "Server ID this took place in. (Leave blank to use your own server ID)"] 37 + guild_id: Option<String>, 38 + #[description = "ID or URL to VRChat account."] vrc_id: Option<String>, 39 + #[description = "Link to image or google drive folder of images."] image: Option<String>, 40 + #[description = "Anything extra you want to add."] extra: Option<String>, 41 + ) -> Result<(), Error> { 42 + ctx.defer().await?; 43 + 44 + let mut is_not_user = false; 45 + 46 + let uid = id.trim().parse::<u64>().map_or_else( 47 + |_| { 48 + is_not_user = true; 49 + 0 50 + }, 51 + |i| i, 52 + ); 53 + 54 + if is_not_user { 55 + ctx.send(|b| { 56 + b.content("Make sure you supplied a user ID") 57 + .ephemeral(true) 58 + }) 59 + .await?; 60 + } else { 61 + let gid: String = guild_id.map_or_else(|| ctx.guild_id().unwrap().to_string(), |url| url); 62 + 63 + let mut con = open_redis_connection().await?; 64 + 65 + let result: Option<String> = redis::cmd("JSON.GET") 66 + .arg(format!("user:{uid}")) 67 + .clone() 68 + .query_async(&mut con) 69 + .await?; 70 + 71 + let uname = (UserId::from(uid).to_user(ctx).await) 72 + .map_or_else(|_| "NoUsernameFoundInDB".to_string(), |u| u.tag()); 73 + 74 + match result { 75 + None => { 76 + let mut new: UserInfo = UserInfo { 77 + vrc_id: None, 78 + username: Some(uname), 79 + discord_id: Some(format!("{uid}")), 80 + offences: Vec::new(), 81 + }; 82 + 83 + let mut new_offense = Offense { 84 + guild_id: gid, 85 + reason: reason.clone(), 86 + image: image.clone().or_else(|| Some("N/A".to_string())), 87 + extra: extra.clone().or_else(|| Some("N/A".to_string())), 88 + }; 89 + 90 + match vrc_id { 91 + None => { 92 + new.vrc_id = Some("N/A".to_string()); 93 + } 94 + Some(url) => { 95 + new.vrc_id = Some(url); 96 + } 97 + } 98 + 99 + match image { 100 + None => { 101 + new_offense.image = Some("N/A".to_string()); 102 + } 103 + Some(url) => { 104 + new_offense.image = Some(url); 105 + } 106 + } 107 + 108 + match extra { 109 + None => { 110 + new_offense.extra = Some("N/A".to_string()); 111 + } 112 + Some(url) => { 113 + new_offense.extra = Some(url); 114 + } 115 + } 116 + 117 + new.offences.push(new_offense); 118 + 119 + let json_user = serde_json::to_string(&new).unwrap(); 120 + 121 + redis::cmd("JSON.SET") 122 + .arg(format!("user:{uid}")) 123 + .arg("$".to_string()) 124 + .arg(json_user) 125 + .clone() 126 + .query_async(&mut con) 127 + .await?; 128 + } 129 + Some(_) => { 130 + let mut new_offense = Offense { 131 + guild_id: gid, 132 + reason: reason.clone(), 133 + image: image.clone().or_else(|| Some("N/A".to_string())), 134 + extra: extra.clone().or_else(|| Some("N/A".to_string())), 135 + }; 136 + 137 + match image { 138 + None => { 139 + new_offense.image = Some("N/A".to_string()); 140 + } 141 + Some(url) => { 142 + new_offense.image = Some(url); 143 + } 144 + } 145 + 146 + match extra { 147 + None => { 148 + new_offense.extra = Some("N/A".to_string()); 149 + } 150 + Some(url) => { 151 + new_offense.extra = Some(url); 152 + } 153 + } 154 + 155 + let json_offense = serde_json::to_string(&new_offense).unwrap(); 156 + 157 + redis::cmd("JSON.ARRAPPEND") 158 + .arg(format!("user:{uid}")) 159 + .arg("$.offences".to_string()) 160 + .arg(json_offense) 161 + .clone() 162 + .query_async(&mut con) 163 + .await?; 164 + } 165 + } 166 + 167 + ctx.say(format!("<@{uid}> added into DB!\nReason: {reason}")) 168 + .await?; 169 + } 170 + 171 + Ok(()) 172 + } 173 + 174 + // TODO: Change into sub command when we add vrc DB back 175 + /// Search DB for yourself or with ID 176 + #[cfg(feature = "database")] 177 + #[instrument(skip(ctx))] 178 + #[poise::command( 179 + prefix_command, 180 + slash_command, 181 + category = "DB", 182 + member_cooldown = 5, 183 + check = "guild_auth_check", 184 + guild_only 185 + )] 186 + pub async fn search( 187 + ctx: Context<'_>, 188 + #[description = "Member to search for. This must be a user ID."] user_id: String, 189 + ) -> Result<(), Error> { 190 + use pastemyst::paste::*; 191 + use pastemyst::str; 192 + use poise::serenity_prelude::User; 193 + 194 + ctx.defer().await?; 195 + 196 + // let u = id.user.clone(); 197 + 198 + let uid = user_id.trim().parse::<u64>()?; 199 + 200 + let u_opt: Option<User> = match UserId::from(uid).to_user(ctx).await { 201 + Ok(user) => Some(user), 202 + Err(error) => { 203 + if verbose_mode() { 204 + ctx.say(format!( 205 + "ID must be a user ID, make sure you coppied the right one! Error: {:?}", 206 + error 207 + )) 208 + .await?; 209 + } else { 210 + ctx.say("ID must be a user ID, make sure you coppied the right one!") 211 + .await?; 212 + } 213 + 214 + None 215 + } 216 + }; 217 + 218 + if let Some(u) = u_opt { 219 + let result = check_username_against_db(uid).await?; 220 + 221 + match result { 222 + None => { 223 + ctx.say(format!("No result found for {uid}.")).await?; 224 + } 225 + Some(hit) => { 226 + let hit_string: String = hit; 227 + let user: UserInfo = serde_json::from_str(hit_string.as_str())?; 228 + 229 + let username = user.username.unwrap_or_else(|| u.tag()); 230 + let id = user.discord_id.unwrap_or(format!("{}", uid.clone())); 231 + let vrc = user.vrc_id.unwrap_or_else(|| "N/A".to_string()); 232 + 233 + let field_count = user.offences.len() as u64; 234 + 235 + if field_count.mul(7).add(4).ge(&25_u64) { 236 + let mut offences = String::new(); 237 + 238 + let mut i: u64 = 0; 239 + for hit in user.offences.clone() { 240 + i = i.add(1); 241 + 242 + let image = hit.image.unwrap_or_else(|| "N/A".to_string()); 243 + let extra = hit.extra.unwrap_or_else(|| "N/A".to_string()); 244 + offences.push_str(format!("\n\nOffence {i}:\n Guild ID: {0}\n Reason: {1}\n Image(s): {image}\n Extra info: {extra}", hit.guild_id, hit.reason).as_str()); 245 + } 246 + 247 + let msg_start = format!( 248 + "Result found!\nUser has {} hit(s).\nUsername logged in DB: {}\nCurrent username: {}\nUser ID: {}\nVRChat ID: {}", 249 + user.offences.clone().len() as u64, 250 + username, 251 + u.tag(), 252 + id, 253 + vrc 254 + ); 255 + 256 + if (offences.chars().count() + msg_start.chars().count()) < 2000 { 257 + // for format! merges msg_start and offences 258 + ctx.say(format!("{msg_start}```{offences}```")).await?; 259 + } else { 260 + let txt_contents = format!("{msg_start}\n\n{offences}"); 261 + ctx.send(|f| { 262 + f.content("Result found!\nThis user has so many hits that we can't display them all in Discord.\nBellow is a `.txt` file with all of their offences.") 263 + .ephemeral(false) 264 + .attachment(AttachmentType::Bytes { 265 + data: std::borrow::Cow::Borrowed(txt_contents.as_bytes()), 266 + filename: format!("{id}.txt"), 267 + }) 268 + }) 269 + .await?; 270 + } 271 + } else { 272 + let mut fields: Vec<(String, String, bool)> = vec![ 273 + ("Username logged in DB:".to_string(), username.clone(), true), 274 + ("Current username:".to_string(), u.tag(), true), 275 + ("User ID:".to_string(), id.clone(), true), 276 + ("VRChat ID:".to_string(), vrc.clone(), true), 277 + ]; 278 + 279 + let mut i: u64 = 0; 280 + 281 + // let mut buffer_len: usize = 0; 282 + 283 + // for offense in user.offences.clone() { 284 + // buffer_len += offense.reason.chars().into_iter().count(); 285 + // } 286 + 287 + for offense in user.offences.clone() { 288 + i = i.add(1); 289 + fields.push(("Offense:".to_string(), format!("#{i}"), false)); 290 + 291 + let image = offense.image.unwrap_or_else(|| "N/A".to_string()); 292 + let extra = offense.extra.unwrap_or_else(|| "N/A".to_string()); 293 + 294 + fields.push(("Guild ID:".to_string(), offense.guild_id, true)); 295 + fields.push(( 296 + "Reason:".to_string(), 297 + { 298 + if offense.reason.chars().into_iter().count() > 1000 { 299 + let pasties: Vec<PastyObject> = vec![PastyObject { 300 + _id: str!(""), 301 + language: str!(pastemyst::data::language::MARKDOWN), 302 + title: format!("{}'s long offence", username.clone()), 303 + code: offense.reason, 304 + }]; 305 + 306 + let data: CreateObject = CreateObject { 307 + title: format!("{}'s long offence", username.clone()), 308 + expiresIn: String::from("1d"), 309 + isPrivate: false, 310 + isPublic: false, 311 + tags: String::from(""), 312 + pasties: pasties, 313 + }; 314 + 315 + let paste = create_paste(data)?; 316 + 317 + format!("https://paste.myst.rs/{}", paste._id) 318 + } else { 319 + offense.reason 320 + } 321 + }, 322 + true, 323 + )); 324 + fields.push(("Image(s):".to_string(), image, true)); 325 + fields.push(("Extra info:".to_string(), extra, true)); 326 + } 327 + 328 + let colour = &mut rand::thread_rng().gen_range(0..10_000_000); 329 + 330 + let msg = ctx.send(|b| { 331 + b.embed(|e| { 332 + e.title("Result found!") 333 + .description(format!( 334 + "User has {} hit(s).", 335 + user.offences.clone().len() as u64 336 + )) 337 + .fields(fields) 338 + .color(Colour::new(*colour)) 339 + .thumbnail(u.avatar_url().unwrap_or_else(|| "https://discord.com/assets/1f0bfc0865d324c2587920a7d80c609b.png".to_string())) 340 + }) 341 + }) 342 + .await; 343 + if msg.is_err() { 344 + let msg_start = format!( 345 + "Result found!\nUser has {} hit(s).\nUsername logged in DB: {}\nCurrent username: {}\nUser ID: {}\nVRChat ID: {}", 346 + user.offences.clone().len() as u64, 347 + username, 348 + u.tag(), 349 + id, 350 + vrc 351 + ); 352 + 353 + let mut offences = String::new(); 354 + 355 + let mut i: u64 = 0; 356 + for hit in user.offences.clone() { 357 + i = i.add(1); 358 + 359 + let image = hit.image.unwrap_or_else(|| "N/A".to_string()); 360 + let extra = hit.extra.unwrap_or_else(|| "N/A".to_string()); 361 + offences.push_str(format!("\n\nOffence {i}:\n Guild ID: {0}\n Reason: {1}\n Image(s): {image}\n Extra info: {extra}", hit.guild_id, hit.reason).as_str()); 362 + } 363 + 364 + let txt_contents = format!("{msg_start}\n\n{offences}"); 365 + ctx.send(|f| { 366 + f.content("Result found!\nThere was some kinda of error sending the fancy version, most likely one of the field (for example the images) was too long.\nBellow is a `.txt` file with all of their offences formated as nicely as I could make it.") 367 + .ephemeral(false) 368 + .attachment(AttachmentType::Bytes { 369 + data: std::borrow::Cow::Borrowed(txt_contents.as_bytes()), 370 + filename: format!("{username}_{id}.txt"), 371 + }) 372 + }) 373 + .await?; 374 + } 375 + } 376 + } 377 + } 378 + } 379 + 380 + let unix_timecode = rusted_fbt_lib::utils::snowflake_to_unix(u128::from(ctx.author().id.0)); 381 + 382 + #[allow(clippy::cast_possible_truncation)] 383 + // this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN 384 + let date_time_stamp = chrono::NaiveDateTime::from_timestamp_opt(unix_timecode as i64, 0) 385 + .unwrap_or(chrono::NaiveDateTime::MIN); 386 + 387 + let age = chrono::Utc::now() 388 + .naive_utc() 389 + .signed_duration_since(date_time_stamp) 390 + .num_days(); 391 + 392 + let is_user_in_db: Option<String> = check_username_against_db(ctx.author().id.0).await.unwrap(); 393 + 394 + 395 + // TODO: set your own channel ID! 396 + // log user name, id, guild name, id and url to channel 397 + poise::serenity_prelude::ChannelId(0000000000000000000) 398 + .send_message(ctx, |f| { 399 + f.embed(|e| { 400 + e.title("DB search info") 401 + .field("Username", ctx.author().name.clone(), true) 402 + .field("User ID", ctx.author().id.0.to_string(), true) 403 + .field("User Account age (days)", age, true) 404 + .field("Source Server Name", ctx.guild().unwrap().name, true) 405 + .field( 406 + "Source Server ID", 407 + ctx.guild().unwrap().id.0.to_string(), 408 + true, 409 + ) 410 + .field("User ID Provided", user_id, true) 411 + .field( 412 + "Is user in DB (The person who ran command)", 413 + format!("{}", is_user_in_db.is_some()), 414 + false, 415 + ) 416 + }) 417 + }) 418 + .await?; 419 + 420 + Ok(()) 421 + } 422 + 423 + pub async fn check_username_against_db(uid: u64) -> anyhow::Result<Option<String>> { 424 + let mut con = open_redis_connection().await?; 425 + let result: Option<String> = redis::cmd("JSON.GET") 426 + .arg(format!("user:{uid}")) 427 + .clone() 428 + .query_async(&mut con) 429 + .await?; 430 + Ok(result) 431 + } 432 + 433 + #[cfg(feature = "database")] 434 + #[instrument(skip(ctx))] 435 + #[poise::command( 436 + prefix_command, 437 + slash_command, 438 + category = "DB", 439 + member_cooldown = 5, 440 + check = "guild_auth_check", 441 + guild_only 442 + )] 443 + pub async fn remove_guild( 444 + ctx: Context<'_>, 445 + #[description = "ID of guild to remove from the DB."] guild_id: String, 446 + ) -> Result<(), Error> { 447 + ctx.defer().await?; 448 + 449 + let uid = guild_id.trim().to_string(); 450 + 451 + let mut con = open_redis_connection().await?; 452 + 453 + // Create redis pipeline 454 + let mut pipe = redis::pipe(); 455 + 456 + // Get all user keys 457 + let key_list: Vec<String> = redis::cmd("KEYS") 458 + .arg("user:*") 459 + .clone() 460 + .query_async(&mut con) 461 + .await?; 462 + 463 + // I wounder if rayon can be used here? 464 + for key in key_list { 465 + pipe.cmd("JSON.GET").arg(key); 466 + } 467 + 468 + // Should only fail if the DB is empty, if the DB is empty we have worse problems.. 469 + let blacklist_entries_json: Vec<String> = pipe.atomic().query_async(&mut con).await?; 470 + 471 + // Vec os user info structs 472 + let mut blacklist_entries: Vec<UserInfo> = Vec::new(); 473 + 474 + // Convert json to structs 475 + for entry in blacklist_entries_json { 476 + let entry: UserInfo = serde_json::from_str(entry.as_str())?; 477 + blacklist_entries.push(entry); 478 + } 479 + 480 + // Vec of user info structs that have offences in the guild we want to remove 481 + let mut guild_offences: Vec<UserInfo> = Vec::new(); 482 + 483 + // Get all users that have offences in the guild we want to remove 484 + for entry in blacklist_entries { 485 + let entry_clone = entry.clone(); 486 + for offence in entry.offences { 487 + if offence.guild_id.eq(&uid) { 488 + guild_offences.push(entry_clone); 489 + break; 490 + } 491 + } 492 + } 493 + 494 + // Create redis pipeline 495 + let mut pipe2 = redis::pipe(); 496 + 497 + // Remove the guild from all users that have offences in it 498 + for entry in guild_offences.clone() { 499 + let mut offences: Vec<Offense> = Vec::new(); 500 + 501 + for offence in entry.clone().offences { 502 + if !offence.guild_id.eq(&uid) { 503 + offences.push(offence); 504 + } 505 + } 506 + 507 + let mut user = entry.clone(); 508 + user.offences = offences; 509 + 510 + let json_user = serde_json::to_string(&user)?; 511 + 512 + pipe2 513 + .cmd("JSON.SET") 514 + .arg(format!("user:{}", user.discord_id.unwrap())) 515 + .arg("$".to_string()) 516 + .arg(json_user) 517 + .clone() 518 + .query_async(&mut con) 519 + .await?; 520 + } 521 + 522 + // Edit all entries at once 523 + pipe2.atomic().query_async(&mut con).await?; 524 + 525 + // Respond to the user with the amount of users that had offences in the guild we removed 526 + ctx.say(format!( 527 + "Removed guild {} from {} users.", 528 + guild_id, 529 + guild_offences.len() 530 + )) 531 + .await?; 532 + 533 + Ok(()) 534 + } 535 + 536 + /// Update the search engine entries 537 + #[cfg(feature = "database")] 538 + #[instrument(skip(ctx))] 539 + #[poise::command(slash_command, category = "DB", owners_only)] 540 + pub async fn update_search_engine(ctx: Context<'_>) -> Result<(), Error> { 541 + ctx.defer_ephemeral().await?; 542 + 543 + // Create a client (without sending any request so that can't fail) 544 + let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); 545 + 546 + // connect to index "entries" 547 + let entries = client.index("entries"); 548 + 549 + let msg = ctx 550 + .send(|b| b.content("Connected to search engine")) 551 + .await?; 552 + 553 + // connect to redis and pull all blacklist entries into a vec 554 + 555 + let mut con = open_redis_connection().await?; 556 + let mut pipe = redis::pipe(); 557 + 558 + msg.edit(ctx, |b| b.content("Connected to DB")).await?; 559 + 560 + // Get all user keys 561 + let key_list: Vec<String> = redis::cmd("KEYS") 562 + .arg("user:*") 563 + .clone() 564 + .query_async(&mut con) 565 + .await?; 566 + 567 + msg.edit(ctx, |b| b.content("Fetching all DB entries")) 568 + .await?; 569 + 570 + // I wounder if rayon can be used here? 571 + for key in key_list { 572 + pipe.cmd("JSON.GET").arg(key); 573 + } 574 + 575 + // Should only fail if the DB is empty, if the DB is empty we have larger problems.. 576 + let blacklist_entries: Vec<String> = pipe.atomic().query_async(&mut con).await?; 577 + 578 + let mut black_listed_users = Vec::new(); 579 + for user in blacklist_entries { 580 + let user: UserInfo = serde_json::from_str(&user).expect("It was 701"); 581 + 582 + black_listed_users.push(user); 583 + } 584 + 585 + msg.edit(ctx, |b| b.content("Updating search engine")) 586 + .await?; 587 + 588 + // push entries to meidisearch 589 + match entries 590 + .add_documents(black_listed_users.as_slice(), Some("discord_id")) 591 + .await 592 + { 593 + Ok(_) => {} 594 + Err(err) => { 595 + event!( 596 + Level::INFO, 597 + suggestion = "This error can probably be ignored?", 598 + error = ?err 599 + ); 600 + } 601 + } 602 + 603 + msg.edit(ctx, |b| { 604 + b.content("All DB entries sent to search engine, any changes or new entries should be avaliable in a couple minutes.") 605 + }).await?; 606 + 607 + event!(Level::INFO, "Search engine was updated."); 608 + 609 + Ok(()) 610 + } 611 + 612 + /// Check your entire server against the database. Now with output options! 613 + #[cfg(feature = "database")] 614 + #[instrument(skip(ctx))] 615 + #[poise::command( 616 + prefix_command, 617 + slash_command, 618 + category = "DB", 619 + guild_cooldown = 30, 620 + check = "guild_auth_check", 621 + guild_only 622 + )] 623 + pub async fn footprint_lookup( 624 + ctx: Context<'_>, 625 + #[description = "What format do you want the results as?"] output_format: Option< 626 + BlacklistOutput, 627 + >, 628 + ) -> Result<(), Error> { 629 + ctx.defer().await?; 630 + 631 + let mut con = open_redis_connection().await?; 632 + 633 + // let key_list: Vec<String> = redis::cmd("KEYS") 634 + // .arg("user:*") 635 + // .clone() 636 + // .query_async(&mut con) 637 + // .await?; 638 + 639 + let mut key_pipe = redis::pipe(); 640 + 641 + let mut i = 1; 642 + let mut guild_members: Vec<Member> = Vec::new(); 643 + 644 + while i < ctx.guild().unwrap().member_count { 645 + let latest = guild_members.last().map(|u| u.user.id); 646 + 647 + let mut guild_members_temp = ctx 648 + .guild() 649 + .unwrap() 650 + .members(ctx, Some(1000), latest) 651 + .await 652 + .expect("It was 710"); 653 + 654 + i = i.add(1000); 655 + guild_members.append(&mut guild_members_temp); 656 + } 657 + 658 + // let mut blacklist_entries: Vec<String> = Vec::new(); 659 + 660 + guild_members.clone().into_iter().for_each(|key| { 661 + key_pipe 662 + .cmd("JSON.GET") 663 + .arg(format!("user:{}", key.user.id.as_u64())); 664 + }); 665 + 666 + let opt_blacklist_entries: Vec<Option<String>> = 667 + key_pipe.atomic().query_async(&mut con).await?; 668 + 669 + let mut blacklist_entries: Vec<String> = Vec::new(); 670 + 671 + for ent in opt_blacklist_entries { 672 + let _ = ent.map_or((), |thing| { 673 + blacklist_entries.push(thing); 674 + }); 675 + } 676 + 677 + let mut black_listed_users = Vec::new(); 678 + for user in blacklist_entries { 679 + let user: UserInfo = serde_json::from_str(&user)?; 680 + 681 + black_listed_users.push(user); 682 + } 683 + 684 + let mut hit_count: u64 = 0; 685 + 686 + // Setup CSV 687 + let mut blacklist_file = csv::Writer::from_writer(vec![]); 688 + blacklist_file 689 + .write_record([ 690 + "Discord ID", 691 + "Username", 692 + "Guild ID", 693 + "Reason", 694 + "Related image(s)", 695 + "Extra details", 696 + ]) 697 + .expect("Unable to repare CSV"); 698 + 699 + // Setup "Json" 700 + let mut struct_hit_list: Vec<BlacklistHit> = Vec::new(); 701 + 702 + for member in guild_members { 703 + let member_id = format!("{}", member.user.id.as_u64()); 704 + let hit_list: Vec<UserInfo> = black_listed_users 705 + .clone() 706 + .into_iter() 707 + .filter(|blu| { 708 + blu.discord_id 709 + .as_ref() 710 + .expect("Missing ID in BL user somehow?") 711 + == &member_id 712 + }) 713 + .collect(); 714 + 715 + // Moves to next loop if empty 716 + if hit_list.is_empty() { 717 + continue; 718 + } 719 + 720 + hit_count += 1; 721 + 722 + // TODO: Add .json and other output options 723 + // TODO: Add compact output format: 724 + // @user1 - 1 725 + // @user2 - 5 726 + // @user3 - 2 727 + 728 + // Write discord specific instances to CSV 729 + for hit in hit_list { 730 + let cloned_hit = hit.clone(); 731 + let username = UserId::from(cloned_hit.discord_id.as_ref().unwrap().parse::<u64>()?) 732 + .to_user(ctx) 733 + .await? 734 + .name 735 + .to_string(); 736 + let user_id = cloned_hit 737 + .discord_id 738 + .expect("Missing ID in BL user somehow?") 739 + .to_string(); 740 + 741 + match output_format { 742 + None | Some(BlacklistOutput::Csv) => { 743 + for offense in hit.offences { 744 + blacklist_file.write_record(&[ 745 + format!("'{}'", user_id.clone()), 746 + format!("'{}'", username.clone()), 747 + format!("'{}'", offense.guild_id), 748 + format!("'{}'", offense.reason), 749 + format!( 750 + "'{}'", 751 + offense 752 + .image 753 + .map_or_else(|| "N/A".to_string(), |image| image) 754 + ), 755 + format!( 756 + "'{}'", 757 + offense 758 + .extra 759 + .map_or_else(|| "N/A".to_string(), |extra| extra) 760 + ), 761 + ])?; 762 + } 763 + } 764 + Some( 765 + BlacklistOutput::Json | BlacklistOutput::Chat | BlacklistOutput::CompactChat, 766 + ) => { 767 + for offense in hit.offences { 768 + struct_hit_list.push(BlacklistHit { 769 + user_id: user_id.clone(), 770 + username: username.clone(), 771 + guild_id: offense.guild_id.clone(), 772 + reason: offense.reason.clone(), 773 + image: (offense 774 + .image 775 + .map_or_else(|| "N/A".to_string(), |image| image)) 776 + .to_string(), 777 + extra: (offense 778 + .extra 779 + .map_or_else(|| "N/A".to_string(), |extra| extra)) 780 + .to_string(), 781 + }); 782 + } 783 + } 784 + } 785 + } 786 + } 787 + 788 + if hit_count > 0 { 789 + match output_format { 790 + None | Some(BlacklistOutput::Csv) => { 791 + // ? There has to be a btter way to handle this 792 + let blf_as_bytes = String::from_utf8(blacklist_file.into_inner().unwrap()) 793 + .expect("Unable to convert CSV data to string") 794 + .as_bytes() 795 + .to_owned(); 796 + 797 + ctx.send(|b| { 798 + b.content(format!( 799 + "Your server has {} bad actor(s).\nA .csv file is attaches with all of the results, you can open this in any text editor but is more suited for google sheets/excel.\nrun `/ban ban_help` to find out how to ban multiple users at once.", 800 + hit_count 801 + )) 802 + .attachment(AttachmentType::Bytes { 803 + data: std::borrow::Cow::Borrowed(&blf_as_bytes), 804 + filename: format!("{}_footprint_results.csv", 805 + match ctx.guild() { 806 + Some(g) => g.name, 807 + None => "your".to_string() 808 + }), 809 + }) 810 + }) 811 + .await?; 812 + } 813 + Some(BlacklistOutput::Json) => { 814 + let json_str = serde_json::to_string_pretty(&struct_hit_list)?; 815 + 816 + let blf_as_bytes = json_str.as_bytes().to_owned(); 817 + 818 + ctx.send(|b| { 819 + b.content(format!( 820 + "Your server has {} bad actor(s).\nA .json file is attaches with all of the results.\nYou requested a json file so I'm going to assume you know what you're doing!", 821 + hit_count 822 + )) 823 + .attachment(AttachmentType::Bytes { 824 + data: std::borrow::Cow::Borrowed(&blf_as_bytes), 825 + filename: format!("{}_footprint_results.json", 826 + match ctx.guild() { 827 + Some(g) => g.name, 828 + None => "your".to_string() 829 + }), 830 + }) 831 + }) 832 + .await?; 833 + } 834 + 835 + Some(BlacklistOutput::Chat) => { 836 + let mut message_content: Vec<String> = Vec::new(); 837 + 838 + for hit in struct_hit_list { 839 + let hit_msg = format!( 840 + "<@{}>/{0} was found in your server for the following reason: `{}`\nExtras: {}\nImages: {}", 841 + hit.user_id, 842 + hit.reason, 843 + hit.extra, 844 + hit.image 845 + ); 846 + 847 + let combined_message = message_content.join("\n-----\n"); 848 + 849 + // The +5 is to account for the `\n-----\n` 850 + if (combined_message.chars().count() + hit_msg.chars().count() + 5) < 2000 { 851 + message_content.push(hit_msg); 852 + } else { 853 + match ctx.say(combined_message.clone()).await { 854 + Ok(_) => {} 855 + Err(err) => { 856 + event!( 857 + Level::ERROR, 858 + suggestion = "Message might be too large again?", 859 + error = ?err, 860 + msg_content = combined_message, 861 + ); 862 + panic!(); 863 + } 864 + } 865 + 866 + message_content.clear(); 867 + 868 + if hit_msg.chars().count() > 2000 { 869 + ctx.send(|f| { 870 + f.content(format!( 871 + "<@{}>/{0} was found in your server for the following reason:", 872 + hit.user_id 873 + )) 874 + .ephemeral(false) 875 + .attachment( 876 + AttachmentType::Bytes { 877 + data: std::borrow::Cow::Borrowed(hit_msg.as_bytes()), 878 + filename: format!( 879 + "{}-{}_extra_large_offence.txt", 880 + hit.user_id, hit.guild_id 881 + ), 882 + }, 883 + ) 884 + }) 885 + .await?; 886 + } else { 887 + message_content.push(hit_msg); 888 + } 889 + } 890 + } 891 + 892 + if !message_content.is_empty() { 893 + ctx.say(message_content.join("\n-----\n")).await?; 894 + } 895 + 896 + ctx.say(format!("Found {hit_count} user(s) from the database!\nYou can easily ban them by right clicking on their @")).await?; 897 + } 898 + Some(BlacklistOutput::CompactChat) => { 899 + let mut message_content: Vec<String> = Vec::new(); 900 + 901 + let mut unique_hits: HashSet<String> = HashSet::new(); 902 + 903 + for hit in struct_hit_list { 904 + unique_hits.insert(format!("Found: <@{0}>/{0}", hit.user_id)); 905 + } 906 + 907 + for unique_hit in unique_hits { 908 + if message_content.len() == 20 { 909 + ctx.say(message_content.join("\n")).await?; 910 + message_content.clear(); 911 + } else { 912 + message_content.push(unique_hit); 913 + } 914 + } 915 + 916 + if !message_content.is_empty() { 917 + ctx.say(message_content.join("\n")).await?; 918 + } 919 + 920 + ctx.say(format!("Found {hit_count} user(s) from the database!\nYou can easily ban them by right clicking on their @")).await?; 921 + } 922 + } 923 + } else { 924 + ctx.say("Looks like the server is squeaky clean and free from cringe users, good job!") 925 + .await?; 926 + } 927 + 928 + let unix_timecode = rusted_fbt_lib::utils::snowflake_to_unix(u128::from(ctx.author().id.0)); 929 + 930 + #[allow(clippy::cast_possible_truncation)] 931 + // this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN 932 + let date_time_stamp = chrono::NaiveDateTime::from_timestamp_opt(unix_timecode as i64, 0) 933 + .unwrap_or(chrono::NaiveDateTime::MIN); 934 + 935 + let age = chrono::Utc::now() 936 + .naive_utc() 937 + .signed_duration_since(date_time_stamp) 938 + .num_days(); 939 + 940 + let is_user_in_db: Option<String> = 941 + check_username_against_db(ctx.author().id.00).await.unwrap(); 942 + 943 + 944 + // TODO: set your own channel ID! 945 + // log user name, id, guild name, id and url to channel 946 + poise::serenity_prelude::ChannelId(0000000000000000000) 947 + .send_message(ctx, |f| { 948 + f.embed(|e| { 949 + e.title("Entire server footprint request") 950 + .field("Username", ctx.author().name.clone(), true) 951 + .field("User ID", ctx.author().id.0.to_string(), true) 952 + .field("User Account age (days)", age, true) 953 + .field("Source Server Name", ctx.guild().unwrap().name, true) 954 + .field( 955 + "Source Server ID", 956 + ctx.guild().unwrap().id.0.to_string(), 957 + true, 958 + ) 959 + .field( 960 + "Is user in DB (The person who ran command)", 961 + format!("{}", is_user_in_db.is_some()), 962 + false, 963 + ) 964 + }) 965 + }) 966 + .await?; 967 + 968 + Ok(()) 969 + } 970 + 971 + /// Process an entire CSV into the Redis database (V2.0) 972 + #[cfg(feature = "database")] 973 + #[instrument(skip(ctx))] 974 + #[poise::command( 975 + prefix_command, 976 + slash_command, 977 + category = "DB", 978 + required_permissions = "ADMINISTRATOR", 979 + check = "bot_admin_check", 980 + guild_only 981 + )] 982 + pub async fn excel( 983 + ctx: Context<'_>, 984 + #[description = "CSV file to upload"] csv_file: Attachment, 985 + #[description = "ID of server the users are from"] guild_id: String, 986 + #[description = "Reason for being added to the DB"] reason: String, 987 + ) -> Result<(), Error> { 988 + ctx.defer_or_broadcast().await?; 989 + 990 + #[allow(clippy::case_sensitive_file_extension_comparisons)] 991 + // Not actually case sensitive thanks to `.to_lowercase()` 992 + if csv_file.url.to_lowercase().ends_with(".csv") { 993 + let embed_message = ctx 994 + .send(|b| { 995 + b.embed(|e| { 996 + e.description("Uploading entried to DB now") 997 + .color(colours::css::WARNING) 998 + .thumbnail("https://media1.giphy.com/media/3o7bu3XilJ5BOiSGic/giphy.gif") 999 + }) 1000 + }) 1001 + .await?; 1002 + 1003 + // Download file and convert to csv reader 1004 + let response = reqwest::get(csv_file.url).await?; 1005 + let csv_content = response.text().await?; 1006 + let mut csv_reader = csv::Reader::from_reader(csv_content.as_bytes()); 1007 + 1008 + // Start timer 1009 + let start = tokio::time::Instant::now(); 1010 + 1011 + let mut records = Vec::new(); 1012 + 1013 + // Converts each line csv into a CsvEntry object 1014 + for record in csv_reader.deserialize() { 1015 + let entry: CsvEntry = match record { 1016 + Err(err) => { 1017 + if verbose_mode() { 1018 + ctx.send(|b| { 1019 + b.embed(|e| { 1020 + e.description(format!("Failed to read a line of the CSV because: {err:?}")) 1021 + .color(colours::css::DANGER) 1022 + .thumbnail("https://media0.giphy.com/media/TqiwHbFBaZ4ti/giphy.gif?cid=ecf05e476gqwddci6tjtal5ohkp9ql3tq3m3scilolen1jh8&rid=giphy.gif&ct=g") 1023 + }) 1024 + }).await?; 1025 + } else { 1026 + ctx.say("Failed to read a line of the CSV, probably a , in someone's name.\nYou can probably ignore this.".to_string()) 1027 + .await?; 1028 + } 1029 + CsvEntry { 1030 + AuthorID: "0".to_string(), 1031 + Author: "0".to_string(), 1032 + } 1033 + } 1034 + Ok(out) => out, 1035 + }; 1036 + 1037 + if entry.AuthorID == *"0" && entry.Author == *"0" { 1038 + continue; 1039 + } 1040 + 1041 + records.push(entry); 1042 + } 1043 + 1044 + #[allow(clippy::iter_with_drain)] 1045 + let records_set: HashSet<_> = records.drain(..).collect(); // dedup 1046 + records.extend(records_set.into_iter()); 1047 + 1048 + // End conversion 1049 + 1050 + let mut con = open_redis_connection().await?; 1051 + let mut pipe = redis::pipe(); 1052 + 1053 + let mut users = Vec::new(); 1054 + let mut user_ids = Vec::new(); 1055 + 1056 + for entry in records { 1057 + // TODO: Add more checks once other lists are setup? 1058 + if !BOT_IDS.contains(&entry.AuthorID.parse::<u64>()?) 1059 + || BOT_ADMINS.contains(&entry.AuthorID.parse::<u64>()?) 1060 + { 1061 + let offense = vec![Offense { 1062 + guild_id: guild_id.parse().expect("Invalid Guild ID"), 1063 + reason: reason.clone(), 1064 + image: None, 1065 + extra: None, 1066 + }]; 1067 + 1068 + let user = UserInfo { 1069 + vrc_id: None, 1070 + username: Some(entry.Author), 1071 + discord_id: Some(entry.AuthorID), 1072 + offences: offense, 1073 + }; 1074 + 1075 + users.push(user.clone()); 1076 + user_ids.push(user.discord_id); 1077 + } 1078 + } 1079 + 1080 + #[allow(clippy::iter_with_drain)] 1081 + let user_ids_set: HashSet<_> = user_ids.drain(..).collect(); // dedup 1082 + user_ids.extend(user_ids_set.into_iter()); 1083 + 1084 + let actual_count = user_ids.len() as u64; 1085 + 1086 + let mut combined_users = Vec::new(); 1087 + 1088 + let key_list: Vec<String> = redis::cmd("KEYS") 1089 + .arg("user:*") 1090 + .clone() 1091 + .query_async(&mut con) 1092 + .await?; 1093 + 1094 + let mut key_pipe = redis::pipe(); 1095 + 1096 + for key in key_list { 1097 + key_pipe.cmd("JSON.GET").arg(key); 1098 + } 1099 + 1100 + let old_users: Vec<String> = key_pipe.atomic().query_async(&mut con).await?; 1101 + 1102 + for old_user in old_users { 1103 + let user: UserInfo = serde_json::from_str(&old_user)?; 1104 + 1105 + users.push(user); 1106 + } 1107 + 1108 + for id in user_ids { 1109 + let matches: Vec<UserInfo> = users 1110 + .clone() 1111 + .into_iter() 1112 + .filter(|u| u.discord_id == id) 1113 + .collect(); 1114 + 1115 + let mut out_user = UserInfo { 1116 + vrc_id: None, 1117 + username: None, 1118 + discord_id: None, 1119 + offences: Vec::new(), 1120 + }; 1121 + 1122 + // Merge every match of this user into out_user 1123 + for user in matches { 1124 + out_user.merge(user); 1125 + } 1126 + let out_user_offences_set: HashSet<_> = out_user.offences.drain(..).collect(); // dedup 1127 + out_user.offences.extend(out_user_offences_set.into_iter()); 1128 + 1129 + combined_users.push(out_user); 1130 + } 1131 + 1132 + for user in combined_users { 1133 + let json_user = serde_json::to_string(&user).unwrap(); 1134 + 1135 + // Queue up JSON.SET commands 1136 + pipe.cmd("JSON.SET").arg(&[ 1137 + format!( 1138 + "user:{}", 1139 + user.discord_id.expect("Invalid Discord ID in entry") 1140 + ), 1141 + "$".to_string(), 1142 + json_user, 1143 + ]); 1144 + } 1145 + 1146 + // Upload all entries at once 1147 + pipe.atomic().query_async(&mut con).await?; 1148 + 1149 + // End timer 1150 + let duration = start.elapsed(); 1151 + 1152 + embed_message.edit(ctx, |b| { 1153 + b.embed(|e| { 1154 + e.description("Completed upload!".to_string()) 1155 + .color(colours::css::POSITIVE) 1156 + .thumbnail("https://media3.giphy.com/media/mJHSkWKziszrkcNJPo/giphy.gif?cid=ecf05e47sli83f591onowkgacia9xezewha5pcoj6651yszz&rid=giphy.gif&ct=g") 1157 + .fields([ 1158 + ("Upload time", format!("{duration:?}"), false), 1159 + ("Entries processed", format!("{actual_count}"), false) 1160 + ]) 1161 + }) 1162 + }).await?; 1163 + } else { 1164 + ctx.say("The file needs to be a `.csv`!").await?; 1165 + } 1166 + 1167 + Ok(()) 1168 + } 1169 + 1170 + /// Print out the meaning behind C/R/L/T in blacklist entries 1171 + #[cfg(feature = "database")] 1172 + #[instrument(skip(ctx))] 1173 + #[poise::command(prefix_command, slash_command, category = "DB")] 1174 + pub async fn key(ctx: Context<'_>) -> Result<(), Error> { 1175 + ctx.say( 1176 + "This is the Key for the Database: C/R/L/T\nC = Crasher, R = Ripper, L = Leaker, T = Toxic", 1177 + ) 1178 + .await?; 1179 + 1180 + Ok(()) 1181 + } 1182 + 1183 + /// Add user id to alt protection whitelist 1184 + #[cfg(feature = "database")] 1185 + #[instrument(skip(ctx))] 1186 + #[poise::command( 1187 + prefix_command, 1188 + slash_command, 1189 + check = "bot_admin_check", 1190 + category = "DB" 1191 + )] 1192 + pub async fn whitelist(ctx: Context<'_>, user_id: String) -> Result<(), Error> { 1193 + let mut con = open_redis_connection().await?; 1194 + 1195 + // get kick-whitelist as a hashset and check if user is already in it 1196 + let kick_whitelist: HashSet<String> = redis::cmd("SMEMBERS") 1197 + .arg("kick-whitelist") 1198 + .clone() 1199 + .query_async(&mut con) 1200 + .await?; 1201 + 1202 + if kick_whitelist.contains(&user_id) { 1203 + ctx.say("User is already in the whitelist!").await?; 1204 + } else { 1205 + // add user to kick-whitelist 1206 + redis::cmd("SADD") 1207 + .arg("kick-whitelist") 1208 + .arg(user_id) 1209 + .query_async(&mut con) 1210 + .await?; 1211 + 1212 + ctx.say("User added to the whitelist!").await?; 1213 + } 1214 + 1215 + Ok(()) 1216 + }
+176
src/commands/fun.rs
··· 1 + use core::time; 2 + 3 + use poise::serenity_prelude::{self as serenity, AttachmentType}; 4 + use rusted_fbt_lib::enums::WaifuTypes; 5 + use rusted_fbt_lib::types::{Context, Error}; 6 + use serde::{Deserialize, Serialize}; 7 + use tokio::time::sleep; 8 + use tracing::instrument; 9 + use uwuifier::uwuify_str_sse; 10 + 11 + #[instrument(skip(ctx))] 12 + #[poise::command(prefix_command, slash_command, category = "Fun", member_cooldown = 15)] 13 + /// This user is cringe 14 + pub async fn cringe( 15 + ctx: Context<'_>, 16 + #[description = "Optionally call this user cringe"] user: Option<serenity::User>, 17 + ) -> Result<(), Error> { 18 + let camera_message = ctx.say("<a:camera:870459823907553352>").await?; 19 + 20 + sleep(time::Duration::from_secs(1)).await; 21 + 22 + camera_message 23 + .edit(ctx, |b| { 24 + b.content("<a:camera_with_flash:870458599325986898>") 25 + }) 26 + .await?; 27 + 28 + sleep(time::Duration::from_secs(1)).await; 29 + 30 + camera_message 31 + .edit(ctx, |b| b.content("<a:camera:870459823907553352>")) 32 + .await?; 33 + 34 + match user { 35 + None => { 36 + ctx.say("Yep, that's going in my cringe compilation") 37 + .await?; 38 + } 39 + Some(user) => { 40 + ctx.send(|m| { 41 + m.content(format!( 42 + "Yep <@{}>, that's going in my cringe compilation", 43 + user.id 44 + )) 45 + }) 46 + .await?; 47 + } 48 + } 49 + 50 + Ok(()) 51 + } 52 + 53 + /// OwOifys your message 54 + #[instrument(skip(ctx))] 55 + #[poise::command(prefix_command, slash_command, category = "Fun")] 56 + pub async fn owo(ctx: Context<'_>, #[description = "Message"] msg: String) -> Result<(), Error> { 57 + ctx.say(uwuify_str_sse(msg.as_str())).await?; 58 + 59 + Ok(()) 60 + } 61 + 62 + /// Replies with pog pog pog and pog frog! 63 + #[instrument(skip(ctx))] 64 + #[poise::command(prefix_command, slash_command, category = "Fun", member_cooldown = 10)] 65 + pub async fn pog(ctx: Context<'_>) -> Result<(), Error> { 66 + ctx.send(|f| { 67 + f.content("Pog pog pog!") 68 + .ephemeral(false) 69 + .attachment(AttachmentType::Bytes { 70 + data: std::borrow::Cow::Borrowed(include_bytes!("../../assets/pog-frog.gif")), 71 + filename: String::from("pog-frog.gif"), 72 + }) 73 + }) 74 + .await?; 75 + 76 + Ok(()) 77 + } 78 + 79 + /// Sends a random waifu (SFW) 80 + #[instrument(skip(ctx))] 81 + #[poise::command(slash_command, category = "Fun", member_cooldown = 5)] 82 + pub async fn waifu( 83 + ctx: Context<'_>, 84 + #[description = "What waifu do you want?"] waifu_type: Option<WaifuTypes>, 85 + ) -> Result<(), Error> { 86 + #[derive(Debug, Serialize, Deserialize, Clone)] 87 + struct Waifu { 88 + url: String, 89 + } 90 + 91 + let choice: String = match waifu_type { 92 + None => "waifu".to_string(), 93 + Some(WaifuTypes::Neko) => "neko".to_string(), 94 + Some(WaifuTypes::Megumin) => "megumin".to_string(), 95 + Some(WaifuTypes::Bully) => "bully".to_string(), 96 + Some(WaifuTypes::Cuddle) => "cuddle".to_string(), 97 + Some(WaifuTypes::Cry) => "cry".to_string(), 98 + Some(WaifuTypes::Kiss) => "kiss".to_string(), 99 + Some(WaifuTypes::Lick) => "lick".to_string(), 100 + Some(WaifuTypes::Pat) => "pat".to_string(), 101 + Some(WaifuTypes::Smug) => "smug".to_string(), 102 + Some(WaifuTypes::Bonk) => "bonk".to_string(), 103 + Some(WaifuTypes::Blush) => "blush".to_string(), 104 + Some(WaifuTypes::Smile) => "smile".to_string(), 105 + Some(WaifuTypes::Wave) => "wave".to_string(), 106 + Some(WaifuTypes::Highfive) => "highfive".to_string(), 107 + Some(WaifuTypes::Handhold) => "handhold".to_string(), 108 + Some(WaifuTypes::Nom) => "nom".to_string(), 109 + Some(WaifuTypes::Bite) => "bite".to_string(), 110 + Some(WaifuTypes::Glomp) => "glomp".to_string(), 111 + Some(WaifuTypes::Slap) => "slap".to_string(), 112 + Some(WaifuTypes::Kill) => "kill".to_string(), 113 + Some(WaifuTypes::Happy) => "happy".to_string(), 114 + Some(WaifuTypes::Wink) => "wink".to_string(), 115 + Some(WaifuTypes::Poke) => "poke".to_string(), 116 + Some(WaifuTypes::Dance) => "dance".to_string(), 117 + Some(WaifuTypes::Cringe) => "cringe".to_string(), 118 + }; 119 + 120 + let response = reqwest::get(format!("https://api.waifu.pics/sfw/{choice}")).await?; 121 + let waifu: Waifu = response.json().await?; 122 + 123 + // let waifu: Waifu = serde_json::from_str(json_content.as_str())?; 124 + 125 + ctx.send(|b| b.embed(|e| e.title("Your waifu:").image(waifu.url))) 126 + .await?; 127 + 128 + Ok(()) 129 + } 130 + 131 + /// Replies with pong! 132 + #[instrument(skip(ctx))] 133 + #[poise::command(prefix_command, slash_command, category = "Fun", member_cooldown = 15)] 134 + pub async fn ping(ctx: Context<'_>) -> Result<(), Error> { 135 + ctx.say("Pong! 🏓").await?; 136 + 137 + Ok(()) 138 + } 139 + 140 + /// That's toxic 141 + #[instrument(skip(ctx))] 142 + #[poise::command(slash_command, category = "Fun", member_cooldown = 15)] 143 + pub async fn toxic( 144 + ctx: Context<'_>, 145 + #[description = "Optionally call this user cringe"] user: Option<serenity::User>, 146 + ) -> Result<(), Error> { 147 + const GIF_NAME: &str = "toxic.gif"; 148 + let toxic_gif = include_bytes!("../../assets/toxic.gif"); 149 + 150 + ctx.defer().await?; 151 + 152 + match user { 153 + None => { 154 + ctx.send(|m| { 155 + m.content("That's toxic!") 156 + .attachment(AttachmentType::Bytes { 157 + data: std::borrow::Cow::Borrowed(toxic_gif), // include_bytes! directly embeds the gif file into the executable at compile time. 158 + filename: GIF_NAME.to_string(), 159 + }) 160 + }) 161 + .await?; 162 + } 163 + Some(user) => { 164 + ctx.send(|m| { 165 + m.content(format!("<@{}>, That's toxic!", user.id.as_u64())) 166 + .attachment(AttachmentType::Bytes { 167 + data: std::borrow::Cow::Borrowed(toxic_gif), // include_bytes! directly embeds the gif file into the executable at compile time. 168 + filename: GIF_NAME.to_string(), 169 + }) 170 + }) 171 + .await?; 172 + } 173 + } 174 + 175 + Ok(()) 176 + }
+147
src/commands/info.rs
··· 1 + use poise::serenity_prelude::{ChannelId, Colour}; 2 + use rand::Rng; 3 + use rusted_fbt_lib::{ 4 + types::{Context, Error}, 5 + utils::open_redis_connection, 6 + vars::{FEEDBACK_CHANNEL_ID, HELP_EXTRA_TEXT, VERSION}, 7 + }; 8 + #[cfg(feature = "database")] 9 + use std::time::{SystemTime, UNIX_EPOCH}; 10 + use tracing::instrument; 11 + use tracing::{event, Level}; 12 + 13 + #[instrument(skip(ctx))] 14 + #[poise::command(prefix_command, track_edits, slash_command, category = "Info")] 15 + /// Show this help menu 16 + pub async fn help( 17 + ctx: Context<'_>, 18 + #[description = "Specific command to show help about"] 19 + #[autocomplete = "poise::builtins::autocomplete_command"] 20 + command: Option<String>, 21 + ) -> Result<(), Error> { 22 + poise::builtins::help( 23 + ctx, 24 + command.as_deref(), 25 + poise::builtins::HelpConfiguration { 26 + extra_text_at_bottom: HELP_EXTRA_TEXT, 27 + show_context_menu_commands: true, 28 + ..Default::default() 29 + }, 30 + ) 31 + .await?; 32 + Ok(()) 33 + } 34 + 35 + #[cfg(feature = "database")] 36 + #[instrument(skip(ctx))] 37 + #[poise::command(slash_command, category = "Info", member_cooldown = 10, ephemeral)] 38 + /// Provide feedback for the bot team to look at! 39 + pub async fn feedback( 40 + ctx: Context<'_>, 41 + #[description = "Feedback you want to provide"] feedback: String, 42 + ) -> Result<(), Error> { 43 + ctx.defer_ephemeral().await?; 44 + let mut con = open_redis_connection().await?; 45 + 46 + redis::cmd("SET") 47 + .arg(format!( 48 + "feedback:{}-{}-{}", 49 + SystemTime::now() 50 + .duration_since(UNIX_EPOCH) 51 + .unwrap() 52 + .as_secs(), 53 + ctx.author().id.as_u64(), 54 + ctx.author().tag() 55 + )) 56 + .arg(feedback.clone()) 57 + .clone() 58 + .query_async(&mut con) 59 + .await?; 60 + 61 + let colour = &mut rand::thread_rng().gen_range(0..10_000_000); 62 + ChannelId(FEEDBACK_CHANNEL_ID) 63 + .send_message(ctx, |f| { 64 + f.embed(|e| { 65 + e.title("New feedback!".to_string()) 66 + .description(feedback) 67 + .color(Colour::new(*colour)) 68 + .author(|a| a.icon_url(ctx.author().face()).name(ctx.author().tag())) 69 + .thumbnail("https://media.giphy.com/media/U4sfHXAALLYBQzPcWk/giphy.gif") 70 + }) 71 + }) 72 + .await?; 73 + 74 + ctx.say("Thank you for the feedback! It has been sent directly to our developers.") 75 + .await?; 76 + 77 + Ok(()) 78 + } 79 + 80 + #[instrument(skip(ctx))] 81 + #[poise::command(prefix_command, slash_command, category = "Info", member_cooldown = 10)] 82 + /// Have some info about the bot 83 + pub async fn about(ctx: Context<'_>) -> Result<(), Error> { 84 + let guild_count = ctx 85 + .serenity_context() 86 + .cache 87 + .guilds() 88 + .len() 89 + .clone() 90 + .to_string(); 91 + 92 + // TODO: change to your own URLs 93 + let mut fields = vec![ 94 + ("Help page:", "[https://fbtsecurity.fbtheaven.com/](https://fbtsecurity.fbtheaven.com/)", false), 95 + ("Remove any of your info from the bot:", "[Delete your data](https://fbtsecurity.fbtheaven.com/data-and-privacy-policy#delete-your-data)", false), 96 + ("Bot version:", VERSION.unwrap_or("unknown"), false), 97 + ("Server count:", &guild_count, false), 98 + ]; 99 + 100 + // TODO: reduce the ammount of #[cfg(feature = "database")] here!! 101 + 102 + #[cfg(feature = "database")] 103 + let mut con = open_redis_connection().await?; 104 + 105 + #[cfg(feature = "database")] 106 + let execution_count: String = redis::cmd("GET") 107 + .arg("status:commands-executed") 108 + .clone() 109 + .query_async(&mut con) 110 + .await?; 111 + 112 + #[cfg(feature = "database")] 113 + let mut new_field = vec![( 114 + "Total commands run since 2.0.18:", 115 + execution_count.as_str(), 116 + false, 117 + )]; 118 + 119 + #[cfg(feature = "database")] 120 + fields.append(&mut new_field); 121 + 122 + // TODO: change to your own URLs 123 + ctx.send(|f| { 124 + f.embed(|e| { 125 + e.title("About") 126 + .url("https://fbtsecurity.fbtheaven.com/") 127 + .author(|a| { 128 + a.name("FBT Staff") 129 + .url("https://fbtsecurity.fbtheaven.com/") 130 + }) 131 + .fields(fields) 132 + .footer(|foot| { 133 + foot.text("Time mojo spent on V2.0+") 134 + }) 135 + .image(format!("https://wakatime.com/badge/user/fd57ff6b-f3f1-4957-b9c6-7e09bc3f0559/project/d2f87f17-8c44-4835-b4f6-f0089e52515f.png?rand={}", rand::thread_rng().gen_range(0..1_000_000_000))) 136 + }) 137 + }) 138 + .await?; 139 + 140 + #[cfg(feature = "database")] 141 + event!( 142 + Level::INFO, 143 + "Total commands run since 2.0.18" = execution_count.parse::<u32>().unwrap() + 1 144 + ); 145 + 146 + Ok(()) 147 + }
+6
src/commands/mod.rs
··· 1 + pub mod admin; 2 + pub mod database; 3 + pub mod fun; 4 + pub mod info; 5 + pub mod tickets; 6 + pub mod tools;
+225
src/commands/tickets.rs
··· 1 + use poise::serenity_prelude::{self as serenity}; 2 + use poise::serenity_prelude::{ChannelId, RoleId}; 3 + use poise::serenity_prelude::{PermissionOverwrite, PermissionOverwriteType, Permissions}; 4 + use rusted_fbt_lib::enums::CloseTicketFail; 5 + use rusted_fbt_lib::types::{Context, Error}; 6 + use rusted_fbt_lib::utils::verbose_mode; 7 + use rusted_fbt_lib::vars::{CLOSED_TICKET_CATEGORY, FBT_GUILD_ID, TICKET_CATEGORY}; 8 + use tracing::instrument; 9 + 10 + #[instrument(skip(ctx))] 11 + #[poise::command(slash_command, category = "Ticket", guild_only)] 12 + /// Create new ticket (FBT discord only!) 13 + pub async fn new_ticket( 14 + ctx: Context<'_>, 15 + #[description = "An optional topic to put on the ticket"] topic: Option<String>, 16 + ) -> Result<(), Error> { 17 + ctx.defer_ephemeral().await?; 18 + 19 + if *ctx.guild_id().unwrap().as_u64() == FBT_GUILD_ID { 20 + let mut channels = Vec::new(); 21 + 22 + for channel in ctx.guild().unwrap().channels { 23 + let parent_id = match channel.1.clone() { 24 + serenity::Channel::Guild(g) => g.parent_id, 25 + _ => None, 26 + }; 27 + 28 + if let Some(cat) = parent_id { 29 + if *cat.as_u64() == TICKET_CATEGORY { 30 + channels.push(channel.0.name(ctx).await.unwrap()); 31 + } 32 + } 33 + } 34 + 35 + let mut existing_ticket = false; 36 + 37 + for ch in channels { 38 + if ch.starts_with(&ctx.author().name) { 39 + existing_ticket = true; 40 + } 41 + } 42 + 43 + if existing_ticket { 44 + ctx.send(|b| { 45 + b.content("You already have a ticket open, you cannot open another!") 46 + .ephemeral(true) 47 + }) 48 + .await?; 49 + } else { 50 + 51 + // TODO: change these IDs to be your own server roles 52 + 53 + // Assuming a guild has already been bound. 54 + let perms = vec![ 55 + PermissionOverwrite { 56 + allow: Permissions::READ_MESSAGE_HISTORY 57 + | Permissions::VIEW_CHANNEL 58 + | Permissions::SEND_MESSAGES 59 + | Permissions::ADD_REACTIONS 60 + | Permissions::EMBED_LINKS 61 + | Permissions::ATTACH_FILES 62 + | Permissions::USE_EXTERNAL_EMOJIS, 63 + deny: Permissions::empty(), 64 + kind: PermissionOverwriteType::Member(ctx.author().id), 65 + }, 66 + PermissionOverwrite { 67 + allow: Permissions::all(), 68 + deny: Permissions::SEND_TTS_MESSAGES, 69 + kind: PermissionOverwriteType::Role(RoleId::from(737_168_134_569_590_888)), // Secretary (Probably not needed) 70 + }, 71 + PermissionOverwrite { 72 + allow: Permissions::all(), 73 + deny: Permissions::SEND_TTS_MESSAGES, 74 + kind: PermissionOverwriteType::Role(RoleId::from(820_914_502_220_513_330)), // Admin (Probably not needed) 75 + }, 76 + PermissionOverwrite { 77 + allow: Permissions::all(), 78 + deny: Permissions::SEND_TTS_MESSAGES, 79 + kind: PermissionOverwriteType::Role(RoleId::from(874_898_210_534_096_907)), // Mods 80 + }, 81 + PermissionOverwrite { 82 + allow: Permissions::all(), 83 + deny: Permissions::SEND_TTS_MESSAGES, 84 + kind: PermissionOverwriteType::Role(RoleId::from(1_005_994_060_416_294_942)), // World admin panel 85 + }, 86 + PermissionOverwrite { 87 + allow: Permissions::all(), 88 + deny: Permissions::SEND_TTS_MESSAGES, 89 + kind: PermissionOverwriteType::Role(RoleId::from(1_046_937_023_400_919_091)), // World admin panel trainee 90 + }, 91 + PermissionOverwrite { 92 + allow: Permissions::empty(), 93 + deny: Permissions::all(), 94 + kind: PermissionOverwriteType::Role(RoleId::from(737_168_134_502_350_849)), // @everyone 95 + }, 96 + ]; 97 + 98 + match ctx 99 + .guild() 100 + .expect("") 101 + .create_channel(ctx, |c| { 102 + c.category(ChannelId::from(TICKET_CATEGORY)) 103 + .name(format!( 104 + "{}-{}", 105 + ctx.author().name, 106 + chrono::offset::Utc::now().format("%s") 107 + )) 108 + .permissions(perms) 109 + .topic(topic.unwrap_or_else(|| "A new ticket".to_string())) 110 + }) 111 + .await 112 + { 113 + Ok(ch) => { 114 + ctx.send(|b| { 115 + b.content(format!( 116 + "Ticket created! Find it here: <#{}>", 117 + ch.id.as_u64() 118 + )) 119 + .ephemeral(true) 120 + }) 121 + .await?; 122 + 123 + ch.say( 124 + ctx, 125 + format!("New ticket opened by <@{}>!", ctx.author().id.as_u64()), 126 + ) 127 + .await?; 128 + } 129 + Err(error) => { 130 + let err_msg = if verbose_mode() { 131 + format!("Failed to create ticket. Reason: {error:?}") 132 + } else { 133 + "Failed to create ticket".to_string() 134 + }; 135 + 136 + ctx.send(|b| b.content(err_msg).ephemeral(true)).await?; 137 + } 138 + } 139 + } 140 + } 141 + 142 + Ok(()) 143 + } 144 + 145 + #[poise::command(slash_command, category = "Ticket", guild_only)] 146 + /// Closes the current ticket (FBT discord only!) 147 + pub async fn close_ticket(ctx: Context<'_>) -> Result<(), Error> { 148 + ctx.defer_ephemeral().await?; 149 + 150 + if *ctx.guild_id().unwrap().as_u64() == FBT_GUILD_ID { 151 + let mut failed = CloseTicketFail::False; 152 + 153 + let current_channel = ctx.channel_id().to_channel(ctx).await?; 154 + let chnnl_name = ctx 155 + .channel_id() 156 + .name(ctx) 157 + .await 158 + .unwrap_or_else(|| "Unkown Ticket".to_string()); 159 + 160 + let parent_id = match current_channel { 161 + serenity::Channel::Guild(g) => g.parent_id, 162 + _ => None, 163 + }; 164 + 165 + match parent_id { 166 + None => { 167 + failed = CloseTicketFail::False; 168 + } 169 + Some(channel_category) => { 170 + if *channel_category.as_u64() == TICKET_CATEGORY { 171 + match ctx 172 + .channel_id() 173 + .edit(ctx, |c| { 174 + c.category(Some(ChannelId::from(CLOSED_TICKET_CATEGORY))) 175 + .name(format!( 176 + "{}-{}", 177 + chnnl_name, 178 + chrono::offset::Utc::now().format("%s") 179 + )) 180 + }) 181 + .await 182 + { 183 + Ok(_) => {} 184 + Err(fail_reason) => { 185 + failed = CloseTicketFail::SerenityError(fail_reason); 186 + } 187 + } 188 + } else { 189 + failed = CloseTicketFail::IncorrectCategory; 190 + } 191 + } 192 + } 193 + 194 + match failed { 195 + CloseTicketFail::False => { 196 + ctx.say("Ticket closed!").await?; 197 + } 198 + CloseTicketFail::IncorrectCategory => { 199 + ctx.send(|b| { 200 + b.content(format!( 201 + "This can only be ran inside of a channel under <#{}>!", 202 + TICKET_CATEGORY 203 + )) 204 + .ephemeral(true) 205 + }) 206 + .await?; 207 + } 208 + CloseTicketFail::SerenityError(error) => { 209 + ctx.send(|b| { 210 + b.content(format!( 211 + "Failed to close ticker because of following error:\n{}", 212 + error 213 + )) 214 + .ephemeral(true) 215 + }) 216 + .await?; 217 + } 218 + } 219 + } else { 220 + ctx.say("This command must be ran inside of FBT's discord") 221 + .await?; 222 + } 223 + 224 + Ok(()) 225 + }
+320
src/commands/tools.rs
··· 1 + use chrono::NaiveDateTime; 2 + use poise::serenity_prelude::{self as serenity, AttachmentType, RichInvite}; 3 + use rusted_fbt_lib::{ 4 + checks::guild_auth_check, 5 + types::{Context, Error}, 6 + utils::snowflake_to_unix, 7 + }; 8 + use serde::Deserialize; 9 + use tracing::instrument; 10 + 11 + use crate::commands::database::check_username_against_db; 12 + 13 + #[instrument(skip(ctx))] 14 + #[poise::command(slash_command, track_edits, category = "Tools")] 15 + /// Display your or another user's account creation date 16 + pub async fn account_age( 17 + ctx: Context<'_>, 18 + #[description = "Selected user"] user: Option<serenity::User>, 19 + ) -> Result<(), Error> { 20 + let user = user.as_ref().unwrap_or_else(|| ctx.author()); 21 + 22 + let uid = *user.id.as_u64(); 23 + 24 + let unix_timecode = snowflake_to_unix(u128::from(uid)); 25 + 26 + #[allow(clippy::cast_possible_truncation)] 27 + // this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN 28 + let date_time_stamp = 29 + NaiveDateTime::from_timestamp_opt(unix_timecode as i64, 0).unwrap_or(NaiveDateTime::MIN); 30 + 31 + let age = chrono::Utc::now() 32 + .naive_utc() 33 + .signed_duration_since(date_time_stamp) 34 + .num_days(); 35 + 36 + ctx.say(format!( 37 + "{}'s account was created at {}.\nSo They are {} days old.", 38 + user.name, 39 + user.created_at(), 40 + age 41 + )) 42 + .await?; 43 + 44 + Ok(()) 45 + } 46 + 47 + /// Gets the creation date or a Snowflake ID 48 + #[instrument(skip(ctx))] 49 + #[poise::command(prefix_command, slash_command, category = "Tools")] 50 + pub async fn creation_date( 51 + ctx: Context<'_>, 52 + #[description = "ID of User/Message/Channel/ect"] snowflake_id: u128, 53 + ) -> Result<(), Error> { 54 + let unix_timecode = snowflake_to_unix(snowflake_id); 55 + 56 + #[allow(clippy::cast_possible_truncation)] 57 + // this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN 58 + let date_time_stamp = 59 + NaiveDateTime::from_timestamp_opt(unix_timecode as i64, 0).unwrap_or(NaiveDateTime::MIN); 60 + 61 + ctx.say(format!("Created/Joined on {date_time_stamp}")) 62 + .await?; 63 + 64 + Ok(()) 65 + } 66 + 67 + /// qmit 68 + #[instrument(skip(ctx))] 69 + #[poise::command(owners_only, slash_command, hide_in_help)] 70 + pub async fn bot_owner_tool_1(ctx: Context<'_>) -> Result<(), Error> { 71 + ctx.defer_ephemeral().await?; 72 + 73 + let guild_list = ctx.serenity_context().cache.guilds(); 74 + 75 + let mut invites: Vec<RichInvite> = Vec::new(); 76 + 77 + for guild in guild_list { 78 + let guild_invites: Option<Vec<RichInvite>> = (guild.invites(ctx).await).ok(); 79 + 80 + if guild_invites.clone().is_some() { 81 + invites.append(&mut guild_invites.unwrap()); 82 + } 83 + } 84 + 85 + // let shit_list = format!("All invites the bot can see:\n\n{:?}", invites); 86 + 87 + let mut new_list: String = "Every invite the bot can see, grouped by guild:\n\n[\n".to_string(); 88 + 89 + for invite in invites { 90 + new_list.push_str(format!("{},\n", serde_json::to_string(&invite)?).as_str()); 91 + } 92 + 93 + new_list.push(']'); 94 + 95 + ctx.send(|b| { 96 + b.content("All bot invites:".to_string()) 97 + .attachment(AttachmentType::Bytes { 98 + data: std::borrow::Cow::Borrowed(new_list.as_bytes()), 99 + filename: format!("{}_invites.txt", ctx.id()), 100 + }) 101 + }) 102 + .await?; 103 + 104 + Ok(()) 105 + } 106 + 107 + /// Get's all avaliable info from a Discord Invite 108 + #[instrument(skip(ctx))] 109 + #[poise::command( 110 + prefix_command, 111 + slash_command, 112 + category = "Tools", 113 + member_cooldown = 5, 114 + check = "guild_auth_check", 115 + guild_only 116 + )] 117 + pub async fn invite_info( 118 + ctx: Context<'_>, 119 + #[description = "Invite URL"] invite_url: String, 120 + ) -> Result<(), Error> { 121 + use linkify::LinkFinder; 122 + 123 + #[derive(Debug, Deserialize, Clone)] 124 + struct InviteObject { 125 + #[serde(rename(deserialize = "type"))] 126 + _type: u64, 127 + code: String, 128 + inviter: InviterObject, 129 + expires_at: Option<String>, 130 + guild: PartialGuild, 131 + guild_id: String, 132 + channel: PartialChannel, 133 + approximate_member_count: u64, 134 + approximate_presence_count: u64, 135 + } 136 + 137 + #[derive(Debug, Deserialize, Clone)] 138 + struct InviterObject { 139 + id: String, 140 + username: String, 141 + #[allow(dead_code)] 142 + avatar: Option<String>, 143 + discriminator: Option<String>, 144 + #[allow(dead_code)] 145 + public_flags: u64, 146 + #[allow(dead_code)] 147 + flags: u64, 148 + #[allow(dead_code)] 149 + banner: Option<String>, 150 + #[allow(dead_code)] 151 + accent_color: Option<u64>, 152 + global_name: Option<String>, 153 + #[allow(dead_code)] 154 + avatar_decoration_data: Option<String>, 155 + #[allow(dead_code)] 156 + banner_color: Option<String>, 157 + } 158 + 159 + #[derive(Debug, Deserialize, Clone)] 160 + struct PartialGuild { 161 + #[allow(dead_code)] 162 + id: String, 163 + name: String, 164 + #[allow(dead_code)] 165 + splash: Option<String>, 166 + #[allow(dead_code)] 167 + banner: Option<String>, 168 + description: Option<String>, 169 + #[allow(dead_code)] 170 + icon: Option<String>, 171 + #[allow(dead_code)] 172 + features: Vec<String>, 173 + #[allow(dead_code)] 174 + verification_level: u64, 175 + vanity_url_code: Option<String>, 176 + #[allow(dead_code)] 177 + nsfw_level: u64, 178 + #[allow(dead_code)] 179 + nsfw: bool, 180 + premium_subscription_count: u64, 181 + } 182 + 183 + #[derive(Debug, Deserialize, Clone)] 184 + struct PartialChannel { 185 + id: String, 186 + #[serde(rename(deserialize = "type"))] 187 + _type: u64, 188 + name: String, 189 + } 190 + 191 + let finder = LinkFinder::new(); 192 + let links: Vec<_> = finder.links(&invite_url).collect(); 193 + 194 + if links.is_empty() { 195 + ctx.say("No valid links found").await?; 196 + return Ok(()); 197 + } 198 + 199 + let link_str = links[0].as_str().to_owned(); 200 + 201 + let (_link, invite_code) = link_str.split_at(19); 202 + 203 + let response = reqwest::get(format!( 204 + "https://discord.com/api/v10/invites/{invite_code}?with_counts=true" 205 + )) 206 + .await?; 207 + let response_formatted: Option<InviteObject> = response.json().await?; 208 + 209 + if response_formatted.is_none() { 210 + ctx.say("Invite not found").await?; 211 + return Ok(()); 212 + } 213 + 214 + let invite = response_formatted.unwrap(); 215 + 216 + let invite_info_fields = vec![ 217 + ("Code:", invite.code, false), 218 + ("Expires:", invite.expires_at.unwrap_or_default(), false), 219 + ("Destination channel name:", invite.channel.name, false), 220 + ("Destination channel ID:", invite.channel.id, false), 221 + ]; 222 + 223 + let guild_info_fields = vec![ 224 + ("Server name:", invite.guild.name, false), 225 + ("Server ID:", invite.guild_id, false), 226 + ( 227 + "Server Description:", 228 + invite.guild.description.unwrap_or_default(), 229 + true, 230 + ), 231 + ( 232 + "Vanity URL code:", 233 + invite.guild.vanity_url_code.unwrap_or_default(), 234 + false, 235 + ), 236 + ( 237 + "Server boosts count:", 238 + format!("{}", invite.guild.premium_subscription_count), 239 + false, 240 + ), 241 + ( 242 + "Approx member count:", 243 + format!("{}", invite.approximate_member_count), 244 + false, 245 + ), 246 + ( 247 + "Approx online user count:", 248 + format!("{}", invite.approximate_presence_count), 249 + false, 250 + ), 251 + ]; 252 + 253 + let inviter_info_fields = vec![ 254 + ("Username:", invite.inviter.username, false), 255 + ( 256 + "Global username:", 257 + invite.inviter.global_name.unwrap_or_default(), 258 + false, 259 + ), 260 + ("User ID:", invite.inviter.id.clone(), false), 261 + ( 262 + "Discriminator(Eg: #0001):", 263 + invite.inviter.discriminator.unwrap_or_default(), 264 + false, 265 + ), 266 + ]; 267 + 268 + ctx.send(|f| { 269 + f.embed(|e| e.title("Invite Info").fields(invite_info_fields)) 270 + .embed(|e| e.title("Guild Info").fields(guild_info_fields)) 271 + .embed(|e| e.title("Inviter Info").fields(inviter_info_fields)) 272 + }) 273 + .await?; 274 + 275 + let unix_timecode = snowflake_to_unix(u128::from(ctx.author().id.0)); 276 + 277 + #[allow(clippy::cast_possible_truncation)] 278 + // this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN 279 + let date_time_stamp = 280 + NaiveDateTime::from_timestamp_opt(unix_timecode as i64, 0).unwrap_or(NaiveDateTime::MIN); 281 + 282 + let age = chrono::Utc::now() 283 + .naive_utc() 284 + .signed_duration_since(date_time_stamp) 285 + .num_days(); 286 + 287 + let is_user_in_db: Option<String> = 288 + check_username_against_db(invite.inviter.id.parse::<u64>().unwrap()) 289 + .await 290 + .unwrap(); 291 + 292 + 293 + // TODO: set your own channel ID! 294 + 295 + // log user name, id, guild name, id and url to channel 296 + serenity::ChannelId() 297 + .send_message(ctx, |f| { 298 + f.embed(|e| { 299 + e.title("User requested invite info") 300 + .field("Username", ctx.author().name.clone(), true) 301 + .field("User ID", ctx.author().id.0.to_string(), true) 302 + .field("User Account age (days)", age, true) 303 + .field("Source Server Name", ctx.guild().unwrap().name, true) 304 + .field( 305 + "Source Server ID", 306 + ctx.guild().unwrap().id.0.to_string(), 307 + true, 308 + ) 309 + .field("Url provided", link_str, true) 310 + .field( 311 + "Is User in DB", 312 + format!("{}", is_user_in_db.is_some()), 313 + false, 314 + ) 315 + }) 316 + }) 317 + .await?; 318 + 319 + Ok(()) 320 + }
+36
src/lib/args.rs
··· 1 + use crate::enums::{DebugLevel, LogDebugLevel}; 2 + use crate::vars::BOT_TOKEN; 3 + use clap::Parser; 4 + 5 + /// This is where CLI args are set 6 + #[derive(Parser, Debug)] 7 + #[clap(author, version, about, long_about = None)] 8 + pub struct Args { 9 + /// Discord bot token 10 + #[clap( 11 + short, 12 + long, 13 + default_value = BOT_TOKEN 14 + )] 15 + pub token: String, 16 + 17 + /// Command prefix for message commands 18 + #[clap(short, long, default_value = "`")] 19 + pub prefix: String, 20 + 21 + /// Output extra information in discord reply errors 22 + #[clap(short, long)] 23 + pub verbose: bool, 24 + 25 + /// Print out list of guilds in cache on startup 26 + #[clap(long("gpc"))] 27 + pub print_guild_cache: bool, 28 + 29 + /// emit debug information to both stdout and a file 30 + #[clap(value_enum, long, default_value = "most")] 31 + pub debug: DebugLevel, 32 + 33 + /// emit debug information to both stdout and a file 34 + #[clap(value_enum, long, default_value = "most")] 35 + pub debug_log: LogDebugLevel, 36 + }
+100
src/lib/checks.rs
··· 1 + use crate::types::{Context, Error}; 2 + use crate::vars::BOT_ADMINS; 3 + use std::collections::HashSet; 4 + 5 + /// Check if command user is in the `BOT_ADMINS` list 6 + /// 7 + /// # Errors 8 + /// 9 + /// This function will never return an error. 10 + #[allow(clippy::unused_async, clippy::missing_errors_doc)] // async is used by command checks but clippy can't tell 11 + pub async fn bot_admin_check(ctx: Context<'_>) -> Result<bool, Error> { 12 + // ? The bellow commented out code is for quick testing, automatic fails on my ID 13 + // match ctx.author().id.as_u64() { 14 + // 383507911160233985 => Ok(false), 15 + // _ => { 16 + // match BOT_ADMINS.contains(ctx.author().id.as_u64()) { 17 + // true => Ok(true), 18 + // false => Ok(false), 19 + // } 20 + // } 21 + // } 22 + if BOT_ADMINS.contains(ctx.author().id.as_u64()) { 23 + Ok(true) 24 + } else { 25 + Ok(false) 26 + } 27 + } 28 + 29 + // ? This might not be needed, I thinik it's a left over from before we dud guild based authing 30 + // ! Remove the _ if put into use! 31 + #[cfg(feature = "database")] 32 + #[deprecated( 33 + since = "0.1.12", 34 + note = "left over from before we dud guild based auth" 35 + )] 36 + #[allow(clippy::unused_async, clippy::missing_errors_doc)] // no need to lint dead code 37 + pub async fn _bot_auth_check(_ctx: Context<'_>) -> Result<bool, Error> { 38 + // if let Ok(res) = bot_admin_check(ctx).await { 39 + // if res { 40 + // return Ok(true); 41 + // } 42 + // } 43 + 44 + // let mut con = open_redis_connection().await?; 45 + 46 + // let key_list: HashSet<u64> = redis::cmd("SMEMBERS") 47 + // .arg("user-lists:authorised-users") 48 + // .clone() 49 + // .query_async(&mut con) 50 + // .await?; 51 + 52 + // if key_list.contains(ctx.author().id.as_u64()) { 53 + // Ok(true) 54 + // } else { 55 + // ctx.say("You are not authorized to use this command! Please contact a bot admin or Azuki!") 56 + // .await?; 57 + // Ok(false) 58 + // } 59 + Ok(false) 60 + } 61 + 62 + /// Checks if a user is authorised to use the bot in the current server 63 + /// 64 + /// # Errors 65 + /// 66 + /// This function will return an error if unable to connet to or query DB. 67 + #[cfg(feature = "database")] 68 + pub async fn guild_auth_check(ctx: Context<'_>) -> Result<bool, Error> { 69 + use crate::utils::open_redis_connection; 70 + 71 + if let Ok(res) = bot_admin_check(ctx).await { 72 + if res { 73 + return Ok(true); 74 + } 75 + } 76 + 77 + let mut con = open_redis_connection().await?; 78 + 79 + let key_list: Option<HashSet<String>> = redis::cmd("SMEMBERS") 80 + .arg(format!( 81 + "authed-server-users:{}", 82 + ctx.guild_id() 83 + .unwrap_or(poise::serenity_prelude::GuildId(0)) 84 + .as_u64() 85 + )) 86 + .clone() 87 + .query_async(&mut con) 88 + .await?; 89 + 90 + match key_list { 91 + None => Ok(false), 92 + Some(list) if !list.contains(&format!("{}", ctx.author().id.as_u64())) => { 93 + Ok({ 94 + ctx.say("You are not authorized to use this command! Please contact a bot admin or Azuki!").await?; 95 + false 96 + }) 97 + } 98 + Some(_list) => Ok(true), 99 + } 100 + }
+57
src/lib/enums.rs
··· 1 + use poise::serenity_prelude::{self as serenity}; 2 + 3 + #[derive(Debug, poise::ChoiceParameter)] 4 + pub enum WaifuTypes { 5 + Neko, 6 + Megumin, 7 + Bully, 8 + Cuddle, 9 + Cry, 10 + Kiss, 11 + Lick, 12 + Pat, 13 + Smug, 14 + Bonk, 15 + Blush, 16 + Smile, 17 + Wave, 18 + Highfive, 19 + Handhold, 20 + Nom, 21 + Bite, 22 + Glomp, 23 + Slap, 24 + Kill, 25 + Happy, 26 + Wink, 27 + Poke, 28 + Dance, 29 + Cringe, 30 + } 31 + 32 + #[derive(Debug, Clone, Copy, Eq, PartialEq, clap::ValueEnum)] 33 + pub enum DebugLevel { 34 + Off, 35 + Some, 36 + Most, 37 + All, 38 + } 39 + 40 + impl DebugLevel { 41 + #[must_use] 42 + pub fn enabled(&self) -> bool { 43 + *self != Self::Off 44 + } 45 + } 46 + 47 + #[derive(Debug, Clone, Copy, Eq, PartialEq, clap::ValueEnum)] 48 + pub enum LogDebugLevel { 49 + Most, 50 + All, 51 + } 52 + 53 + pub enum CloseTicketFail { 54 + False, 55 + IncorrectCategory, 56 + SerenityError(serenity::Error), 57 + }
+574
src/lib/event_handlers.rs
··· 1 + use crate::structs::{GuildSettings, UserInfo, WaybackResponse, WaybackStatus}; 2 + use crate::utils::snowflake_to_unix; 3 + use crate::vars::FBT_GUILD_ID; 4 + use chrono::NaiveDateTime; 5 + use chrono::Utc; 6 + use chrono_tz::Australia::Melbourne; 7 + use colored::Colorize; 8 + use poise::serenity_prelude::{self as serenity, ChannelId, Colour, MessageUpdateEvent}; 9 + use rand::Rng; 10 + use std::collections::HashMap; 11 + use tracing::{event, Level}; 12 + 13 + // TODO: Change to the ID of a channel you want all DMs sent to the bot to be relayed to 14 + const DM_CHANNEL_ID: u64 = 0000000000000000000; 15 + 16 + /// If enabled on a server it will warn them on black listed users joining 17 + /// 18 + /// # Panics 19 + /// 20 + /// Panics if unable to parse channel ID from DB to u64. 21 + /// 22 + /// # Errors 23 + /// 24 + /// This function will return an error if; 25 + /// - Fails to contact redis DB. 26 + /// - Fails to get guild settings from DB. 27 + /// - Fails to ask Redis for coresponding DB entry for user. 28 + /// - Fails to send message to channel. 29 + #[cfg(feature = "database")] 30 + pub async fn bl_warner( 31 + ctx: &serenity::Context, 32 + member: &serenity::Member, 33 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 34 + use crate::utils::open_redis_connection; 35 + 36 + let mut con = open_redis_connection().await?; 37 + 38 + let guild_settings_json_in: Option<String> = redis::cmd("JSON.GET") 39 + .arg(format!("guild-settings:{}", member.guild_id.as_u64())) 40 + .clone() 41 + .query_async(&mut con) 42 + .await?; 43 + 44 + let if_on_bl: Option<String> = redis::cmd("JSON.GET") 45 + .arg(format!("user:{}", member.user.id.as_u64())) 46 + .clone() 47 + .query_async(&mut con) 48 + .await?; 49 + 50 + match if_on_bl { 51 + None => {} 52 + Some(user_json) => { 53 + match guild_settings_json_in { 54 + None => {} // Do nothing 55 + // Check guild settings 56 + Some(server_json) => { 57 + let settings: GuildSettings = serde_json::from_str(&server_json)?; 58 + let user: UserInfo = serde_json::from_str(&user_json)?; 59 + 60 + ChannelId::from(settings.channel_id.parse::<u64>().unwrap()) 61 + .say( 62 + ctx, 63 + format!( 64 + "<@{}>/{0} Just joined your server with {} offenses on record", 65 + user.discord_id.unwrap(), 66 + user.offences.len() 67 + ), 68 + ) 69 + .await?; 70 + } 71 + } 72 + } 73 + } 74 + 75 + Ok(()) 76 + } 77 + 78 + /// Checks if server has alt protection enabled and then kicks the new member if they are >90 days old 79 + /// 80 + /// # Errors 81 + /// 82 + /// This function will return an error if; 83 + /// - Fails to connect to Redis DB. 84 + /// - Fails to serde guild settings json to `GuildSettings` struct. 85 + /// - Fails to send DM to user getting kicked. 86 + /// - Fails to actually kick member. 87 + /// 88 + #[cfg(feature = "database")] 89 + pub async fn alt_kicker( 90 + ctx: &serenity::Context, 91 + member: &serenity::Member, 92 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 93 + use crate::utils::open_redis_connection; 94 + use std::collections::HashSet; 95 + 96 + let mut con = open_redis_connection().await?; 97 + 98 + let whitelist: HashSet<String> = redis::cmd("SMEMBERS") 99 + .arg("kick-whitelist") 100 + .clone() 101 + .query_async(&mut con) 102 + .await?; 103 + 104 + if whitelist.contains(&member.user.id.0.to_string()) { 105 + return Ok(()); // Don't kick whitelisted users 106 + } 107 + 108 + let guild_settings_json_in: Option<String> = redis::cmd("JSON.GET") 109 + .arg(format!("guild-settings:{}", member.guild_id.as_u64())) 110 + .clone() 111 + .query_async(&mut con) 112 + .await?; 113 + 114 + match guild_settings_json_in { 115 + None => {} // Do nothing 116 + // Check guild settings 117 + Some(json_in) => { 118 + let settings: GuildSettings = serde_json::from_str(&json_in)?; 119 + // Is kicking enabled? 120 + if settings.kick { 121 + let uid = *member.user.id.as_u64(); 122 + 123 + // Trying to handle the pfp here to see if it catches more or maybe most alts really do have the same pfp 124 + let pfp = member 125 + .avatar_url() 126 + .unwrap_or_else(|| { 127 + "https://discord.com/assets/1f0bfc0865d324c2587920a7d80c609b.png" 128 + .to_string() 129 + }) 130 + .clone(); 131 + 132 + let unix_timecode = snowflake_to_unix(u128::from(uid)); 133 + 134 + #[allow(clippy::pedantic)] 135 + // it literally only take's i64, no need to warn about truncation here. 136 + let date_time_stamp = NaiveDateTime::from_timestamp_opt(unix_timecode as i64, 0) 137 + .unwrap_or(NaiveDateTime::MIN); 138 + 139 + let age = chrono::Utc::now() 140 + .naive_utc() 141 + .signed_duration_since(date_time_stamp) 142 + .num_days(); 143 + 144 + // Compare user age 145 + if !age.ge(&90_i64) { 146 + member.user.direct_message(ctx.http.clone(), |f| { 147 + f.content("It looks like your account is under 90 days old, or has been detected as a potential alt. You have been kick from the server!\nYou have not been banned, feel free to join back when your account is over 90 days old.\nRun the `about` slash command or send `help in this chat to find out more.") 148 + }).await?; 149 + member 150 + .kick_with_reason( 151 + ctx.http.clone(), 152 + &format!("Potential alt detected, account was {age:.0} day(s) old"), 153 + ) 154 + .await?; 155 + 156 + let colour = &mut rand::thread_rng().gen_range(0..10_000_000); 157 + 158 + ChannelId(settings.channel_id.parse::<u64>()?) 159 + .send_message(ctx.http.clone(), |f| { 160 + f.embed(|e| { 161 + e.title("Alt kicked!") 162 + .description(format!( 163 + "Potential alt detected, account was {:.0} day(s) old", 164 + age 165 + )) 166 + .thumbnail(pfp) 167 + .field("User ID", uid, true) 168 + .field("Name", member.user.name.clone(), true) 169 + .color(Colour::new(*colour)) 170 + }) 171 + }) 172 + .await?; 173 + } 174 + } 175 + } 176 + } 177 + Ok(()) 178 + } 179 + 180 + /// Sends all recieved DMs into a specified channel 181 + /// 182 + /// # Errors 183 + /// 184 + /// This function will return an error if; 185 + /// - Fails to handle message attachments. 186 + /// - Fails to handle message stickers. 187 + /// - Fails to send request to wayback machine. 188 + /// - Fails to send message to DM channel. 189 + // TODO: Handle attachments, list of links? 190 + pub async fn handle_dms( 191 + event: &serenity::Message, 192 + ctx: &serenity::Context, 193 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 194 + if !event.author.bot { 195 + let message = event.clone(); 196 + let uid = *message.author.id.as_u64(); 197 + 198 + let icon = message.author.avatar_url().map_or_else( 199 + || "https://discord.com/assets/1f0bfc0865d324c2587920a7d80c609b.png".to_string(), 200 + |url| url, 201 + ); 202 + 203 + let cache = ctx.http.clone(); 204 + 205 + let colour = &mut rand::thread_rng().gen_range(0..10_000_000); 206 + 207 + let now = Utc::now().with_timezone(&Melbourne); 208 + 209 + let local_time = now.to_string(); 210 + 211 + let timestamp = local_time.to_string(); 212 + 213 + let mut wayback_job_ids = Vec::new(); 214 + 215 + let list_of_files = if message.attachments.is_empty() | message.sticker_items.is_empty() { 216 + "N/A".to_string() 217 + } else { 218 + let mut urls = Vec::new(); 219 + 220 + handle_files(&message, &mut wayback_job_ids, &mut urls).await?; 221 + 222 + // Duped code for stickers, could probably refactor into function 223 + handle_stickers(&message, ctx, &mut wayback_job_ids, &mut urls).await?; 224 + 225 + urls.join("\n \n") 226 + }; 227 + 228 + let mut msg = ChannelId(DM_CHANNEL_ID) 229 + .send_message(cache, |f| { 230 + f.embed(|e| { 231 + e.title("New message:") 232 + .description(message.content.clone()) 233 + .field("Attachments/Stickers:", list_of_files.clone(), false) 234 + .field("User ID", uid, false) 235 + .field("Recieved at:", timestamp.clone(), false) 236 + .author(|a| a.icon_url(icon.clone()).name(message.author.name.clone())) 237 + .color(Colour::new(*colour)) 238 + }) 239 + }) 240 + .await?; 241 + 242 + let mut wayback_urls: Vec<String> = Vec::new(); 243 + 244 + for job in wayback_job_ids { 245 + let mut is_not_done = true; 246 + while is_not_done { 247 + let client = reqwest::Client::new(); 248 + 249 + // TODO: Change to your own wayback machine authorization key 250 + let response = client 251 + .get(format!("https://web.archive.org/save/status/{job}")) 252 + .header("Accept", "application/json") 253 + .header("Authorization", "LOW asdgasdg:fasfaf") // auth key here!! 254 + .send() 255 + .await?; 256 + let response_content = response.text().await?; 257 + let wayback_status: WaybackStatus = serde_json::from_str(&response_content)?; 258 + 259 + if wayback_status.status == *"success" { 260 + wayback_urls.push(format!( 261 + "https://web.archive.org/web/{}", 262 + wayback_status.original_url.unwrap_or_else(|| { 263 + "20220901093722/https://www.dafk.net/what/".to_string() 264 + }) 265 + )); 266 + is_not_done = false; 267 + } 268 + } 269 + } 270 + 271 + if !wayback_urls.is_empty() { 272 + msg.edit(ctx, |f| { 273 + f.embed(|e| { 274 + e.title("New message:") 275 + .description(message.content.clone()) 276 + .field("Attachments/Stickers:", list_of_files, false) 277 + .field( 278 + "Archived Attachments/Stickers:", 279 + wayback_urls.join("\n \n"), 280 + false, 281 + ) 282 + .field("User ID", uid, false) 283 + .field("Recieved at:", timestamp, false) 284 + .author(|a| a.icon_url(icon).name(message.author.name.clone())) 285 + .color(Colour::new(*colour)) 286 + }) 287 + }) 288 + .await?; 289 + } 290 + } 291 + 292 + Ok(()) 293 + } 294 + 295 + /// Handles DM files. 296 + /// 297 + /// # Errors 298 + /// 299 + /// This function will return an error if Failes to contact wayback machine. 300 + async fn handle_files( 301 + message: &serenity::Message, 302 + wayback_job_ids: &mut Vec<String>, 303 + urls: &mut Vec<String>, 304 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 305 + for file in message.attachments.clone() { 306 + let client = reqwest::Client::new(); 307 + 308 + let mut params = HashMap::new(); 309 + params.insert("url".to_string(), file.url.clone()); 310 + params.insert("skip_first_archive".to_string(), "1".to_string()); 311 + 312 + // TODO: Change to your own wayback machine authorization key 313 + 314 + let response = client 315 + .post("https://web.archive.org/save") 316 + .form(&params) 317 + .header("Accept", "application/json") 318 + .header("Authorization", "LOW asdgasdg:fasfaf") 319 + .send() 320 + .await?; 321 + 322 + let response_content = response.text().await?; 323 + let wayback_status: WaybackResponse = serde_json::from_str(&response_content)?; 324 + 325 + if wayback_status.status.is_none() { 326 + if let Some(jid) = wayback_status.job_id { 327 + wayback_job_ids.push(jid); 328 + } 329 + } 330 + 331 + urls.push(file.url); 332 + } 333 + Ok(()) 334 + } 335 + 336 + /// Handles DM stickers. 337 + /// 338 + /// # Errors 339 + /// 340 + /// This function will return an error if Failes to contact wayback machine. 341 + async fn handle_stickers( 342 + message: &serenity::Message, 343 + ctx: &serenity::Context, 344 + wayback_job_ids: &mut Vec<String>, 345 + urls: &mut Vec<String>, 346 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 347 + for file in message.sticker_items.clone() { 348 + let client = reqwest::Client::new(); 349 + 350 + let mut params = HashMap::new(); 351 + params.insert( 352 + "url".to_string(), 353 + file.to_sticker(ctx) 354 + .await 355 + .unwrap() 356 + .image_url() 357 + .unwrap() 358 + .clone(), 359 + ); 360 + params.insert("skip_first_archive".to_string(), "1".to_string()); 361 + 362 + // TODO: Change to your own wayback machine authorization key 363 + 364 + let response = client 365 + .post("https://web.archive.org/save") 366 + .form(&params) 367 + .header("Accept", "application/json") 368 + .header("Authorization", "LOW asdgasdg:fasfaf") 369 + .send() 370 + .await?; 371 + 372 + let response_content = response.text().await?; 373 + let wayback_status: WaybackResponse = serde_json::from_str(&response_content)?; 374 + 375 + match wayback_status.status { 376 + None => { 377 + if let Some(jid) = wayback_status.job_id { 378 + wayback_job_ids.push(jid); 379 + } 380 + } 381 + Some(_) => {} 382 + } 383 + 384 + urls.push(file.to_sticker(ctx).await.unwrap().image_url().unwrap()); 385 + } 386 + Ok(()) 387 + } 388 + 389 + /// When a message is edited in FBT this function will send the new and old message to a specified channel. 390 + /// 391 + /// # Panics 392 + /// 393 + /// Panics if an author doesn't exist, should be unreachable. 394 + /// 395 + /// # Errors 396 + /// 397 + /// This function will return an error if the message fails to send. 398 + pub async fn handle_msg_edit( 399 + event: MessageUpdateEvent, 400 + old_if_available: &Option<serenity::Message>, 401 + ctx: &serenity::Context, 402 + new: &Option<serenity::Message>, 403 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 404 + if event.guild_id.is_some() { 405 + if let Some(author) = event.author.clone() { 406 + if !author.bot { 407 + let old_message = old_if_available.as_ref().map_or_else( 408 + || "Message not stored in cache :(".to_string(), 409 + |msg| msg.content.to_string(), 410 + ); 411 + 412 + let new_message = new.as_ref().map_or_else( 413 + || "Message not stored in cache :(".to_string(), 414 + |msg| msg.content.to_string(), 415 + ); 416 + 417 + let message_url = new.as_ref().map_or_else( 418 + || "URL stored in cache :(".to_string(), 419 + poise::serenity_prelude::Message::link, 420 + ); 421 + 422 + let current_time = Utc::now().with_timezone(&Melbourne); 423 + 424 + let local_time = current_time.to_string(); 425 + 426 + let timestamp = local_time.to_string(); 427 + 428 + // TODO: channel to alert you that a message has been deleted 429 + 430 + ChannelId(891_294_507_923_025_951) 431 + .send_message(ctx.http.clone(), |f| { 432 + f.embed(|e| { 433 + e.title(format!( 434 + "\"{}\" Edited a message", 435 + event.author.clone().unwrap().tag() 436 + )) 437 + .field("Old message content:", old_message, false) 438 + .field("New message content:", new_message, false) 439 + .field("Link:", message_url, false) 440 + .field("Edited at:", timestamp, false) 441 + .footer(|f| { 442 + f.text(format!( 443 + "User ID: {}", 444 + event.author.clone().unwrap().id.as_u64() 445 + )) 446 + }) 447 + .color(Colour::new(0x00FA_A81A)) 448 + }) 449 + }) 450 + .await?; 451 + } 452 + } 453 + }; 454 + Ok(()) 455 + } 456 + 457 + /// Handles messages that have been deleted 458 + /// 459 + /// # Panics 460 + /// 461 + /// Panics if there is no message object in cache. 462 + /// 463 + /// # Errors 464 + /// 465 + /// This function will return an error if unable to send message to channel. 466 + pub async fn handle_msg_delete( 467 + guild_id: &Option<serenity::GuildId>, 468 + ctx: &serenity::Context, 469 + channel_id: &ChannelId, 470 + deleted_message_id: &serenity::MessageId, 471 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 472 + match guild_id { 473 + None => {} 474 + Some(gid) => { 475 + // TODO: this logs any delted message in FBT specifically, change to your own server ID 476 + if *gid.as_u64() == 737_168_134_502_350_849 { 477 + match ctx.cache.message(channel_id, deleted_message_id) { 478 + None => {} 479 + Some(msg) => { 480 + if !msg.author.bot { 481 + let message = match ctx.cache.message(channel_id, deleted_message_id) { 482 + None => "Message not stored in cache :(".to_string(), 483 + Some(msg) => format!("{:?}", msg.content), 484 + }; 485 + 486 + let author_id = match message.as_str() { 487 + "Message not stored in cache :(" => 0_u64, 488 + _ => *ctx 489 + .cache 490 + .message(channel_id, deleted_message_id) 491 + .unwrap() 492 + .author 493 + .id 494 + .as_u64(), 495 + }; 496 + 497 + let author_tag = 498 + if message.clone().as_str() == "Message not stored in cache :(" { 499 + "Not in cache#000".to_string() 500 + } else { 501 + format!( 502 + "{:?}", 503 + match ctx.cache.message(channel_id, deleted_message_id) { 504 + Some(msg) => { 505 + msg.author.tag() 506 + } 507 + None => { 508 + String::new() // This just creates "" 509 + } 510 + } 511 + ) 512 + }; 513 + 514 + let now = Utc::now().with_timezone(&Melbourne); 515 + 516 + let local_time = now.to_string(); 517 + 518 + let timestamp = local_time.to_string(); 519 + 520 + // TODO: This is the channel the deleted messages are sent to 521 + 522 + ChannelId(891_294_507_923_025_951) 523 + .send_message(ctx.http.clone(), |f| { 524 + f.embed(|e| { 525 + e.title(format!("{author_tag} deleted a message")) 526 + .field("Message content:", message, false) 527 + .field("Deleted at:", timestamp, false) 528 + .field( 529 + "Channel link:", 530 + format!( 531 + "https://discord.com/channels/{}/{}", 532 + guild_id 533 + .unwrap_or(serenity::GuildId::from( 534 + FBT_GUILD_ID 535 + )) 536 + .as_u64(), 537 + channel_id.as_u64() 538 + ), 539 + false, 540 + ) 541 + .footer(|f| f.text(format!("User ID: {author_id}"))) 542 + .color(Colour::new(0x00ED_4245)) 543 + }) 544 + }) 545 + .await?; 546 + } 547 + } 548 + } 549 + } 550 + } 551 + }; 552 + Ok(()) 553 + } 554 + 555 + /// Prints message and outputs trace if in verbose mode 556 + pub fn handle_resume(event: &serenity::ResumedEvent) { 557 + event!( 558 + Level::INFO, 559 + "ResumedEvent" = format!( 560 + "{}", 561 + "Bot went offline but is online again".bright_red().italic() 562 + ) 563 + ); 564 + 565 + // Is this a good idea? 566 + event!( 567 + Level::TRACE, 568 + "ResumedEvent" = format!( 569 + "{}", 570 + "Bot went offline but is online again".bright_red().italic() 571 + ), 572 + "event" = ?event 573 + ); 574 + }
+9
src/lib/lib.rs
··· 1 + pub mod args; 2 + pub mod checks; 3 + pub mod enums; 4 + pub mod event_handlers; 5 + pub mod memes; 6 + pub mod structs; 7 + pub mod types; 8 + pub mod utils; 9 + pub mod vars;
+77
src/lib/memes.rs
··· 1 + use crate::vars::FBT_GUILD_ID; 2 + use once_cell::sync::Lazy; 3 + use poise::serenity_prelude::{self as serenity}; 4 + use regex::Regex; 5 + use strip_markdown::strip_markdown; 6 + 7 + // Some times maybe good sometimes maybe shit 8 + pub static POG_RE: Lazy<Regex> = Lazy::new(|| { 9 + Regex::new( 10 + r"[pP𝓹⍴𝖕𝔭የ𝕡ק🅟🅿ⓟρᑭ𝙥քp̷þp͎₱ᵽ℘ア𝐩𝒑𝓅p̞̈͑̚͞℘p͓̽ք𝓹ᕶp̶p̳p̅][oO0øØ𝓸᥆𝖔𝔬ዐ𝕠๏🅞🅾ⓞσOoÒօoo̷ðo͎の𝗼ᴏᵒ🇴‌𝙤ѻⲟᓍӨo͓̽o͟o̲o̅o̳o̶🄾o̯̱̊͊͢Ꭷσℴ𝒐𝐨][gG9𝓰𝖌𝔤𝕘🅖🅶ⓖɠGg𝑔ցg̷gg͎g̲g͟ǥ₲ɢg͓̽Gɠ𝓰𝙜🇬‌Ꮆᵍɢ𝗴𝐠𝒈𝑔ᧁg𝚐₲Ꮆ𝑔ĝ̽̓̀͑𝘨ງ🄶𝔤ģ]\b", 11 + ).unwrap() 12 + }); 13 + 14 + /// This will read a message and check to see if the message contains the word `pog` 15 + /// 16 + /// # Panics 17 + /// 18 + /// Panics if regex fails to compile, this should be unreachable unless I acidentally change something before compile time. 19 + /// 20 + /// # Errors 21 + /// 22 + /// This function will return an error if . 23 + pub async fn pog_be_gone( 24 + new_message: &serenity::Message, 25 + ctx: &serenity::Context, 26 + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 27 + if !new_message.author.bot && !new_message.content.is_empty() { 28 + match new_message.guild(ctx) { 29 + None => {} // Probably a DM, do nothing 30 + Some(guild) => { 31 + if guild.id.as_u64() == &FBT_GUILD_ID { 32 + let lowercase_message = new_message.content.to_lowercase(); 33 + let cleaned_message = strip_markdown(&lowercase_message); 34 + 35 + let words: Vec<&str> = cleaned_message.split(' ').collect(); 36 + let mut hits: Vec<&str> = Vec::new(); 37 + 38 + for word in words { 39 + POG_RE.find(word).map_or((), |pog| { 40 + hits.push(pog.as_str()); 41 + }); 42 + } 43 + 44 + if !hits.is_empty() { 45 + // there is at least 1 pog found 46 + if hits.capacity().gt(&10) { 47 + new_message 48 + .reply( 49 + ctx, 50 + format!( 51 + "Jesus dude, why did you pog {} times?! stop it!", 52 + hits.len() 53 + ), 54 + ) 55 + .await?; 56 + } else { 57 + new_message.reply_mention(ctx, "please refer to the rules and use the term 'poi' instead of 'pog'!").await?; 58 + } 59 + } 60 + } 61 + } 62 + } 63 + }; 64 + Ok(()) 65 + } 66 + 67 + #[cfg(test)] 68 + mod meme_tests { 69 + use super::*; 70 + 71 + #[test] 72 + fn test_regex() { 73 + let pog_test = "pog"; 74 + 75 + assert!(POG_RE.is_match(pog_test)); 76 + } 77 + }
+157
src/lib/structs.rs
··· 1 + use merge::Merge; 2 + use serde::{Deserialize, Serialize}; 3 + use serde_with::{As, FromInto}; 4 + 5 + // User data, which is stored and accessible in all command invocations 6 + #[derive(Debug)] 7 + pub struct Data {} 8 + 9 + #[allow(non_snake_case)] 10 + #[derive(Debug, Deserialize, PartialEq, Hash, Eq)] 11 + pub struct CsvEntry { 12 + pub AuthorID: String, 13 + pub Author: String, 14 + } 15 + 16 + #[derive(Debug, Serialize, Deserialize, PartialEq, Merge, Clone, Hash, Eq)] 17 + pub struct UserInfo { 18 + #[serde(with = "As::<FromInto<OptionalString2>>")] 19 + pub vrc_id: Option<String>, 20 + #[serde(with = "As::<FromInto<OptionalString>>")] 21 + pub username: Option<String>, 22 + #[serde(with = "As::<FromInto<OptionalString2>>")] 23 + pub discord_id: Option<String>, 24 + #[merge(strategy = merge::vec::append)] 25 + pub offences: Vec<Offense>, 26 + } 27 + 28 + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Hash, Eq)] 29 + pub struct GuildSettings { 30 + pub channel_id: String, 31 + pub kick: bool, 32 + pub server_name: String, 33 + } 34 + 35 + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Merge, Hash, Eq)] 36 + pub struct Offense { 37 + #[merge(skip)] 38 + pub guild_id: String, 39 + #[merge(skip)] 40 + pub reason: String, 41 + #[serde(with = "As::<FromInto<OptionalString2>>")] 42 + pub image: Option<String>, 43 + #[serde(with = "As::<FromInto<OptionalString2>>")] 44 + pub extra: Option<String>, 45 + } 46 + 47 + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Hash, Eq)] 48 + pub struct GuildAuthList { 49 + pub users: Vec<String>, 50 + } 51 + 52 + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Hash, Eq)] 53 + pub struct ClearedUser { 54 + pub user_id: String, 55 + pub username: String, 56 + pub where_found: String, 57 + pub reason: String, 58 + } 59 + 60 + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Hash, Eq)] 61 + pub struct MonitoredGuildInfo { 62 + pub guild_name: String, 63 + pub guild_id: String, 64 + pub invite_link: String, 65 + pub updated: String, 66 + pub status: String, 67 + } 68 + 69 + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Hash, Eq)] 70 + pub struct BlacklistHit { 71 + pub user_id: String, 72 + pub username: String, 73 + pub guild_id: String, 74 + pub reason: String, 75 + pub image: String, 76 + pub extra: String, 77 + } 78 + 79 + #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 80 + #[serde(rename_all = "camelCase")] 81 + pub struct WaybackResponse { 82 + pub url: Option<String>, 83 + #[serde(rename = "job_id")] 84 + pub job_id: Option<String>, 85 + pub message: Option<String>, 86 + pub status: Option<String>, 87 + #[serde(rename = "status_ext")] 88 + pub status_ext: Option<String>, 89 + } 90 + 91 + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 92 + #[serde(rename_all = "camelCase")] 93 + pub struct WaybackStatus { 94 + #[serde(rename = "http_status")] 95 + pub http_status: Option<i64>, 96 + #[serde(default)] 97 + outlinks: Vec<String>, 98 + pub timestamp: Option<String>, 99 + #[serde(rename = "original_url")] 100 + pub original_url: Option<String>, 101 + resources: Vec<String>, 102 + #[serde(rename = "duration_sec")] 103 + pub duration_sec: Option<f64>, 104 + pub status: String, 105 + #[serde(rename = "job_id")] 106 + pub job_id: String, 107 + pub counters: Option<Counters>, 108 + } 109 + 110 + #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 111 + #[serde(rename_all = "camelCase")] 112 + pub struct Counters { 113 + pub outlinks: i64, 114 + pub embeds: i64, 115 + } 116 + 117 + #[derive(Deserialize, Serialize)] 118 + pub struct OptionalString(pub Option<String>); 119 + 120 + impl From<OptionalString> for Option<String> { 121 + fn from(val: OptionalString) -> Self { 122 + val.0.map_or_else(|| Some("N/A".to_string()), Some) 123 + } 124 + } 125 + 126 + impl From<Option<String>> for OptionalString { 127 + fn from(val: Option<String>) -> Self { 128 + val.map_or_else(|| Self(Some("N/A".to_string())), |s| Self(Some(s))) 129 + } 130 + } 131 + 132 + #[derive(Deserialize, Serialize)] 133 + pub struct OptionalString2(pub Option<String>); 134 + 135 + impl From<OptionalString2> for Option<String> { 136 + fn from(val: OptionalString2) -> Self { 137 + val.0.map_or_else( 138 + || Some("N/A".to_string()), 139 + |s| match s.as_str() { 140 + "0" => Some("N/A".to_string()), 141 + x => Some(x.to_string()), 142 + }, 143 + ) 144 + } 145 + } 146 + 147 + impl From<Option<String>> for OptionalString2 { 148 + fn from(val: Option<String>) -> Self { 149 + val.map_or_else(|| Self(Some("N/A".to_string())), |s| Self(Some(s))) 150 + } 151 + } 152 + 153 + #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 154 + #[serde(rename_all = "camelCase")] 155 + pub struct PasteResponse { 156 + pub key: String, 157 + }
+4
src/lib/types.rs
··· 1 + use crate::structs::Data; 2 + 3 + pub type Error = Box<dyn std::error::Error + Send + Sync>; 4 + pub type Context<'a> = poise::Context<'a, Data, Error>;
+142
src/lib/utils.rs
··· 1 + use crate::args::Args; 2 + use crate::structs::GuildSettings; 3 + use crate::types::Context; 4 + use crate::types::Error; 5 + use crate::vars::REDIS_ADDR; 6 + use clap::Parser; 7 + use tracing::instrument; 8 + 9 + /// Converts a dsicord snowflake to a unix timecode 10 + #[must_use] 11 + pub const fn snowflake_to_unix(id: u128) -> u128 { 12 + const DISCORD_EPOCH: u128 = 1_420_070_400_000; 13 + 14 + ((id >> 22) + DISCORD_EPOCH) / 1000 15 + } 16 + 17 + /// Quickly checks if the verbose flag was used on launch 18 + #[must_use] 19 + pub fn verbose_mode() -> bool { 20 + let args = Args::parse(); 21 + 22 + args.verbose 23 + } 24 + 25 + /// Open a tokio redis connection 26 + #[cfg(feature = "database")] 27 + #[instrument()] 28 + pub async fn open_redis_connection() -> Result<redis::aio::Connection, anyhow::Error> { 29 + let redis_connection = redis::Client::open(REDIS_ADDR)? 30 + .get_tokio_connection() 31 + .await?; 32 + 33 + Ok(redis_connection) 34 + } 35 + 36 + /// Pushes guild settings to DB 37 + #[cfg(feature = "database")] 38 + #[instrument(skip(con))] 39 + pub async fn set_guild_settings( 40 + ctx: Context<'_>, 41 + con: &mut redis::aio::Connection, 42 + settings: GuildSettings, 43 + ) -> Result<(), Error> { 44 + let json = serde_json::to_string(&settings).unwrap(); 45 + 46 + let mut pipe = redis::pipe(); 47 + 48 + pipe.cmd("JSON.SET").arg(&[ 49 + format!( 50 + "guild-settings:{}", 51 + ctx.guild_id().expect("Not run inside guild") 52 + ), 53 + "$".to_string(), 54 + json, 55 + ]); 56 + 57 + pipe.atomic().query_async(con).await?; 58 + 59 + Ok(()) 60 + } 61 + 62 + /// Adds the user to a server's auth list in the DB 63 + #[cfg(feature = "database")] 64 + #[instrument(skip(con))] 65 + pub async fn auth( 66 + ctx: Context<'_>, 67 + con: &mut redis::aio::Connection, 68 + uid: String, 69 + ) -> Result<(), Error> { 70 + redis::cmd("SADD") 71 + .arg(&[ 72 + format!( 73 + "authed-server-users:{}", 74 + ctx.guild_id().expect("Not run inside guild") 75 + ), 76 + uid, 77 + ]) 78 + .query_async(con) 79 + .await?; 80 + 81 + Ok(()) 82 + } 83 + 84 + /// Increases the total commands run count in the DB 85 + #[cfg(feature = "database")] 86 + #[instrument] 87 + pub async fn inc_execution_count() -> Result<(), Error> { 88 + let mut con = open_redis_connection().await?; 89 + 90 + // increment status:commands-executed in redis DB 91 + redis::cmd("INCR") 92 + .arg("status:commands-executed") 93 + .query_async(&mut con) 94 + .await?; 95 + 96 + Ok(()) 97 + } 98 + 99 + #[cfg(feature = "database")] 100 + #[instrument] 101 + pub async fn is_uid_valid_user(uid: u64, ctx: &Context<'_>) -> anyhow::Result<bool> { 102 + let u_opt: Option<poise::serenity_prelude::User> = 103 + match poise::serenity_prelude::UserId::from(uid) 104 + .to_user(ctx) 105 + .await 106 + { 107 + Ok(user) => Some(user), 108 + Err(error) => { 109 + if verbose_mode() { 110 + ctx.say(format!( 111 + "ID must be a user ID, make sure you coppied the right one! Error: {:?}", 112 + error 113 + )) 114 + .await?; 115 + } else { 116 + ctx.say("ID must be a user ID, make sure you coppied the right one!") 117 + .await?; 118 + } 119 + 120 + None 121 + } 122 + }; 123 + 124 + Ok(u_opt.is_some()) 125 + } 126 + 127 + #[cfg(test)] 128 + mod utils_tests { 129 + 130 + use crate::utils::{snowflake_to_unix, verbose_mode}; 131 + 132 + #[test] 133 + fn snowflake_unix_test() { 134 + assert_eq!(snowflake_to_unix(383_507_911_160_233_985), 1_511_505_811); 135 + } 136 + 137 + #[test] 138 + fn verbose_mode_test() { 139 + // Inverting output since verbose mode is disabled by default 140 + assert!(!verbose_mode()); 141 + } 142 + }
+90
src/lib/vars.rs
··· 1 + pub const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); 2 + 3 + pub const HELP_EXTRA_TEXT: &str = "Find the documentation website at https://fbtsecurity.fbtheaven.com/\nRun the About command to find out more (/about)"; 4 + 5 + // TODO: change this list to your own bot admin user IDs 6 + 7 + // You need to increase the number in [u64; X] so rust knows the limit of the array 8 + pub const BOT_ADMINS: [u64; 6] = [ 9 + 212_132_817_017_110_528, 10 + 288_186_677_967_585_280, 11 + 211_027_317_068_136_448, 12 + 383_507_911_160_233_985, 13 + 168_600_506_233_651_201, 14 + 231_482_341_921_521_664, 15 + ]; // Azuki, Komi, Xeno, Mojo, Ellie, Wundie 16 + 17 + // TODO: you can mass replace the name of this variable easily 18 + // TODO: change to your own guild ID 19 + 20 + pub const FBT_GUILD_ID: u64 = 737_168_134_502_350_849; // FBT's guild ID 21 + 22 + // TODO: this is the channel wehre the feedback command sends it's response for you to read 23 + pub const FEEDBACK_CHANNEL_ID: u64 = 925_599_477_283_311_636; 24 + 25 + //pub const FBT_GUILD_ID: u64 = 838658675916275722; // My test server ID 26 + 27 + // TODO: you need your own Redis DB, this is where you put in the login details and adress of the DB 28 + // format: "redis://USERNAME:PASSWORD@ADDRESS:PORT/DB_INDEX" 29 + 30 + #[cfg(feature = "database")] 31 + pub const REDIS_ADDR: &str = 32 + "redis://:ForSureARealRedisPassword@google.com:6379/0"; 33 + 34 + // TODO: change to your own Meilisearch address 35 + #[cfg(feature = "database")] 36 + pub const MEILISEARCH_HOST: &str = "http://google.com:7777"; 37 + 38 + // TODO: change to your own Meilisearch API key 39 + #[cfg(feature = "database")] 40 + pub const MEILISEARCH_API_KEY: &str = "why-so-strange"; 41 + 42 + // TODO: change to your own bot token 43 + pub const BOT_TOKEN: &str = 44 + "not touching this <3"; 45 + 46 + //TODO: these are popular discord bots, used to ignore their messages and stuff 47 + // Part of blacklist for now but I should add it as a check to the excel command too 48 + #[cfg(feature = "database")] 49 + pub const BOT_IDS: [u64; 22] = [ 50 + 134_133_271_750_639_616, 51 + 155_149_108_183_695_360, 52 + 159_985_870_458_322_944, 53 + 159_985_870_458_322_944, 54 + 184_405_311_681_986_560, 55 + 204_255_221_017_214_977, 56 + 216_437_513_709_944_832, 57 + 235_088_799_074_484_224, 58 + 235_148_962_103_951_360, 59 + 294_882_584_201_003_009, 60 + 351_227_880_153_546_754, 61 + 375_805_687_529_209_857, 62 + 537_429_661_139_861_504, 63 + 550_613_223_733_329_920, 64 + 559_426_966_151_757_824, 65 + 583_995_825_269_768_211, 66 + 625_588_618_525_802_507, 67 + 649_535_344_236_167_212, 68 + 743_269_383_438_073_856, 69 + 743_269_383_438_073_856, 70 + 887_914_294_988_140_565, 71 + 935_372_708_089_315_369, 72 + ]; 73 + 74 + // TODO: this is for the ticket system, change to your own ticket category ID. 75 + // it creates new threads in TICKET_CATEGORY and moves them to CLOSED_TICKET_CATEGORY once closed 76 + pub const TICKET_CATEGORY: u64 = 982_769_870_259_240_981; 77 + pub const CLOSED_TICKET_CATEGORY: u64 = 983_228_142_107_918_336; 78 + 79 + #[cfg(feature = "database")] 80 + #[derive(Debug, poise::ChoiceParameter)] 81 + pub enum BlacklistOutput { 82 + #[name = "Chat - Output resulting @, ID and Reasons to chat"] 83 + Chat, 84 + #[name = "Compact Chat - Only send resulting @ and IDs"] 85 + CompactChat, 86 + #[name = "CSV - Output all relevant info as a single .csv file"] 87 + Csv, 88 + #[name = "Json - Output all relevant info as a single .json file"] 89 + Json, 90 + }
+464
src/main.rs
··· 1 + #![forbid(unsafe_code)] 2 + 3 + //! Please increade the version in the Cargo.toml file by 0.0.1 for &&every minor commit or command and by 0.1.0 for any majoy function rewrite or implamentation 4 + 5 + // TODO: Add ticket ssytem 6 + // ? /close_ticket could check a DB list to see if it contains the channel ID and if it does then close? 7 + // ? If we wan't more info we can store each ticket as a json file and then only close if an entry for the channel exists in DB 8 + 9 + use clap::Parser; 10 + use colored::Colorize; 11 + use commands::database::remove_guild; 12 + use poise::builtins::register_application_commands_buttons; 13 + use poise::serenity_prelude::{self as serenity, ChannelId, Colour, UserId}; 14 + use serenity::model::gateway::Activity; 15 + use serenity::model::user::OnlineStatus; 16 + use std::collections::HashSet; 17 + use std::fs::File; 18 + use tracing::instrument; 19 + use tracing::metadata::LevelFilter; 20 + use tracing::{event, Level}; 21 + use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer}; 22 + 23 + #[macro_use] 24 + extern crate maplit; 25 + 26 + #[cfg(feature = "database")] 27 + use rusted_fbt_lib::utils::open_redis_connection; 28 + use rusted_fbt_lib::vars::FBT_GUILD_ID; 29 + // Import everything from the commands folder 30 + mod commands; 31 + use commands::admin::{ 32 + announcement, authorize, ban, botmsg, request_setup, setup, shutdown, toggle_kick, 33 + }; 34 + #[cfg(feature = "database")] 35 + use commands::database::{ 36 + add, excel, footprint_lookup, key, search, update_search_engine, whitelist, 37 + }; 38 + use commands::fun::{cringe, owo, ping, pog, toxic, waifu}; 39 + use commands::info::{about, feedback, help}; 40 + use commands::tickets::{close_ticket, new_ticket}; 41 + use commands::tools::{account_age, bot_owner_tool_1, creation_date}; 42 + 43 + // New rust librabry to never leave this reposity :D 44 + use rusted_fbt_lib::args::Args; 45 + use rusted_fbt_lib::checks::bot_admin_check; 46 + use rusted_fbt_lib::enums::{DebugLevel, LogDebugLevel}; 47 + use rusted_fbt_lib::event_handlers::{ 48 + alt_kicker, bl_warner, handle_dms, handle_msg_delete, handle_msg_edit, handle_resume, 49 + }; 50 + use rusted_fbt_lib::memes::pog_be_gone; 51 + use rusted_fbt_lib::structs::{Data, PasteResponse}; 52 + use rusted_fbt_lib::types::{Context, Error}; 53 + use rusted_fbt_lib::utils::inc_execution_count; 54 + 55 + use crate::commands::tools::invite_info; 56 + 57 + /// Register application commands in this guild or globally 58 + /// 59 + /// Run with no arguments to register in guild, run with argument "global" to register globally. 60 + #[instrument(skip(ctx))] 61 + #[poise::command(prefix_command, slash_command, hide_in_help, owners_only)] 62 + async fn register(ctx: Context<'_>) -> Result<(), Error> { 63 + register_application_commands_buttons(ctx).await?; 64 + event!(Level::INFO, "Commandwhere registered"); 65 + Ok(()) 66 + } 67 + 68 + /// Custom error handeling 69 + #[instrument] 70 + async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { 71 + // This is our custom error handler 72 + // They are many errors that can occur, so we only handle the ones we want to customize 73 + // and forward the rest to the default handler 74 + match error { 75 + // allow unused_variables because we don't use all the variables 76 + #[allow(unused_variables)] 77 + poise::FrameworkError::Setup { 78 + error, 79 + framework, 80 + data_about_bot, 81 + ctx, 82 + } => { 83 + // Log failed bot setup 84 + event!(Level::ERROR, "Bot setup failed" = ?error); 85 + } 86 + poise::FrameworkError::Command { error, ctx } => { 87 + event!(Level::WARN, "Error in command" = ?ctx.command(), "error" = ?error); 88 + } 89 + poise::FrameworkError::CommandCheckFailed { error, ctx } => { 90 + if ctx.command().name.as_str() == "setup" { 91 + ctx.send(|m| { 92 + m.content("If you can't run this because you don't have the correct permissions then please ask a local admin to run `/request_setup` or `/authorize`!\nAn admin will come and check out your server ASAP after `/request_setup` is executed.") 93 + .ephemeral(true) 94 + }).await 95 + .expect("Failed to tell user about request_setup during error handeling"); 96 + } 97 + 98 + event!(Level::INFO, "CommandCheckFailed" = ?ctx.command(), "error" = ?error); 99 + } 100 + poise::FrameworkError::MissingBotPermissions { 101 + missing_permissions, 102 + ctx, 103 + } => { 104 + ctx.say(format!("I'm currently missing the follow permission(s) required to execute this command:\n\n```{}```\n\nPlease ask a local server admin to fix this in my bot role!", missing_permissions.get_permission_names().join("\n"))).await 105 + .expect("Unable to tell a server what permissions I am missing!"); 106 + } 107 + poise::FrameworkError::CooldownHit { 108 + remaining_cooldown, 109 + ctx, 110 + } => { 111 + ctx.send(|m| { 112 + m.content(format!( 113 + "You are on cooldown try again in {} seconds, moron!", 114 + remaining_cooldown.as_secs() 115 + )) 116 + .ephemeral(true) 117 + }) 118 + .await 119 + .expect("Failed to meme on someone for running a command while on cooldown"); 120 + } 121 + error => { 122 + if let Err(e) = poise::builtins::on_error(error).await { 123 + event!(Level::WARN, info = "Error while handling error (ironic)", error = ?e); 124 + } 125 + } 126 + } 127 + } 128 + 129 + /// Handle events here, should move anything that isn't just println! to a seperate function to avoid the mess that was `on_message` in the python version 130 + #[instrument(skip(ctx, _framework, event, _user_data))] 131 + async fn event_listener( 132 + ctx: &serenity::Context, 133 + event: &poise::Event<'_>, 134 + _framework: poise::FrameworkContext<'_, Data, Error>, 135 + _user_data: &Data, 136 + ) -> Result<(), Error> { 137 + match event { 138 + poise::Event::Ready { data_about_bot } => { 139 + println!( 140 + "{} {}{}", 141 + "Bot is now online as".color("Purple"), 142 + data_about_bot.user.name.bright_cyan().bold().underline(), 143 + "!".color("Purple") 144 + ); 145 + 146 + let activity = Activity::playing( 147 + "use /help to see all commands. /request_setup to request extra admin features.", 148 + ); 149 + let status = OnlineStatus::Online; 150 + 151 + // TODO: Store and get from DB so we can change it later and keep it consistent between boots 152 + ctx.set_presence(Some(activity), status).await; 153 + } 154 + poise::Event::Resume { event } => { 155 + handle_resume(event); 156 + } 157 + poise::Event::Message { new_message } => { 158 + if new_message.is_private() { 159 + handle_dms(new_message, ctx).await?; 160 + } else { 161 + pog_be_gone(new_message, ctx).await?; 162 + } 163 + } 164 + poise::Event::GuildMemberAddition { new_member } => { 165 + #[cfg(feature = "database")] 166 + alt_kicker(ctx, new_member).await?; 167 + 168 + #[cfg(feature = "database")] 169 + bl_warner(ctx, new_member).await?; 170 + } 171 + poise::Event::MessageDelete { 172 + channel_id, 173 + deleted_message_id, 174 + guild_id, 175 + } => { 176 + handle_msg_delete(guild_id, ctx, channel_id, deleted_message_id).await?; 177 + } 178 + poise::Event::MessageUpdate { 179 + old_if_available, 180 + new, 181 + event, 182 + } => { 183 + if let Some(n) = new.clone() { 184 + // TODO: put your own guild ID here, this is for tracking message edits 185 + if !n.is_private() && *event.guild_id.unwrap().as_u64() == 737_168_134_502_350_849 { 186 + // I need to learn the overall benifit to using `if let` 187 + handle_msg_edit(event.clone(), old_if_available, ctx, new).await?; 188 + } 189 + } 190 + } 191 + poise::Event::ChannelDelete { channel } => { 192 + if *channel.guild_id.as_u64() == FBT_GUILD_ID { 193 + let messages = ctx.cache.channel_messages_field(channel.id.0, |s| { 194 + s.filter_map(|m| { 195 + if m.channel_id.0 == channel.id.0 { 196 + Some(m.clone()) 197 + } else { 198 + None 199 + } 200 + }) 201 + .collect::<Vec<_>>() 202 + }); 203 + 204 + // let messages = match channel.messages(ctx, |b| b.limit(100)).await { 205 + // Ok(vec) => format!("{:?}", vec), 206 + // Err(e) => format!("{:?}", e), 207 + // }; 208 + 209 + let shit_list = format!( 210 + "Stored channel info:\n\n{:?}\n\nLast 100 messages stored in cache:\n\n{:#?}", 211 + channel, messages 212 + ); 213 + 214 + let client = reqwest::Client::new(); 215 + 216 + // TODO: I setup a custom paste bin here uhh you can figure out how to reaplce it or just comment out the poise::Event::ChannelDelete event 217 + 218 + let response = client 219 + .post("https://paste.buymymojo.net/documents") 220 + .body(shit_list) 221 + .header("Accept", "application/json") 222 + .send() 223 + .await?; 224 + 225 + let response_content = response 226 + .text() 227 + .await 228 + .unwrap_or_else(|_| "{'key': 'nope<3'}".to_string()); 229 + 230 + let id: PasteResponse = serde_json::from_str(&response_content)?; 231 + 232 + // TODO: channel to alert users that a channel has been deleted 233 + 234 + ChannelId(891_294_507_923_025_951) 235 + .send_message(ctx.http.clone(), |f| { 236 + f.embed(|e| { 237 + e.title("A channel has been deleted".to_string()) 238 + .field( 239 + "The last cached info from the channel:", 240 + format!("https://paste.buymymojo.net/{}", id.key), 241 + false, 242 + ) 243 + .color(Colour::new(0x00ED_4245)) 244 + }) 245 + }) 246 + .await?; 247 + } 248 + } 249 + poise::Event::CacheReady { guilds } => { 250 + let args = Args::parse(); 251 + 252 + if args.print_guild_cache { 253 + event!(Level::INFO, "Cache is ready"); 254 + } else { 255 + event!(Level::INFO, info = "Cache is ready", guilds = ?guilds); 256 + } 257 + } 258 + _ => {} 259 + } 260 + 261 + Ok(()) 262 + } 263 + 264 + #[tokio::main] 265 + async fn main() { 266 + let args = Args::parse(); 267 + 268 + let console_level = match args.debug { 269 + DebugLevel::Off => LevelFilter::ERROR, 270 + DebugLevel::Some => LevelFilter::WARN, 271 + DebugLevel::Most => LevelFilter::INFO, 272 + DebugLevel::All => LevelFilter::TRACE, 273 + }; 274 + 275 + let file_level = match args.debug_log { 276 + LogDebugLevel::Most => LevelFilter::DEBUG, 277 + LogDebugLevel::All => LevelFilter::TRACE, 278 + }; 279 + 280 + let console_layer = tracing_subscriber::fmt::layer() 281 + .with_line_number(true) 282 + .with_ansi(true) 283 + .with_thread_names(true) 284 + .with_target(true) 285 + .with_filter(console_level); 286 + let file_layer = if args.debug.enabled() { 287 + match File::create( 288 + std::path::Path::new(&std::env::current_dir().unwrap()).join(format!( 289 + "./{}_rusted-fbt.verbose.log", 290 + chrono::offset::Local::now().timestamp() 291 + )), 292 + ) { 293 + Ok(handle) => { 294 + let file_log = tracing_subscriber::fmt::layer() 295 + .with_line_number(true) 296 + .with_ansi(false) 297 + .with_thread_names(true) 298 + .with_target(true) 299 + .with_writer(handle) 300 + .with_filter(file_level); 301 + Some(file_log) 302 + } 303 + Err(why) => { 304 + eprintln!("ERROR!: Unable to create log output file: {why:?}"); 305 + None 306 + } 307 + } 308 + } else { 309 + None 310 + }; 311 + 312 + let info_file_layer = if args.debug.enabled() { 313 + match File::create( 314 + std::path::Path::new(&std::env::current_dir().unwrap()).join(format!( 315 + "./{}_rusted-fbt.info.log", 316 + chrono::offset::Local::now().timestamp() 317 + )), 318 + ) { 319 + Ok(handle) => { 320 + let file_log = tracing_subscriber::fmt::layer() 321 + .with_line_number(true) 322 + .with_ansi(false) 323 + .with_thread_names(true) 324 + .with_target(true) 325 + .with_writer(handle) 326 + .with_filter(LevelFilter::INFO); 327 + Some(file_log) 328 + } 329 + Err(why) => { 330 + eprintln!("ERROR!: Unable to create log output file: {why:?}"); 331 + None 332 + } 333 + } 334 + } else { 335 + None 336 + }; 337 + 338 + tracing_subscriber::registry() 339 + .with(console_layer) 340 + .with(file_layer) 341 + .with(info_file_layer) 342 + .init(); 343 + 344 + // TODO: Like bot admins, put your own IDs here 345 + 346 + let bot_owners: HashSet<UserId> = hashset! { 347 + UserId::from(212_132_817_017_110_528), // Azuki 348 + UserId::from(164_694_510_947_794_944), // Cross 349 + UserId::from(383_507_911_160_233_985), // Mojo 350 + }; 351 + 352 + // * This is where we put the functions that we want in discord 353 + #[allow(unused_mut)] 354 + let mut discord_commands = vec![ 355 + about(), 356 + account_age(), 357 + ban(), 358 + botmsg(), 359 + creation_date(), 360 + cringe(), 361 + help(), 362 + owo(), 363 + ping(), 364 + pog(), 365 + register(), 366 + shutdown(), 367 + toxic(), 368 + waifu(), 369 + new_ticket(), 370 + close_ticket(), 371 + bot_owner_tool_1(), 372 + ]; 373 + 374 + // * Any command that requires the DB goes here 375 + #[cfg(feature = "database")] 376 + { 377 + let mut db_vec = vec![ 378 + add(), 379 + announcement(), 380 + authorize(), 381 + footprint_lookup(), 382 + excel(), 383 + feedback(), 384 + remove_guild(), 385 + whitelist(), 386 + request_setup(), 387 + search(), 388 + setup(), 389 + // sqlite_transfer(), // Deprecated 390 + toggle_kick(), 391 + update_search_engine(), 392 + key(), 393 + invite_info(), 394 + ]; 395 + 396 + discord_commands.append(&mut db_vec); 397 + } 398 + 399 + // * Any command that are not complete/working here 400 + #[cfg(feature = "beta")] 401 + { 402 + let mut beta_vec = vec![]; 403 + 404 + discord_commands.append(&mut beta_vec); 405 + } 406 + 407 + let framework = poise::Framework::builder() 408 + .options(poise::FrameworkOptions { 409 + commands: discord_commands, 410 + prefix_options: poise::PrefixFrameworkOptions { 411 + prefix: Some(args.prefix), 412 + ..Default::default() 413 + }, 414 + // The global error handler for all error cases that may occur 415 + on_error: |error| Box::pin(on_error(error)), 416 + event_handler: |ctx, event, framework, user_data| { 417 + Box::pin(event_listener(ctx, event, framework, user_data)) 418 + }, 419 + owners: bot_owners, 420 + // Every command invocation must pass this check to continue execution 421 + #[cfg(feature = "database")] 422 + command_check: Some(|ctx| { 423 + Box::pin(async move { 424 + if bot_admin_check(ctx).await.unwrap() { 425 + return Ok(true); 426 + } 427 + 428 + let mut con = open_redis_connection().await?; 429 + 430 + let key_list: HashSet<String> = redis::cmd("SMEMBERS") 431 + .arg("user-lists:banned-from-bot") 432 + .clone() 433 + .query_async(&mut con) 434 + .await?; 435 + 436 + if key_list.contains(&format!("{}", ctx.author().id.as_u64())) { 437 + Ok({ 438 + println!( 439 + "{}/{} was blocked from using the bot", 440 + ctx.author().id, 441 + ctx.author().name 442 + ); 443 + false 444 + }) 445 + } else { 446 + Ok(true) 447 + } 448 + }) 449 + }), 450 + #[cfg(feature = "database")] 451 + post_command: |_ctx| { 452 + Box::pin(async move { 453 + inc_execution_count().await.expect(""); 454 + }) 455 + }, 456 + ..Default::default() 457 + }) 458 + .token(args.token) 459 + .intents(serenity::GatewayIntents::all()) 460 + .setup(move |_ctx, _ready, _framework| Box::pin(async move { Ok(Data {}) })) 461 + .client_settings(|f| f.cache_settings(|cs| cs.max_messages(5_000))); 462 + 463 + framework.run_autosharded().await.unwrap(); 464 + }
+15
westmere.Dockerfile
··· 1 + FROM rust:latest as builder 2 + WORKDIR /usr/src/rusted-fbt 3 + COPY . . 4 + RUN env RUSTFLAGS="-C target-cpu=westmere" cargo install --path . 5 + 6 + FROM ubuntu:23.10 7 + # RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/* 8 + COPY --from=builder /usr/local/cargo/bin/rusted-fbt /usr/local/bin/rusted-fbt 9 + RUN apt update -y 10 + RUN apt install ca-certificates -y 11 + RUN apt update -y 12 + # This is the only dependancie missing as far as I can tell which is great 13 + RUN apt install libssl-dev -y 14 + RUN apt autoremove -y 15 + CMD ["rusted-fbt"]
+14
znver3.Dockerfile
··· 1 + FROM rust:latest as builder 2 + WORKDIR /usr/src/rusted-fbt 3 + COPY . . 4 + RUN env RUSTFLAGS="-C target-cpu=znver3" cargo install --path . 5 + 6 + FROM debian:buster-slim 7 + # RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/* 8 + COPY --from=builder /usr/local/cargo/bin/rusted-fbt /usr/local/bin/rusted-fbt 9 + run apt update -y 10 + run apt install ca-certificates -y 11 + run apt update -y 12 + # This is the only dependancie missing as far as I can tell which is great 13 + run apt install libssl-dev -y 14 + CMD ["rusted-fbt"]