diff --git a/rustproxy/.gitignore b/rustproxy/.gitignore new file mode 100644 index 00000000..28dc62fc --- /dev/null +++ b/rustproxy/.gitignore @@ -0,0 +1,3 @@ +/target +.idea/ +config.toml \ No newline at end of file diff --git a/rustproxy/Cargo.lock b/rustproxy/Cargo.lock new file mode 100644 index 00000000..e1730554 --- /dev/null +++ b/rustproxy/Cargo.lock @@ -0,0 +1,2625 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "arc-swap" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" + +[[package]] +name = "async-io" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "combine" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util 0.7.3", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "config" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea917b74b6edfb5024e3b55d3c8f710b5f4ed92646429601a42e96f0812b31b" +dependencies = [ + "async-trait", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.8", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "lazy_static", + "maybe-uninit", + "memoffset 0.5.6", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.8", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.12.1", + "lock_api", + "parking_lot_core 0.9.3", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.3", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" +dependencies = [ + "hashbrown 0.12.1", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.2", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.2", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown 0.11.2", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leaky-bucket-lite" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1411c737dd21a748044ab29af14b7f920b2dcc277284df6dd986492c98bf5229" +dependencies = [ + "tokio", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "md-5" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "moka" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df72b50274c0988d9f4a6e808e06d9d926f265db6f8bbda1576bcaa658e72763" +dependencies = [ + "async-io", + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils 0.8.8", + "futures-util", + "num_cpus", + "once_cell", + "parking_lot 0.12.1", + "quanta", + "scheduled-thread-pool", + "skeptic", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "openssl" +version = "0.10.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pk_bot" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "config", + "dashmap", + "futures", + "moka", + "once_cell", + "redis", + "regex", + "serde", + "serde_json", + "smallvec", + "smol_str", + "sqlx", + "thiserror", + "tikv-jemallocator", + "tokio", + "tracing", + "tracing-subscriber", + "twilight-gateway", + "twilight-gateway-queue", + "twilight-http", + "twilight-model", + "twilight-util", +] + +[[package]] +name = "pk_command_parser" +version = "0.1.0" +dependencies = [ + "anyhow", + "matches", + "slab", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quanta" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafd74c340a0a7e79415981ede3460df16b530fd071541901a57416eea950b17" +dependencies = [ + "crossbeam-utils 0.8.8", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi 0.10.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-cpuid" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738bc47119e3eeccc7e94c4a506901aea5e7b4944ecd0829cbebf4af04ceda12" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redis" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "combine", + "dtoa", + "futures", + "futures-util", + "itoa 0.4.8", + "percent-encoding", + "pin-project-lite", + "sha1", + "tokio", + "tokio-util 0.6.10", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scheduled-thread-pool" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" +dependencies = [ + "parking_lot 0.12.1", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa 1.0.2", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "smol_str" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f82cbe94f41641d6c410ded25bbf5097c240cefdf8e3b06d04198d0a96af6a4" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093" +dependencies = [ + "ahash", + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dirs", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "hkdf", + "hmac", + "indexmap", + "itoa 1.0.2", + "libc", + "log", + "md-5", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rand", + "serde", + "serde_json", + "sha-1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40c63177cf23d356b159b60acd27c54af7423f1736988502e36bae9a712118f" +dependencies = [ + "dotenv", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874e93a365a598dc3dadb197565952cb143ae4aa716f7bcc933a8d836f6bf89f" +dependencies = [ + "native-tls", + "once_cell", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.0+5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeab4310214fe0226df8bfeb893a291a58b19682e8a07e1e1d4483ad4200d315" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20612db8a13a6c06d57ec83953694185a367e16945f66565e8028d2c0bd76979" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "libc", + "num_threads", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95eec79ea28c00a365f539f1961e9278fbcaf81c0ff6aaf0e93c181352446948" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "triomphe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45e322b26410d7260e00f64234810c2f17d7ece356182af4df8f7ff07890f09" +dependencies = [ + "memoffset 0.6.5", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "twilight-gateway" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d952446d4dc7189d0908b48f254e94fdc154de88d45a55841c1cfcc76803180" +dependencies = [ + "bitflags", + "flate2", + "futures-util", + "leaky-bucket-lite", + "rustls", + "rustls-native-certs", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "twilight-gateway-queue", + "twilight-http", + "twilight-model", + "url", +] + +[[package]] +name = "twilight-gateway-queue" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7951f688ea81c7f68bad1cfabbe1bb6bcc08984347e3075d20923b65ff2041" +dependencies = [ + "tokio", + "tracing", + "twilight-http", +] + +[[package]] +name = "twilight-http" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02713ac747c27448be764d6629dcf4d07fbcd8fc855096b6e0ab6717c685be0" +dependencies = [ + "brotli", + "hyper", + "hyper-rustls", + "percent-encoding", + "rand", + "serde", + "serde_json", + "tokio", + "tracing", + "twilight-http-ratelimiting", + "twilight-model", + "twilight-validate", +] + +[[package]] +name = "twilight-http-ratelimiting" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8329e421d810eb18ddd41c3c1b78cbc6cd9e82b70850ac3f70d4e6d93e9ff72" +dependencies = [ + "futures-util", + "http", + "tokio", + "tracing", +] + +[[package]] +name = "twilight-model" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44d87f62a032ea0d8053481672844cab0115330d99d655791be94a31d585256" +dependencies = [ + "bitflags", + "serde", + "serde-value", + "serde_repr", + "time 0.3.9", + "tracing", +] + +[[package]] +name = "twilight-util" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f54602b3265361fb7337abc5305d93b7e70faa0b1e76fe59a264144936543" +dependencies = [ + "twilight-model", + "twilight-validate", +] + +[[package]] +name = "twilight-validate" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d8ee22c7e5321064abdd9ad1c6a7f0a7502db11db018a2f72326c9314bcea41" +dependencies = [ + "twilight-model", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/rustproxy/Cargo.toml b/rustproxy/Cargo.toml new file mode 100644 index 00000000..4c6defa6 --- /dev/null +++ b/rustproxy/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "pk_bot", + "pk_command_parser" +] \ No newline at end of file diff --git a/rustproxy/config.example.toml b/rustproxy/config.example.toml new file mode 100644 index 00000000..b852a8d2 --- /dev/null +++ b/rustproxy/config.example.toml @@ -0,0 +1,7 @@ +token = "put your token here" + +database = "postgres://postgres:password@localhost/postgres" + +redis_addr = "redis://127.0.0.1/" +redis_gateway_queue_addr = "redis://127.0.0.1/" +shard_count = 1 \ No newline at end of file diff --git a/rustproxy/pk_bot/Cargo.toml b/rustproxy/pk_bot/Cargo.toml new file mode 100644 index 00000000..e2dbb497 --- /dev/null +++ b/rustproxy/pk_bot/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pk_bot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.57" +async-trait = "0.1.56" +chrono = "0.4.19" +config = { version = "0.13.1", default-features = false, features = ["toml"] } +dashmap = "5.3.4" +futures = "0.3.21" +moka = { version = "0.8.5", features = ["future"] } +once_cell = "1.12.0" +redis = { version = "0.21.5", features = ["connection-manager", "tokio-comp"] } +regex = "1.5.6" +serde = "1.0.137" +serde_json = "1.0.81" +smallvec = "1.8.0" +smol_str = "0.1.23" +sqlx = { version = "0.6.0", features = ["runtime-tokio-native-tls", "postgres", "chrono", "macros"] } +thiserror = "1.0.31" +tokio = { version = "1.19.1", features = ["full"] } +tracing = "0.1.34" +tracing-subscriber = "0.3.11" +twilight-gateway = "0.11.0" +twilight-gateway-queue = "0.11.0" +twilight-http = "0.11.0" +twilight-model = "0.11.0" +twilight-util = { version = "0.11.0", features = ["builder", "permission-calculator"] } + +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = "0.5" diff --git a/rustproxy/pk_bot/src/cache.rs b/rustproxy/pk_bot/src/cache.rs new file mode 100644 index 00000000..7388a366 --- /dev/null +++ b/rustproxy/pk_bot/src/cache.rs @@ -0,0 +1,265 @@ +use std::sync::{Arc, RwLock}; + +use dashmap::mapref::one::Ref; +use dashmap::DashMap; +use smol_str::SmolStr; +use twilight_model::channel::permission_overwrite::PermissionOverwrite; +use twilight_model::channel::{Channel, ChannelType}; +use twilight_model::gateway::event::Event; +use twilight_model::gateway::payload::incoming::ThreadListSync; +use twilight_model::guild::{Guild, PartialMember, Permissions, PremiumTier, Role}; +use twilight_model::id::marker::{ChannelMarker, GuildMarker, RoleMarker, UserMarker}; +use twilight_model::id::Id; +use twilight_util::permission_calculator::PermissionCalculator; + +const DM_PERMISSIONS: Permissions = Permissions::VIEW_CHANNEL + .union(Permissions::SEND_MESSAGES) + .union(Permissions::READ_MESSAGE_HISTORY) + .union(Permissions::ADD_REACTIONS) + .union(Permissions::ATTACH_FILES) + .union(Permissions::EMBED_LINKS) + .union(Permissions::USE_EXTERNAL_EMOJIS); + +#[derive(Debug, Clone)] +pub struct CachedGuild { + owner_id: u64, + _premium_tier: PremiumTier, +} + +#[derive(Debug, Clone)] +pub struct CachedChannel { + _name: Option, // stores strings 22 characters or less inline, which is a large portion of channel names + _parent_id: Option>, + guild_id: Option>, + kind: ChannelType, + overwrites: Vec, +} + +#[derive(Debug, Clone)] +pub struct CachedRole { + permissions: Permissions, + _mentionable: bool, +} + +#[derive(Debug, Clone)] +pub struct CachedBotMember { + roles: Vec>, +} + +pub struct DiscordCache { + bot_user: Arc>>>, + guilds: DashMap, + channels: DashMap, + roles: DashMap, + bot_members: DashMap, +} + +impl DiscordCache { + pub fn new() -> DiscordCache { + DiscordCache { + bot_user: Arc::new(RwLock::new(None)), + guilds: DashMap::new(), + channels: DashMap::new(), + roles: DashMap::new(), + bot_members: DashMap::new(), + } + } + + pub fn handle_event(&self, event: &Event) { + match event { + Event::Ready(ref r) => { + let mut bot_user = self.bot_user.write().unwrap(); + *bot_user = Some(r.user.id); + } + Event::GuildCreate(ref g) => self.update_guild(g), + Event::ChannelCreate(ref ch) => self.update_channel(ch), + Event::ChannelUpdate(ref ch) => self.update_channel(ch), + Event::ChannelDelete(ref ch) => self.delete_channel(ch.id), + Event::ThreadCreate(ref ch) => self.update_channel(ch), + Event::ThreadUpdate(ref ch) => self.update_channel(ch), + Event::ThreadDelete(ref ch) => self.delete_channel(ch.id), + Event::ThreadListSync(ref ts) => self.update_threads(ts), + Event::RoleCreate(ref r) => self.update_role(&r.role), + Event::RoleUpdate(ref r) => self.update_role(&r.role), + Event::RoleDelete(ref r) => self.delete_role(r.role_id), + Event::MemberUpdate(ref member) => { + let current_user = self.bot_user_id(); + if Some(member.user.id) == current_user { + self.update_bot_member(member.guild_id, &member.roles); + } + } + _ => {} + } + } + + fn update_guild(&self, guild: &Guild) { + for channel in &guild.channels { + self.update_channel(channel); + } + + for role in &guild.roles { + self.update_role(role); + } + + let current_user = self.bot_user_id(); + for member in &guild.members { + if Some(member.user.id) == current_user { + self.update_bot_member(member.guild_id, &member.roles); + } + } + + self.guilds.insert( + guild.id.get(), + CachedGuild { + owner_id: guild.owner_id.get(), + _premium_tier: guild.premium_tier, + }, + ); + } + + fn bot_user_id(&self) -> Option> { + *self.bot_user.read().unwrap() + } + + fn update_channel(&self, channel: &Channel) { + self.channels.insert( + channel.id.get(), + CachedChannel { + _name: channel.name.as_deref().map(SmolStr::new), + _parent_id: channel.parent_id, + guild_id: channel.guild_id, + kind: channel.kind, + overwrites: channel.permission_overwrites.clone().unwrap_or_default(), + }, + ); + } + + fn delete_channel(&self, id: Id) { + self.channels.remove(&id.get()); + } + + fn update_role(&self, role: &Role) { + self.roles.insert( + role.id.get(), + CachedRole { + permissions: role.permissions, + _mentionable: role.mentionable, + }, + ); + } + + fn delete_role(&self, id: Id) { + self.roles.remove(&id.get()); + } + + fn update_threads(&self, evt: &ThreadListSync) { + for thread in &evt.threads { + self.update_channel(thread); + } + } + + fn update_bot_member(&self, guild_id: Id, roles: &[Id]) { + self.bot_members.insert( + guild_id.get(), + CachedBotMember { + roles: roles.to_vec(), + }, + ); + } + + pub fn get_guild(&self, guild_id: Id) -> anyhow::Result> { + self.guilds + .get(&guild_id.get()) + .ok_or_else(|| anyhow::anyhow!("could not find guild in cache: {}", guild_id)) + } + + pub fn get_channel( + &self, + channel_id: Id, + ) -> anyhow::Result> { + self.channels + .get(&channel_id.get()) + .ok_or_else(|| anyhow::anyhow!("could not find channel in cache: {}", channel_id)) + } + + pub fn get_role(&self, role_id: Id) -> anyhow::Result> { + self.roles + .get(&role_id.get()) + .ok_or_else(|| anyhow::anyhow!("could not find role in cache: {}", role_id)) + } + + pub fn get_bot_member( + &self, + guild_id: Id, + ) -> anyhow::Result> { + self.bot_members.get(&guild_id.get()).ok_or_else(|| { + anyhow::anyhow!("could not find bot member in cache for guild: {}", guild_id) + }) + } + + fn calculate_permissions_in( + &self, + channel_id: Id, + user_id: Id, + roles: &[Id], + ) -> anyhow::Result { + let channel = self.get_channel(channel_id)?; + + if let Some(guild_id) = channel.guild_id { + let guild = self.get_guild(guild_id)?; + let everyone_role = self.get_role(guild_id.cast())?; + + let mut member_roles = Vec::with_capacity(roles.len()); + for role_id in roles { + let role = self.get_role(*role_id)?; + member_roles.push((role_id.cast(), role.permissions)); + } + + let calc = PermissionCalculator::new( + guild_id, + user_id, + everyone_role.permissions, + &member_roles, + ) + .owner_id(Id::new(guild.owner_id)); + + Ok(calc.in_channel(channel.kind, &channel.overwrites)) + } else { + Ok(DM_PERMISSIONS) + } + } + + pub fn member_permissions( + &self, + channel_id: Id, + user_id: Id, + member: Option<&PartialMember>, + ) -> anyhow::Result { + if let Some(member) = member { + self.calculate_permissions_in(channel_id, user_id, &member.roles) + } else { + // this should just be dm perms, probably? + self.calculate_permissions_in(channel_id, user_id, &[]) + } + } + + pub fn bot_permissions(&self, channel_id: Id) -> anyhow::Result { + let channel = self.get_channel(channel_id)?; + if let Some(guild_id) = channel.guild_id { + let member = self.get_bot_member(guild_id)?; + + let user_id = self + .bot_user_id() + .ok_or_else(|| anyhow::anyhow!("haven't received bot user id yet"))?; + + self.calculate_permissions_in(channel_id, user_id, &member.roles) + } else { + Ok(DM_PERMISSIONS) + } + } + + pub fn channel_type(&self, channel_id: Id) -> anyhow::Result { + let channel = self.get_channel(channel_id)?; + Ok(channel.kind) + } +} diff --git a/rustproxy/pk_bot/src/config.rs b/rustproxy/pk_bot/src/config.rs new file mode 100644 index 00000000..33420ff0 --- /dev/null +++ b/rustproxy/pk_bot/src/config.rs @@ -0,0 +1,22 @@ +use config::{Config, Environment, File, FileFormat}; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct BotConfig { + pub token: String, + + pub max_concurrency: Option, + pub database: String, + pub redis_addr: Option, + pub redis_gateway_queue_addr: Option, + pub shard_count: Option, +} + +// todo: should this be a once_cell::Lazy global const or something +pub fn load_config() -> anyhow::Result { + let builder = Config::builder() + .add_source(Environment::default()) + .add_source(File::new("config", FileFormat::Toml)); + + Ok(builder.build()?.try_deserialize()?) +} diff --git a/rustproxy/pk_bot/src/db.rs b/rustproxy/pk_bot/src/db.rs new file mode 100644 index 00000000..de3ba2e4 --- /dev/null +++ b/rustproxy/pk_bot/src/db.rs @@ -0,0 +1,174 @@ +use std::str::FromStr; + +use crate::{ + config::BotConfig, + model::{PKMember, PKMemberGuild, PKMessage, PKSystem, PKSystemGuild}, +}; +use chrono::{DateTime, Utc}; +use sqlx::{ + postgres::{PgConnectOptions, PgPoolOptions}, + ConnectOptions, FromRow, PgPool, +}; +use tracing::info; + +#[derive(FromRow, Debug, Default)] +pub struct MessageContext { + // being defensive with these values - we need to be explicit with Option + // when the database might return null, and some of these don't have proper default values set + // most of the Options can probably get removed with a few changes to the db function + pub system_id: Option, + pub is_deleting: Option, + pub in_blacklist: Option, + pub in_log_blacklist: Option, + pub proxy_enabled: Option, + pub last_switch: Option, + pub last_switch_members: Option>, + pub last_switch_timestamp: Option>, + pub system_tag: Option, + pub system_guild_tag: Option, + pub tag_enabled: Option, + pub system_avatar: Option, + pub allow_autoproxy: Option, + pub latch_timeout: Option, +} + +pub async fn get_message_context( + pool: &PgPool, + account_id: i64, + guild_id: i64, + channel_id: i64, +) -> anyhow::Result { + Ok(sqlx::query_as("select * from message_context($1, $2, $3)") + .bind(account_id) + .bind(guild_id) + .bind(channel_id) + .fetch_one(pool) + .await?) +} + +#[derive(FromRow, Debug, Clone)] +pub struct ProxyTagEntry { + pub prefix: String, + pub suffix: String, + pub member_id: i32, +} + +impl From<(&str, &str, i32)> for ProxyTagEntry { + fn from((prefix, suffix, member_id): (&str, &str, i32)) -> Self { + ProxyTagEntry { + prefix: prefix.to_string(), + suffix: suffix.to_string(), + member_id, + } + } +} + +pub async fn get_proxy_tags(pool: &PgPool, system_id: i32) -> anyhow::Result> { + Ok(sqlx::query_as("select coalesce((i.tags).prefix, '') as prefix, coalesce((i.tags).suffix, '') as suffix, member_id from (select unnest(proxy_tags) as tags, id as member_id from members where system = $1) as i;") + .bind(system_id) + .fetch_all(pool) + .await?) +} + +#[repr(i32)] +#[derive(sqlx::Type, Debug, Copy, Clone)] +pub enum AutoproxyMode { + Off = 1, + Front = 2, + Latch = 3, + Member = 4, +} + +#[derive(FromRow, Debug, Clone)] +pub struct AutoproxyState { + pub autoproxy_mode: AutoproxyMode, + pub autoproxy_member: Option, + pub last_latch_timestamp: Option>, +} + +pub async fn get_autoproxy_state( + pool: &PgPool, + system_id: i32, + guild_id: i64, + channel_id: i64, +) -> anyhow::Result> { + Ok(sqlx::query_as( + "select * from autoproxy where system = $1 and guild_id = $2 and channel_id = $3;", + ) + .bind(system_id) + .bind(guild_id) + .bind(channel_id) + .fetch_optional(pool) + .await?) +} + +pub async fn get_system_by_id(pool: &PgPool, system_id: i32) -> anyhow::Result> { + Ok(sqlx::query_as("select * from systems where id = $1") + .bind(system_id) + .fetch_optional(pool) + .await?) +} + +pub async fn get_member_by_id(pool: &PgPool, member_id: i32) -> anyhow::Result> { + Ok(sqlx::query_as("select * from members where id = $1") + .bind(member_id) + .fetch_optional(pool) + .await?) +} + +pub async fn get_system_guild( + pool: &PgPool, + system_id: i32, + guild_id: i64, +) -> anyhow::Result> { + Ok( + sqlx::query_as("select * from system_guild where system = $1 and guild = $2") + .bind(system_id) + .bind(guild_id) + .fetch_optional(pool) + .await?, + ) +} + +pub async fn get_member_guild( + pool: &PgPool, + member_id: i32, + guild_id: i64, +) -> anyhow::Result> { + Ok( + sqlx::query_as("select * from member_guild where member = $1 and guild = $2") + .bind(member_id) + .bind(guild_id) + .fetch_optional(pool) + .await?, + ) +} + +pub async fn insert_message(pool: &PgPool, message: PKMessage) -> anyhow::Result<()> { + sqlx::query("insert into messages (mid, guild, channel, member, sender, original_mid) values ($1, $2, $3, $4, $5, $6)") + .bind(message.mid) + .bind(message.guild) + .bind(message.channel) + .bind(message.member_id) + .bind(message.sender) + .bind(message.original_mid) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn init_db(config: &BotConfig) -> anyhow::Result { + info!("connecting to database"); + let options = PgConnectOptions::from_str(&config.database) + .unwrap() + .disable_statement_logging() + .clone(); + + let pool = PgPoolOptions::new() + .max_connections(32) + .connect_with(options) + .await?; + + Ok(pool) +} diff --git a/rustproxy/pk_bot/src/gateway.rs b/rustproxy/pk_bot/src/gateway.rs new file mode 100644 index 00000000..b94a7f6e --- /dev/null +++ b/rustproxy/pk_bot/src/gateway.rs @@ -0,0 +1,63 @@ +use crate::config::BotConfig; +use crate::redis; +use std::{env, sync::Arc}; +use tracing::info; +use twilight_gateway::{ + cluster::{Events, ShardScheme}, + Cluster, EventTypeFlags, Intents, +}; +use twilight_http::Client; + +pub async fn init_gateway( + http: Arc, + config: &BotConfig, +) -> anyhow::Result<(Arc, Events)> { + let mut builder = Cluster::builder( + config.token.clone(), + Intents::GUILDS + | Intents::DIRECT_MESSAGES + | Intents::GUILD_MESSAGES + | Intents::MESSAGE_CONTENT, + ); + builder = builder.http_client(http); + builder = builder.event_types(EventTypeFlags::all()); + + if let Some(scheme) = get_shard_scheme(config)? { + info!("using shard scheme: {:?}", scheme); + builder = builder.shard_scheme(scheme); + } + + if let Some(queue) = redis::init_gateway_queue(config).await? { + info!("using redis gateway queue"); + builder = builder.queue(Arc::new(queue)); + } + + let (cluster, events) = builder.build().await?; + let cluster = Arc::new(cluster); + let cluster_spawn = Arc::clone(&cluster); + tokio::spawn(async move { + info!("starting shards..."); + cluster_spawn.up().await; + }); + + Ok((cluster, events)) +} + +fn get_cluster_id() -> anyhow::Result { + Ok(env::var("NOMAD_ALLOC_INDEX") + .unwrap_or_else(|_| "0".to_string()) + .parse::()?) +} + +fn get_shard_scheme(config: &BotConfig) -> anyhow::Result> { + let shard_count = config.shard_count.unwrap_or(1); + let scheme = if shard_count >= 16 { + let cluster_id = get_cluster_id()?; + let first_shard_id = 16 * cluster_id; + let shard_range = first_shard_id..first_shard_id + 16; + Some(ShardScheme::try_from((shard_range, shard_count))?) + } else { + None + }; + Ok(scheme) +} diff --git a/rustproxy/pk_bot/src/main.rs b/rustproxy/pk_bot/src/main.rs new file mode 100644 index 00000000..c8757609 --- /dev/null +++ b/rustproxy/pk_bot/src/main.rs @@ -0,0 +1,109 @@ +#[cfg(not(target_env = "msvc"))] +use tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; + +use crate::cache::DiscordCache; +use crate::redis::RedisEventProxy; +use futures::StreamExt; +use sqlx::PgPool; +use std::sync::Arc; +use tracing::{error, info}; +use twilight_gateway::Event; +use twilight_http::Client as HttpClient; + +mod cache; +mod config; +mod db; +mod gateway; +mod model; +mod proxy; +mod redis; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + + let config = config::load_config()?; + info!("loaded config: {:?}", config); + + let pool = db::init_db(&config).await?; + let http = Arc::new(HttpClient::new(config.token.clone())); + let (_cluster, mut events) = gateway::init_gateway(Arc::clone(&http), &config).await?; + let cache = Arc::new(DiscordCache::new()); + let redis = redis::init_event_proxy(&config).await?; + + while let Some((shard_id, event)) = events.next().await { + let http = Arc::clone(&http); + let pool = pool.clone(); + let cache = Arc::clone(&cache); + let redis = redis.clone(); + + tokio::spawn(async move { + cache.handle_event(&event); + + let res = handle_event(shard_id, event, http, pool, cache, redis).await; + if let Err(e) = res { + error!("error handling event: {:?}", e); + } + }); + } + + Ok(()) +} + +async fn handle_event( + shard_id: u64, + event: Event, + http: Arc, + pool: PgPool, + cache: Arc, + mut redis: RedisEventProxy, +) -> anyhow::Result<()> { + match event { + Event::MessageCreate(msg) => { + if msg.content.starts_with("pk;") || msg.content.starts_with("pk!") { + redis + .send_event_parsed(shard_id, Event::MessageCreate(msg)) + .await?; + return Ok(()); + } + + let channel_type = cache.channel_type(msg.channel_id)?; + + let ctx = db::get_message_context( + &pool, + msg.author.id.get() as i64, + msg.guild_id.map(|x| x.get()).unwrap_or_default() as i64, + msg.channel_id.get() as i64, + ) + .await?; + + let _member_permissions = + cache.member_permissions(msg.channel_id, msg.author.id, msg.member.as_ref())?; + let bot_permissions = cache.bot_permissions(msg.channel_id)?; + + match proxy::check_preconditions(&msg, channel_type, bot_permissions, &ctx) { + Ok(_) => { + info!("attempting to proxy"); + proxy::do_proxy(&http, &pool, &msg, &ctx).await?; + } + Err(reason) => { + info!("skipping proxy because: {}", reason) + } + } + } + Event::ShardConnected(_) => { + info!("connected on shard {}", shard_id); + } + Event::ShardPayload(payload) => { + redis.send_event_raw(shard_id, &payload.bytes).await?; + } + // Other events here... + _ => {} + } + + Ok(()) +} diff --git a/rustproxy/pk_bot/src/model.rs b/rustproxy/pk_bot/src/model.rs new file mode 100644 index 00000000..35da989c --- /dev/null +++ b/rustproxy/pk_bot/src/model.rs @@ -0,0 +1,49 @@ +use sqlx::FromRow; + +#[derive(FromRow, Debug, Clone)] +pub struct PKSystem { + pub id: i32, + pub name: Option, + pub tag: Option, + pub avatar_url: Option, +} + +#[derive(FromRow, Debug, Clone)] +pub struct PKMember { + pub id: i32, + pub system: i32, + pub name: String, + + pub color: Option, + pub avatar_url: Option, + pub display_name: Option, + pub pronouns: Option, + pub description: Option, +} + +#[derive(FromRow, Debug, Clone)] +pub struct PKMessage { + pub mid: i64, + pub guild: Option, + pub channel: i64, + pub member_id: i32, + pub sender: i64, + pub original_mid: Option, +} + +#[derive(FromRow, Debug, Clone)] +pub struct PKSystemGuild { + pub system: i32, + pub guild: i64, + pub proxy_enabled: bool, + pub tag: Option, + pub tag_enabled: bool, +} + +#[derive(FromRow, Debug, Clone)] +pub struct PKMemberGuild { + pub member: i32, + pub guild: i64, + pub display_name: Option, + pub avatar_url: Option, +} diff --git a/rustproxy/pk_bot/src/proxy/autoproxy.rs b/rustproxy/pk_bot/src/proxy/autoproxy.rs new file mode 100644 index 00000000..f2315798 --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/autoproxy.rs @@ -0,0 +1,31 @@ +use crate::db::{AutoproxyMode, AutoproxyState, MessageContext}; + +pub fn resolve_autoproxy_member( + ctx: &MessageContext, + state: &AutoproxyState, + content: &str, +) -> Option { + if !ctx.allow_autoproxy.unwrap_or(true) { + return None; + } + + if is_escape(content) { + return None; + } + + let first_fronter = ctx.last_switch_members.iter().flatten().cloned().next(); + match (state.autoproxy_mode, state.autoproxy_member, first_fronter) { + (AutoproxyMode::Latch, Some(m), _) => Some(m), + (AutoproxyMode::Member, Some(m), _) => Some(m), + (AutoproxyMode::Front, _, Some(f)) => Some(f), + _ => None, + } +} + +fn _is_unlatch(content: &str) -> bool { + content.starts_with("\\\\") || content.starts_with("\\\u{200b}\\") +} + +fn is_escape(content: &str) -> bool { + content.starts_with('\\') +} diff --git a/rustproxy/pk_bot/src/proxy/mod.rs b/rustproxy/pk_bot/src/proxy/mod.rs new file mode 100644 index 00000000..6f7d2a21 --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/mod.rs @@ -0,0 +1,227 @@ +use self::{post_proxy::ProxyResult, webhook::WebhookExecuteRequest}; +use crate::db::{self, MessageContext}; +use sqlx::PgPool; +use thiserror::Error; +use twilight_http::Client; +use twilight_model::{ + channel::{message::MessageType, ChannelType, Message}, + guild::Permissions, + user::User, +}; + +mod autoproxy; +mod post_proxy; +mod profile; +mod reply; +mod tags; +mod webhook; + +#[derive(Error, Debug)] +// use this for pk;proxycheck or something +pub enum PreconditionFailure { + #[error("bot is missing permissions (has: {has:?}, needs: {needs:?})")] + BotMissingPermission { + has: Permissions, + needs: Permissions, + }, + + #[error("invalid channel type {0:?}")] + InvalidChannelType(ChannelType), + + #[error("invalid message type {0:?}")] + InvalidMessageType(MessageType), + + #[error("user is bot")] + UserIsBot, + + #[error("user is webhook")] + UserIsWebhook, + + #[error("user is discord system")] + UserIsDiscordSystem, + + #[error("user has no system")] + UserHasNoSystem, + + #[error("proxy disabled for system")] + ProxyDisabledForSystem, + + #[error("proxy disabled in channel")] + ProxyDisabledInChannel, + + #[error("message contains activity")] + MessageContainsActivity, + + #[error("message contains sticker")] + MessageContainsSticker, + + #[error("message is empty and has no attachments")] + MessageIsEmpty, +} + +// todo: the parameters here are nasty, refactor+put this code somewhere else maybe +pub fn check_preconditions( + msg: &Message, + channel_type: ChannelType, + bot_permissions: Permissions, + ctx: &MessageContext, +) -> Result<(), PreconditionFailure> { + let required_permissions = + Permissions::SEND_MESSAGES | Permissions::MANAGE_WEBHOOKS | Permissions::MANAGE_MESSAGES; + if !bot_permissions.contains(required_permissions) { + return Err(PreconditionFailure::BotMissingPermission { + has: bot_permissions, + needs: required_permissions, + }); + } + + match channel_type { + ChannelType::GuildText + | ChannelType::GuildNews + | ChannelType::GuildPrivateThread + | ChannelType::GuildPublicThread + | ChannelType::GuildNewsThread => Ok(()), + wrong_type => Err(PreconditionFailure::InvalidChannelType(wrong_type)), + }?; + + match msg.kind { + MessageType::Regular | MessageType::Reply => Ok(()), + wrong_type => Err(PreconditionFailure::InvalidMessageType(wrong_type)), + }?; + + match msg { + Message { + author: User { + system: Some(true), .. + }, + .. + } => Err(PreconditionFailure::UserIsDiscordSystem), + Message { + author: User { bot: true, .. }, + .. + } => Err(PreconditionFailure::UserIsBot), + Message { + webhook_id: Some(_), + .. + } => Err(PreconditionFailure::UserIsWebhook), + Message { + activity: Some(_), .. + } => Err(PreconditionFailure::MessageContainsActivity), + Message { + sticker_items: s, .. + } if !s.is_empty() => Err(PreconditionFailure::MessageContainsSticker), + Message { + content: c, + attachments: a, + .. + } if c.trim().is_empty() && a.is_empty() => Err(PreconditionFailure::MessageIsEmpty), + _ => Ok(()), + }?; + + match ctx { + MessageContext { + system_id: None, .. + } => Err(PreconditionFailure::UserHasNoSystem), + MessageContext { + in_blacklist: Some(true), + .. + } => Err(PreconditionFailure::ProxyDisabledInChannel), + MessageContext { + proxy_enabled: Some(false), + .. + } => Err(PreconditionFailure::ProxyDisabledForSystem), + _ => Ok(()), + }?; + + Ok(()) +} + +struct ProxyMatchResult { + member_id: i32, + inner_content: String, + _tags: Option<(String, String)>, // todo: need this for keepproxy +} + +async fn match_tags_or_autoproxy( + pool: &PgPool, + msg: &Message, + ctx: &MessageContext, +) -> anyhow::Result> { + let guild_id = msg.guild_id.ok_or_else(|| anyhow::anyhow!("no guild id"))?; + let system_id = ctx.system_id.ok_or_else(|| anyhow::anyhow!("no system"))?; + + let tags = db::get_proxy_tags(pool, system_id).await?; + let ap_state = db::get_autoproxy_state( + pool, + system_id, + guild_id.get() as i64, + 0, // all autoproxy has channel id 0? o.o + ) + .await?; + + let tag_match = tags::match_proxy_tags(&tags, &msg.content); + if let Some(tag_match) = tag_match { + return Ok(Some(ProxyMatchResult { + member_id: tag_match.member_id, + inner_content: tag_match.inner_content, + _tags: Some(tag_match.tags), + })); + } + + if let Some(ap_state) = ap_state { + let res = autoproxy::resolve_autoproxy_member(ctx, &ap_state, &msg.content); + if let Some(member_id) = res { + return Ok(Some(ProxyMatchResult { + inner_content: msg.content.clone(), + member_id, + _tags: None, + })); + } + } + + Ok(None) +} + +// todo: this shouldn't depend on a Message object (for reproxy/proxy command/etc) +pub async fn do_proxy( + http: &Client, + pool: &PgPool, + msg: &Message, + ctx: &MessageContext, +) -> anyhow::Result<()> { + let guild_id = msg.guild_id.ok_or_else(|| anyhow::anyhow!("no guild id"))?; + let system_id = ctx.system_id.ok_or_else(|| anyhow::anyhow!("no system"))?; + + // todo: unlatch check/exec should probably go in here somewhere + + let proxy_match = match_tags_or_autoproxy(pool, msg, ctx).await?; + if let Some(result) = proxy_match { + let profile = + profile::fetch_proxy_profile(pool, guild_id.get(), system_id, result.member_id).await?; + + let webhook_req = WebhookExecuteRequest { + channel_id: msg.channel_id.get(), + avatar_url: profile.avatar_url().map(|s| s.to_string()), + content: Some(result.inner_content.clone()), + username: profile.formatted_name(), + embed: msg + .referenced_message + .as_deref() + .map(|msg| reply::create_reply_embed(guild_id, msg)) + .transpose()?, + }; + + let webhook_res = webhook::execute_webhook(http, &webhook_req).await?; + + let proxy_res = ProxyResult { + channel_id: msg.channel_id.get(), + guild_id: guild_id.get(), + member_id: result.member_id, + original_message_id: msg.id.get(), + proxy_message_id: webhook_res.message_id, + sender: msg.author.id.get(), + }; + post_proxy::handle_post_proxy(http, pool, &proxy_res).await?; + } + Ok(()) +} diff --git a/rustproxy/pk_bot/src/proxy/post_proxy.rs b/rustproxy/pk_bot/src/proxy/post_proxy.rs new file mode 100644 index 00000000..041ad1e7 --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/post_proxy.rs @@ -0,0 +1,66 @@ +use crate::db; +use crate::model::PKMessage; +use futures::TryFutureExt; +use sqlx::PgPool; +use tracing::error; +use twilight_http::Client; + +#[derive(Debug, Clone)] +pub struct ProxyResult { + pub guild_id: u64, + pub channel_id: u64, + pub proxy_message_id: u64, + pub original_message_id: u64, + pub sender: u64, + pub member_id: i32, +} + +pub async fn handle_post_proxy( + http: &Client, + pool: &PgPool, + res: &ProxyResult, +) -> anyhow::Result<()> { + // todo: log channel + + let _ = futures::join!( + delete_original_message(http, res) + .inspect_err(|e| error!("error deleting original message: {}", e)), + insert_message_in_db(pool, res) + .inspect_err(|e| error!("error deleting original message: {}", e)) + ); + + Ok(()) +} + +async fn delete_original_message(http: &Client, res: &ProxyResult) -> anyhow::Result<()> { + // todo: sleep some amount + // (do we still need to do that or did discord fix that client bug?) + http.delete_message( + res.channel_id.try_into().unwrap(), + res.original_message_id.try_into().unwrap(), + ) + .exec() + .await?; + + Ok(()) +} + +async fn insert_message_in_db(pool: &PgPool, res: &ProxyResult) -> anyhow::Result<()> { + db::insert_message( + pool, + PKMessage { + mid: res.proxy_message_id as i64, + original_mid: Some(res.original_message_id as i64), + sender: res.sender as i64, + guild: Some(res.guild_id as i64), + channel: res.channel_id as i64, + member_id: res.member_id, + }, + ) + .await?; + + Ok(()) +} + +#[cfg(test)] +mod tests {} diff --git a/rustproxy/pk_bot/src/proxy/profile.rs b/rustproxy/pk_bot/src/proxy/profile.rs new file mode 100644 index 00000000..8a6f87c7 --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/profile.rs @@ -0,0 +1,90 @@ +use futures::try_join; +use sqlx::PgPool; + +use crate::{ + db, + model::{PKMember, PKMemberGuild, PKSystem, PKSystemGuild}, +}; + +// inventing the term "proxy profile" here to describe the info needed to work out the webhook name+avatar +// arbitrary choice to put the source models in the struct and logic in methods, could just as well have had a function to do the math and put the results in a struct +pub struct ProxyProfile { + system: PKSystem, + member: PKMember, + system_guild: Option, + member_guild: Option, +} + +impl ProxyProfile { + pub fn name(&self) -> &str { + let member_name = &self.member.name; + let display_name = self.member.display_name.as_deref(); + let server_name = self + .member_guild + .as_ref() + .and_then(|x| x.display_name.as_deref()); + server_name.or(display_name).unwrap_or(member_name) + } + + pub fn avatar_url(&self) -> Option<&str> { + let system_avatar = self.system.avatar_url.as_deref(); + let member_avatar = self.member.avatar_url.as_deref(); + let server_avatar = self + .member_guild + .as_ref() + .and_then(|x| x.avatar_url.as_deref()); + server_avatar.or(member_avatar).or(system_avatar) + } + + pub fn tag(&self) -> Option<&str> { + let server_tag = self.system_guild.as_ref().and_then(|x| x.tag.as_deref()); + let system_tag = self.system.tag.as_deref(); + server_tag.or(system_tag) + } + + pub fn formatted_name(&self) -> String { + let mut name = if let Some(tag) = self.tag() { + format!("{} {}", self.name(), tag) + } else { + self.name().to_string() + }; + + if name.len() == 1 { + name.push('\u{17b5}'); + } + + name + } +} + +pub async fn fetch_proxy_profile( + pool: &PgPool, + guild_id: u64, + system_id: i32, + member_id: i32, +) -> anyhow::Result { + // todo: this should be a db view with joins + // this is all the info that proxy_members returned, so a single-member version of that could work nicely + let system = db::get_system_by_id(pool, system_id); + let member = db::get_member_by_id(pool, member_id); + let system_guild = db::get_system_guild(pool, system_id, guild_id as i64); + let member_guild = db::get_member_guild(pool, member_id, guild_id as i64); + + let (system, member, system_guild, member_guild) = + try_join!(system, member, system_guild, member_guild)?; + + let system = system.ok_or_else(|| anyhow::anyhow!("could not find system"))?; + let member = member.ok_or_else(|| anyhow::anyhow!("could not find member"))?; + + Ok(ProxyProfile { + system, + member, + system_guild, + member_guild, + }) +} + +#[cfg(test)] +mod tests { + // todo: this code is gonna be easy to unit test so we should do that +} \ No newline at end of file diff --git a/rustproxy/pk_bot/src/proxy/reply.rs b/rustproxy/pk_bot/src/proxy/reply.rs new file mode 100644 index 00000000..44c37042 --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/reply.rs @@ -0,0 +1,57 @@ +use twilight_http::Client; +use twilight_model::{ + channel::{embed::Embed, Message}, + id::{ + marker::{GuildMarker, UserMarker}, + Id, + }, + util::ImageHash, +}; +use twilight_util::builder::embed::{EmbedAuthorBuilder, EmbedBuilder, ImageSource}; + +async fn _fetch_additional_reply_info(_http: &Client) -> anyhow::Result<()> { + Ok(()) +} + +pub fn create_reply_embed( + guild_id: Id, + replied_to: &Message, +) -> anyhow::Result { + // todo: guild avatars, guild nicknames + // probably put this in fetch_additional_reply_info + + let author = { + let icon = replied_to + .author + .avatar + .map(|hash| get_avatar_url(replied_to.author.id, hash)) + .and_then(|url| ImageSource::url(url).ok()); + + let mut builder = EmbedAuthorBuilder::new(replied_to.author.name.clone()); + if let Some(icon) = icon { + builder = builder.icon_url(icon); + }; + builder.build() + }; + + let content = { + let jump_link = format!( + "https://discord.com/channels/{}/{}/{}", + guild_id, replied_to.channel_id, replied_to.id + ); + + let content = format!("**[Reply to:]({})** ", jump_link); + // todo: properly add truncated content (including handling links/spoilers/etc) + content + }; + + let builder = EmbedBuilder::new().description(content).author(author); + Ok(builder.build()) +} + +fn get_avatar_url(user_id: Id, hash: ImageHash) -> String { + format!( + "https://cdn.discordapp.com/avatars/{}/{}.png", + user_id, hash + ) +} diff --git a/rustproxy/pk_bot/src/proxy/tags.rs b/rustproxy/pk_bot/src/proxy/tags.rs new file mode 100644 index 00000000..6883eeeb --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/tags.rs @@ -0,0 +1,93 @@ +use crate::db::ProxyTagEntry; +use tracing::info; + +#[derive(Debug)] +pub struct ProxyTagMatch { + pub inner_content: String, + pub tags: (String, String), + pub member_id: i32, +} + +pub fn match_proxy_tags(tags: &[ProxyTagEntry], content: &str) -> Option { + let content = content.trim(); + + let mut sorted_entries = tags.to_vec(); + sorted_entries.sort_by_key(|x| -((x.prefix.len() + x.suffix.len()) as i32)); + + for entry in sorted_entries { + let is_tag_match = content.starts_with(&entry.prefix) && content.ends_with(&entry.suffix); + info!( + "prefix: {}, suffix: {}, content: {}, is_match: {}", + entry.prefix, entry.suffix, content, is_tag_match + ); + + // todo: extract leading mentions + // todo: allow empty matches only if we're proxying an attachment + // todo: properly handle <>s etc, there's some regex stuff in there i don't entirely understand + // todo: there's some weird edge cases with various unicode control characters and emoji joiners and whatever, should figure that out + unit test it + if is_tag_match { + let inner_content = + &content[entry.prefix.len()..(content.len() - entry.suffix.len())].trim(); + return Some(ProxyTagMatch { + inner_content: inner_content.to_string(), + tags: (entry.prefix, entry.suffix), + member_id: entry.member_id, + }); + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_match() { + let tags = vec![ + ("[", "]", 0).into(), + ("[[", "]]", 1).into(), + ("P:", "", 2).into(), + ("", "-P", 3).into(), + ("+ ", "", 4).into(), + ]; + assert_no_match(&tags, "hello world"); + assert_match(&tags, "[hello world]", "hello world", 0); + assert_match(&tags, "[ hello world ]", "hello world", 0); + assert_match(&tags, " [ hello world ] ", "hello world", 0); + assert_match(&tags, "[\nhello\n]", "hello", 0); + assert_match(&tags, "[\nhello\nworld\n]", "hello\nworld", 0); + + assert_match(&tags, "[[text]]", "text", 1); + assert_match(&tags, "[text]]", "text]", 0); + assert_match(&tags, "[[[text]]]", "[text]", 1); + + assert_match(&tags, "P:text", "text", 2); + assert_match(&tags, "text -P", "text", 3); + + assert_match(&tags, "+ hello", "hello", 4); + assert_no_match(&tags, "+hello"); // (prefix contains trailing space) + + assert_match(&tags, "[]", "", 0); + + // edge case: the c# implementation currently does what the commented out test does + // *if* the message doesn't have an attachment. not sure if we should mirror this here. + // assert_match(&tags, "[[]]", "[]", 0); + assert_match(&tags, "[[]]", "", 1); + } + + fn assert_match(tags: &[ProxyTagEntry], message: &str, inner: &str, member: i32) { + let res = match_proxy_tags(tags, message); + assert_eq!( + res.as_ref() + .map(|x| (x.inner_content.as_str(), x.member_id)), + Some((inner, member)) + ); + } + + fn assert_no_match(tags: &[ProxyTagEntry], message: &str) { + let res = match_proxy_tags(tags, message); + assert!(res.is_none()); + } +} diff --git a/rustproxy/pk_bot/src/proxy/webhook.rs b/rustproxy/pk_bot/src/proxy/webhook.rs new file mode 100644 index 00000000..14062cd8 --- /dev/null +++ b/rustproxy/pk_bot/src/proxy/webhook.rs @@ -0,0 +1,143 @@ +use moka::future::Cache; +use once_cell::sync::Lazy; +use tracing::info; +use twilight_http::Client; +use twilight_model::channel::{embed::Embed, Webhook, WebhookType}; + +// space for 1 million is probably way overkill, this is a LRU cache so it's okay to evict occasionally +static WEBHOOK_CACHE: Lazy> = Lazy::new(|| Cache::new(1024 * 1024)); +const WEBHOOK_NAME: &str = "PluralKit Proxy Webhook"; + +#[derive(Debug, Clone)] +pub struct CachedWebhook { + pub id: u64, + pub token: String, +} + +pub async fn get_webhook_cached(http: &Client, channel_id: u64) -> anyhow::Result { + let res = WEBHOOK_CACHE + .try_get_with(channel_id, fetch_or_create_pk_webhook(http, channel_id)) + .await; + + // todo: what happens if fetch_or_create_pk_webhook errors? i think moka handles it properly and just retries + // but i'm not entiiiirely sure + // https://docs.rs/moka/0.8.5/moka/future/struct.Cache.html#method.try_get_with + + // error is Arc here and it's hard to convert that into an owned ref so we just make a new error lmao + res.map_err(|_e| anyhow::anyhow!( + "could not fetch webhook: {}", _e + )) +} + +async fn fetch_or_create_pk_webhook( + http: &Client, + channel_id: u64, +) -> anyhow::Result { + match fetch_pk_webhook(http, channel_id).await? { + Some(hook) => Ok(hook), + None => create_pk_webhook(http, channel_id).await, + } +} + +async fn fetch_pk_webhook(http: &Client, channel_id: u64) -> anyhow::Result> { + info!("cache miss, fetching webhook for channel {}", channel_id); + + let webhooks = http + .channel_webhooks(channel_id.try_into().unwrap()) + .exec() + .await? + .models() + .await?; + + webhooks + .iter() + .find(|wh| is_proxy_webhook(wh)) + .map(|x| { + let token = x + .token + .as_ref() + .map(|x| x.to_string()) + .ok_or_else(|| anyhow::anyhow!("webhook should contain token")); + + token.map(|token| CachedWebhook { + id: x.id.get(), + token, + }) + }) + .transpose() +} + +async fn create_pk_webhook(http: &Client, channel_id: u64) -> anyhow::Result { + let response = http + .create_webhook(channel_id.try_into().unwrap(), WEBHOOK_NAME)? + .exec() + .await?; + + // todo: error handling here + let val = response.model().await?; + Ok(CachedWebhook { + id: val.id.get(), + token: val + .token + .ok_or_else(|| anyhow::anyhow!("webhook should contain token"))?, + }) +} + +fn is_proxy_webhook(wh: &Webhook) -> bool { + wh.kind == WebhookType::Incoming + && wh.token.is_some() + && wh.name.as_deref() == Some(WEBHOOK_NAME) +} + +#[derive(Debug)] +pub struct WebhookExecuteRequest { + pub channel_id: u64, + pub username: String, + pub avatar_url: Option, + pub content: Option, + pub embed: Option, +} + +#[derive(Debug)] +pub struct WebhookExecuteResult { + pub message_id: u64, +} + +pub async fn execute_webhook( + http: &Client, + req: &WebhookExecuteRequest, +) -> anyhow::Result { + let webhook = get_webhook_cached(http, req.channel_id).await?; + let mut request = http + .execute_webhook(webhook.id.try_into().unwrap(), &webhook.token) + .username(&req.username)?; + + if let Some(ref content) = req.content { + request = request.content(content)?; + } + + if let Some(ref avatar_url) = req.avatar_url { + request = request.avatar_url(avatar_url); + } + + let mut embeds = Vec::new(); + if let Some(ref embed) = req.embed { + embeds.push(embed.clone()); + request = request.embeds(&embeds)?; + } + + // todo: handle error if webhook was deleted, should invalidate and retry + let result = request.wait().exec().await?; + + let model = result.model().await?; + if model.channel_id != req.channel_id { + // it's possible for someone to "redirect" a webhook to another channel + // and the only way we find out is when we send a message. + // if this has happened remove it from cache and refetch later + WEBHOOK_CACHE.invalidate(&req.channel_id).await; + } + + Ok(WebhookExecuteResult { + message_id: model.id.get(), + }) +} diff --git a/rustproxy/pk_bot/src/redis.rs b/rustproxy/pk_bot/src/redis.rs new file mode 100644 index 00000000..cf746261 --- /dev/null +++ b/rustproxy/pk_bot/src/redis.rs @@ -0,0 +1,168 @@ +use crate::config::BotConfig; +use once_cell::sync::Lazy; +use redis::aio::ConnectionManager; +use std::fmt::Debug; +use std::time::Duration; +use tracing::{error, info}; +use twilight_gateway::Event; +use twilight_gateway_queue::Queue; +use twilight_model::gateway::event::{DispatchEvent, GatewayEvent, GatewayEventDeserializer}; + +#[derive(Clone)] +pub struct RedisEventProxy { + // todo: don't know if i want this struct to have responsibility for ignoring calls if redis is disabled + inner: Option, +} + +// events that should be sent in the raw handler +// does not include message create/update since we want first dibs on those and pass them on later +static ALLOWED_EVENTS: Lazy> = Lazy::new(|| { + vec![ + "INTERACTION_CREATE", + "MESSAGE_DELETE", + "MESSAGE_DELETE_BULK", + "MESSAGE_REACTION_ADD", + "READY", + "GUILD_CREATE", + "GUILD_UPDATE", + "GUILD_DELETE", + "GUILD_ROLE_CREATE", + "GUILD_ROLE_UPDATE", + "GUILD_ROLE_DELETE", + "CHANNEL_CREATE", + "CHANNEL_UPDATE", + "CHANNEL_DELETE", + "THREAD_CREATE", + "THREAD_UPDATE", + "THREAD_DELETE", + "THREAD_LIST_SYNC", + ] +}); + +impl RedisEventProxy { + async fn send_event_raw_inner(&mut self, shard_id: u64, payload: &[u8]) -> anyhow::Result<()> { + if let Some(ref mut redis) = self.inner { + info!(shard_id = shard_id, "publishing event"); + let key = format!("evt-{}", shard_id); + + redis::cmd("PUBLISH") + .arg(&key[..]) + .arg(payload) + .query_async(redis) + .await?; + } + + Ok(()) + } + + pub async fn send_event_raw(&mut self, shard_id: u64, payload: &[u8]) -> anyhow::Result<()> { + let payload_str = std::str::from_utf8(payload)?; + + if let Some(deser) = GatewayEventDeserializer::from_json(payload_str) { + if let Some(event_type) = deser.event_type_ref() { + if ALLOWED_EVENTS.contains(&event_type) { + self.send_event_raw_inner(shard_id, payload).await?; + } + } + } + + Ok(()) + } + + pub async fn send_event_parsed(&mut self, shard_id: u64, evt: Event) -> anyhow::Result<()> { + info!(shard_id, "sending parsed: {:?}", evt.kind()); + + let dispatch_event = DispatchEvent::try_from(evt)?; + let gateway_event = GatewayEvent::Dispatch(0, Box::new(dispatch_event)); + let buf = serde_json::to_vec(&gateway_event)?; + self.send_event_raw_inner(shard_id, &buf).await?; + Ok(()) + } +} + +pub async fn connect_to_redis(addr: &str) -> anyhow::Result { + let client = redis::Client::open(addr)?; + info!("connecting to redis at {}...", addr); + Ok(ConnectionManager::new(client).await?) +} + +pub async fn init_event_proxy(config: &BotConfig) -> anyhow::Result { + let mgr = if let Some(redis_addr) = &config.redis_addr { + Some(connect_to_redis(redis_addr).await?) + } else { + info!("no redis address specified, skipping"); + None + }; + + Ok(RedisEventProxy { inner: mgr }) +} + +#[derive(Clone)] +pub struct RedisQueue { + pub redis: ConnectionManager, + pub concurrency: u64, +} + +impl Debug for RedisQueue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RedisQueue") + .field("concurrency", &self.concurrency) + .finish() + } +} + +impl Queue for RedisQueue { + fn request<'a>( + &'a self, + shard_id: [u64; 2], + ) -> std::pin::Pin + Send + 'a>> { + Box::pin(request_inner( + self.redis.clone(), + self.concurrency, + *shard_id.first().unwrap(), + )) + } +} + +async fn request_inner(mut client: ConnectionManager, concurrency: u64, shard_id: u64) { + let bucket = shard_id % concurrency; + let key = format!("pluralkit:identify:{}", bucket); + + // SET bucket 1 EX 6 NX = write a key expiring after 6 seconds if there's not already one + let mut cmd = redis::cmd("SET"); + cmd.arg(key).arg("1").arg("EX").arg(6i8).arg("NX"); + + info!(shard_id, bucket, "waiting for allowance..."); + loop { + let done = cmd + .clone() + .query_async::<_, Option>(&mut client) + .await; + match done { + Ok(Some(_)) => { + info!(shard_id, bucket, "got allowance!"); + return; + } + Ok(None) => { + // not allowed yet, waiting + } + Err(e) => { + error!(shard_id, bucket, "error getting shard allowance: {}", e) + } + } + + tokio::time::sleep(Duration::from_millis(500)).await; + } +} + +pub async fn init_gateway_queue(config: &BotConfig) -> anyhow::Result> { + let queue = if let Some(ref addr) = config.redis_gateway_queue_addr { + let redis = connect_to_redis(addr).await?; + let concurrency = config.max_concurrency.unwrap_or(1); + Some(RedisQueue { redis, concurrency }) + } else { + None + }; + + Ok(queue) +} diff --git a/rustproxy/pk_command_parser/Cargo.toml b/rustproxy/pk_command_parser/Cargo.toml new file mode 100644 index 00000000..b38bd1e8 --- /dev/null +++ b/rustproxy/pk_command_parser/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pk_command_parser" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.57" +matches = "0.1.9" +slab = "0.4.6" diff --git a/rustproxy/pk_command_parser/src/lib.rs b/rustproxy/pk_command_parser/src/lib.rs new file mode 100644 index 00000000..b36655af --- /dev/null +++ b/rustproxy/pk_command_parser/src/lib.rs @@ -0,0 +1,2 @@ +mod matcher; +mod tokenizer; \ No newline at end of file diff --git a/rustproxy/pk_command_parser/src/matcher.rs b/rustproxy/pk_command_parser/src/matcher.rs new file mode 100644 index 00000000..94392640 --- /dev/null +++ b/rustproxy/pk_command_parser/src/matcher.rs @@ -0,0 +1,177 @@ +use std::ops::Range; + +use crate::tokenizer::{Token, Tokenizer}; + +#[derive(Debug)] +pub enum Segment { + Word(Vec), + Parameter { name: String, optional: bool }, +} + +#[derive(Debug)] +pub struct Pattern { + segments: Vec, +} + +#[derive(Debug, PartialEq)] +pub struct ParameterMatch { + name: String, + value: String, + span: Range, +} + +#[derive(Debug, PartialEq)] +pub struct FlagMatch { + name: String, + value: Option, +} + +#[derive(Debug, PartialEq)] +pub struct MatchResult { + parameters: Vec, + flags: Vec, + remainder: Option, +} + +pub fn does_match(s: &str, pat: &Pattern) -> Option { + let mut flags = Vec::new(); + let mut parameters = Vec::new(); + + let mut remainder_pos = None; + + let mut segments = pat.segments.iter().peekable(); + let mut tokenizer = Tokenizer::new(s); + + // loop until we find a keyword token + while let Some(token) = tokenizer.next() { + match token { + Token::Flag { name, .. } => { + // flags are set aside + flags.push(FlagMatch { name, value: None }); + } + Token::Keyword { + value, + quoted: _, + span, + } => { + let mut next_segment = segments.next(); + + match next_segment { + Some(Segment::Word(options)) => { + // keyword doesn't match? definitely not a match then + if !matches_word(&value, &options) { + return None; + } + } + Some(Segment::Parameter { name, optional }) => { + // for an optional parameter, check the next token instead and consume + if let Some(Segment::Word(options)) = segments.peek() { + if *optional { + if !matches_word(&value, &options) { + return None; + } + + segments.next(); + continue; + } + } + + // set parameter aside for later + parameters.push(ParameterMatch { + name: name.clone(), + span: span, + value: value.clone(), + }); + } + None => { + // out of segments to match, but we already consumed the next token + // so set position aside for remainder and exit + remainder_pos = Some(span.start); + break; + } + } + } + } + } + + Some(MatchResult { + parameters, + flags, + remainder: remainder_pos.map(|x| s[x..].to_string()), + }) +} + +fn matches_word(word: &str, options: &[String]) -> bool { + options.iter().any(|o| o.eq_ignore_ascii_case(word)) +} + +struct PatternBuilder { + segments: Vec, +} + +impl PatternBuilder { + fn new() -> PatternBuilder { + PatternBuilder { + segments: Vec::new(), + } + } + + fn word(mut self, options: &[&str]) -> PatternBuilder { + self.segments.push(Segment::Word( + options.iter().map(|x| x.to_string()).collect(), + )); + self + } + + fn param(mut self, name: &str) -> PatternBuilder { + self.segments.push(Segment::Parameter { + name: name.to_string(), + optional: false, + }); + self + } + + fn param_opt(mut self, name: &str) -> PatternBuilder { + self.segments.push(Segment::Parameter { + name: name.to_string(), + optional: true, + }); + self + } + + fn build(self) -> Pattern { + Pattern { + segments: self.segments, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hi() { + let pat = PatternBuilder::new() + .word(&["m", "member"]) + .param("member_ref") + .word(&["desc", "d"]) + .build(); + + assert_eq!( + does_match("member \"Hello World\" -raw desc More text goes here", &pat), + Some(MatchResult { + parameters: vec![ParameterMatch { + name: "member_ref".to_string(), + value: "Hello World".to_string(), + span: 7..20 + }], + flags: vec![FlagMatch { + name: "raw".to_string(), + value: None + }], + remainder: Some("More text goes here".to_string()) + }) + ); + } +} diff --git a/rustproxy/pk_command_parser/src/tokenizer.rs b/rustproxy/pk_command_parser/src/tokenizer.rs new file mode 100644 index 00000000..d05975a5 --- /dev/null +++ b/rustproxy/pk_command_parser/src/tokenizer.rs @@ -0,0 +1,266 @@ +use std::ops::Range; + +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + Keyword { + value: String, + quoted: bool, + span: Range, + }, + Flag { + name: String, + value: Option, + span: Range, + }, +} + +pub struct Tokenizer<'a> { + inner: &'a str, + words: WordSpanIterator<'a>, +} + +impl<'a> Tokenizer<'a> { + pub fn new(s: &str) -> Tokenizer { + Tokenizer { + inner: s, + words: WordSpanIterator::new(s), + } + } + + fn next_token(&mut self) -> Option { + self.words.next().map(|span| { + let word = &self.inner[span.clone()]; + + if let Some((inner, quoted_span)) = self.try_read_quoted_token(span.clone()) { + Token::Keyword { + value: inner.to_string(), + quoted: true, + span: quoted_span, + } + } else if word.starts_with("-") { + let flag_name = word.trim_start_matches('-'); + + let (flag_name, flag_value) = match flag_name.split_once('=') { + Some((flag_name, flag_value)) => { + (flag_name.to_string(), Some(flag_value.to_string())) + } + None => (flag_name.to_string(), None), + }; + + Token::Flag { + name: flag_name, + value: flag_value, + span, + } + } else { + Token::Keyword { + value: word.to_string(), + quoted: false, + span: span, + } + } + }) + } + + fn try_read_quoted_token(&mut self, span: Range) -> Option<(&str, Range)> { + let start_pos = span.start; + find_quote_pair(&self.inner[span.start..]).map(|(left_quote, right_quotes)| { + let mut word_span = span; + word_span.start += left_quote.len_utf8(); + + // effectively do-while-let but rust doesn't have that :/ + loop { + let end_word = &self.inner[word_span.clone()]; + + for right_quote in right_quotes { + if end_word.ends_with(*right_quote) { + let end_pos = word_span.end; + let inner_span = + (start_pos + left_quote.len_utf8())..(end_pos - right_quote.len_utf8()); + let inner_str = &self.inner[inner_span]; + return (inner_str, start_pos..end_pos); + } + } + + if let Some(next_word_span) = self.words.next() { + word_span = next_word_span; + } else { + break; + } + } + + (&self.inner[start_pos..], start_pos..self.inner.len()) + }) + } +} + +impl<'a> Iterator for Tokenizer<'a> { + type Item = Token; + + fn next(&mut self) -> Option { + self.next_token() + } +} + +struct WordSpanIterator<'a> { + iter: std::str::SplitInclusive<'a, fn(char) -> bool>, + pos: usize, +} + +impl<'a> WordSpanIterator<'a> { + fn new(s: &'a str) -> WordSpanIterator<'a> { + WordSpanIterator { + iter: s.split_inclusive(char::is_whitespace), + pos: 0, + } + } +} + +impl<'a> Iterator for WordSpanIterator<'a> { + type Item = Range; + + fn next(&mut self) -> Option { + while let Some(word) = self.iter.next() { + let word_start = self.pos; + self.pos += word.len(); + + let trimmed = word.trim_end(); + if word.trim_end().len() > 0 { + let trimmed_span = word_start..(word_start + trimmed.len()); + return Some(trimmed_span); + } + } + + None + } +} + +fn find_quote_pair(s: &str) -> Option<(char, &'static [char])> { + s.chars() + .next() + .and_then(|c| matching_quotes(c).map(|x| (c, x))) +} + +fn matching_quotes(c: char) -> Option<&'static [char]> { + match c { + // Basic + '"' => Some(&['"']), + '\'' => Some(&['\'']), + + // "Smart quotes" + // Specifically ignore the left/right status of the quotes and match any combination of them + // Left string also includes "low" quotes to allow for the low-high style used in some locales + '\u{201c}' | '\u{201d}' | '\u{201f}' | '\u{201e}' => { + Some(&['\u{201c}', '\u{201d}', '\u{201f}']) + } // double + '\u{2018}' | '\u{2019}' | '\u{201b}' | '\u{201a}' => { + Some(&['\u{2018}', '\u{2019}', '\u{201b}']) + } // single + + // Chevrons (normal and "fullwidth" variants) + '\u{00ab}' | '\u{300a}' => Some(&['\u{00bb}', '\u{300b}']), // double chevrons, pointing away (<>) + '\u{00bb}' | '\u{300b}' => Some(&['\u{00aa}', '\u{300a}']), // double chevrons, pointing together (>>text<<) + '\u{2039}' | '\u{3008}' => Some(&['\u{203a}', '\u{3009}']), // single chevrons, pointing away () + '\u{203a}' | '\u{3009}' => Some(&['\u{2039}', '\u{3008}']), // single chevrons, pointing together (>text<) + + // Other + '\u{300c}' | '\u{300e}' => Some(&['\u{300d}', '\u{300f}']), // corner brackets (Japanese/Chinese) + + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_words() { + let s = "hello world abcdefg"; + let mut tk = Tokenizer::new(s); + + assert_word(tk.next(), "hello", false, 0..5); + assert_word(tk.next(), "world", false, 6..11); + assert_word(tk.next(), "abcdefg", false, 12..19); + } + + #[test] + fn ignore_whitespace() { + // U+2003 EM SPACE is 3 utf-8 bytes + let s = " lotsa \u{2003} spaces \t and \t\n\t stuff \n"; + let mut tk = Tokenizer::new(s); + + assert_word(tk.next(), "lotsa", false, 4..9); + assert_word(tk.next(), "spaces", false, 19..25); + assert_word(tk.next(), "and", false, 29..32); + assert_word(tk.next(), "stuff", false, 37..42); + } + + #[test] + fn quoted_words() { + let mut tk = Tokenizer::new("hello \"in double quotes\" 'and single quotes'"); + assert_word(tk.next(), "hello", false, 0..5); + assert_word(tk.next(), "in double quotes", true, 6..24); + assert_word(tk.next(), "and single quotes", true, 25..44); + + let mut tk = Tokenizer::new("\"quote at start of\" string"); + assert_word(tk.next(), "quote at start of", true, 0..19); + assert_word(tk.next(), "string", false, 20..26); + + let mut tk = Tokenizer::new("\"\n include whitespace\nin quotes\n\""); + assert_word( + tk.next(), + "\n include whitespace\nin quotes\n", + true, + 0..34, + ); + + let mut tk = Tokenizer::new("'it's 5 o'clock' said o'brian"); + assert_word(tk.next(), "it's 5 o'clock", true, 0..16); + assert_word(tk.next(), "said", false, 17..21); + assert_word(tk.next(), "o'brian", false, 22..29); + } + + #[test] + fn flags() { + let mut tk = Tokenizer::new("word -flag and-word"); + assert_word(tk.next(), "word", false, 0..4); + assert_flag(tk.next(), "flag", None, 5..10); + assert_word(tk.next(), "and-word", false, 11..19); + + let mut tk = Tokenizer::new("-lots --of ---dashes"); + assert_flag(tk.next(), "lots", None, 0..5); + assert_flag(tk.next(), "of", None, 6..10); + assert_flag(tk.next(), "dashes", None, 11..20); + } + + #[test] + fn flag_values() { + let mut tk = Tokenizer::new("-flag=value --flag2=value2 -flag3=value3=more"); + assert_flag(tk.next(), "flag", Some("value"), 0..11); + assert_flag(tk.next(), "flag2", Some("value2"), 12..26); + assert_flag(tk.next(), "flag3", Some("value3=more"), 27..45); + } + + fn assert_word(tk: Option, s: &str, quoted: bool, span: Range) { + assert_eq!( + tk, + Some(Token::Keyword { + value: s.to_string(), + quoted: quoted, + span: span + }) + ); + } + + fn assert_flag(tk: Option, name: &str, value: Option<&str>, span: Range) { + assert_eq!( + tk, + Some(Token::Flag { + name: name.to_string(), + span: span, + value: value.map(|x| x.to_string()) + }) + ); + } +}