diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 4e98355..166d4c8 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -15,20 +15,22 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install dependencies + - uses: actions/checkout@v3 + - name: Clone spacebar server run: | - sudo apt-get update - sudo apt-get install -y git python3 build-essential - curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - - sudo apt-get install -y nodejs git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: server/package-lock.json - name: Prepare and start Spacebar server run: | npm install npm run setup npm run start & working-directory: ./server + - uses: Swatinem/rust-cache@v2 - name: Build run: cargo build --verbose - name: Run tests diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index ba12407..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Clippy check - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - - -# Make sure CI fails on all warnings, including Clippy lints -env: - RUSTFLAGS: "-Dwarnings" - -jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Run Clippy - run: cargo clippy --all-targets --all-features \ No newline at end of file diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml new file mode 100644 index 0000000..dbbba76 --- /dev/null +++ b/.github/workflows/rust-clippy.yml @@ -0,0 +1,53 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# rust-clippy is a tool that runs a bunch of lints to catch common +# mistakes in your Rust code and help improve your Rust code. +# More details at https://github.com/rust-lang/rust-clippy +# and https://rust-lang.github.io/rust-clippy/ + +name: rust-clippy analyze + +on: + push: + branches: [ "main", "preserve/*" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + +jobs: + rust-clippy-analyze: + name: Run rust-clippy analyzing + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 + with: + profile: minimal + toolchain: stable + components: clippy + override: true + + - name: Install required cargo + run: cargo install clippy-sarif sarif-fmt + + - name: Run rust-clippy + run: + cargo clippy + --all-features + --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt + continue-on-error: true + + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: rust-clippy-results.sarif + wait-for-processing: true diff --git a/.gitignore b/.gitignore index ef337e7..d3170e2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,19 +2,18 @@ # will have compiled files and executables /target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk - # Added by cargo /target -### +# IDE specific folders and configs .vscode/** -.idea/** \ No newline at end of file +.idea/** + +# macOS + +**/.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d193cf4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2561 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[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 = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "async-trait" +version = "0.1.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[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 = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chorus" +version = "0.1.0" +dependencies = [ + "async-trait", + "base64 0.21.2", + "bitflags 2.3.3", + "chorus-macros", + "chrono", + "custom_error", + "futures-util", + "hostname", + "http", + "jsonwebtoken", + "lazy_static", + "log", + "native-tls", + "openssl", + "poem", + "regex", + "reqwest", + "rusty-hook", + "serde", + "serde-aux", + "serde_json", + "serde_repr", + "serde_with", + "sqlx", + "thiserror", + "tokio", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "chorus-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.26", +] + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ci_info" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f638c70e8c5753795cc9a8c07c44da91554a09e4cf11a7326e8161b0a3c45e" +dependencies = [ + "envmnt", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "custom_error" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.26", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", + "crypto-bigint", + "pem-rfc7468", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "envmnt" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059" +dependencies = [ + "fsio", + "indexmap 1.9.3", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsio" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", +] + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "ipnetwork" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.2", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[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.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nias" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[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-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[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.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[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.6", +] + +[[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.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01de5d978f34aa4b2296576379fcc416034702fd94117c56ffd8a1a767cefb30" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78f66c04ccc83dd4486fd46c33896f4e17b24a7a3a6400dedc48ed0ddd72320" +dependencies = [ + "der", + "pkcs8", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "poem" +version = "1.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a56df40b79ebdccf7986b337f9b0e51ac55cd5e9d21fb20b6aa7c7d49741854" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "headers", + "http", + "hyper", + "mime", + "parking_lot 0.12.1", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "regex", + "rfc7239", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "poem-derive" +version = "1.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1701f977a2d650a03df42c053686ea0efdb83554f34c7b026b89383c0a1b7846" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rfc7239" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087317b3cf7eb481f13bd9025d729324b7cd068d6f470e2d76d049e191f5ba47" +dependencies = [ + "uncased", +] + +[[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 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rsa" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf22754c49613d2b3b119f0e5d46e34a2c628a937e3024b8762de4e7d8c710b" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "smallvec", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-hook" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cee9be61be7e1cbadd851e58ed7449c29c620f00b23df937cb9cbc04ac21a3" +dependencies = [ + "ci_info", + "getopts", + "nias", + "toml", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-aux" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3dfe1b7eb6f9dcf011bd6fad169cdeaae75eda0d61b1a99a3f015b41b0cae39" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "serde_derive" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "serde_json" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" +dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.23", +] + +[[package]] +name = "serde_with_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time 0.3.23", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "ahash 0.7.6", + "atoi", + "bitflags 1.3.2", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "digest", + "dotenvy", + "either", + "event-listener", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "generic-array", + "hashlink", + "hex", + "indexmap 1.9.3", + "ipnetwork", + "itoa", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "num-bigint", + "once_cell", + "paste", + "percent-encoding", + "rand", + "rsa", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "dotenvy", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "native-tls", + "once_cell", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "stringprep" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +dependencies = [ + "time-core", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +dependencies = [ + "autocfg", + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uncased" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "version_check", +] + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[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 = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "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.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.26", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index cece3e2..8dbb172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,31 +10,35 @@ backend = ["poem", "sqlx"] client = [] [dependencies] -tokio = {version = "1.28.1", features = ["rt", "macros", "rt-multi-thread", "full"]} -serde = {version = "1.0.163", features = ["derive"]} -serde_json = {version= "1.0.96", features = ["raw_value"]} +tokio = {version = "1.29.1", features = ["macros"]} +serde = {version = "1.0.171", features = ["derive"]} +serde_json = {version= "1.0.103", features = ["raw_value"]} serde-aux = "4.2.0" serde_with = "3.0.0" -serde_repr = "0.1.12" -reqwest = {version = "0.11.16", features = ["multipart"]} -url = "2.3.1" -chrono = {version = "0.4.24", features = ["serde"]} -regex = "1.7.3" +serde_repr = "0.1.14" +reqwest = {version = "0.11.18", features = ["multipart"]} +url = "2.4.0" +chrono = {version = "0.4.26", features = ["serde"]} +regex = "1.9.1" custom_error = "1.9.2" native-tls = "0.2.11" tokio-tungstenite = {version = "0.19.0", features = ["native-tls"]} futures-util = "0.3.28" http = "0.2.9" -openssl = "0.10.52" +openssl = "0.10.55" base64 = "0.21.2" hostname = "0.3.1" -bitflags = { version = "2.2.1", features = ["serde"] } +bitflags = { version = "2.3.3", features = ["serde"] } lazy_static = "1.4.0" -poem = { version = "1.3.55", optional = true } +poem = { version = "1.3.56", optional = true } sqlx = { git = "https://github.com/zert3x/sqlx", branch="feature/skip", features = ["mysql", "sqlite", "json", "chrono", "ipnetwork", "runtime-tokio-native-tls", "any"], optional = true } -thiserror = "1.0.40" +thiserror = "1.0.43" jsonwebtoken = "8.3.0" +log = "0.4.19" +async-trait = "0.1.71" +chorus-macros = {path = "chorus-macros"} [dev-dependencies] +tokio = {version = "1.29.1", features = ["full"]} lazy_static = "1.4.0" -rusty-hook = "0.11.2" \ No newline at end of file +rusty-hook = "0.11.2" diff --git a/README.md b/README.md index 9a75a54..9292a46 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ accepted, if it violates these guidelines or [our Code of Conduct](https://githu - [x] Channel creation - [x] Channel deletion - [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48) - - [ ] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45) - - [ ] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45) - - [ ] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89) + - [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45) + - [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45) + - [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89) - [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91) - [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90) - [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85) diff --git a/chorus-macros/Cargo.lock b/chorus-macros/Cargo.lock new file mode 100644 index 0000000..4aa96d3 --- /dev/null +++ b/chorus-macros/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "chorus-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" diff --git a/chorus-macros/Cargo.toml b/chorus-macros/Cargo.toml new file mode 100644 index 0000000..cffffe1 --- /dev/null +++ b/chorus-macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "chorus-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = "2" diff --git a/chorus-macros/src/lib.rs b/chorus-macros/src/lib.rs new file mode 100644 index 0000000..c8d5e87 --- /dev/null +++ b/chorus-macros/src/lib.rs @@ -0,0 +1,18 @@ +use proc_macro::TokenStream; +use quote::quote; + +#[proc_macro_derive(Updateable)] +pub fn updateable_macro_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + // No need for macro hygiene, we're only using this in chorus + quote! { + impl Updateable for #name { + fn id(&self) -> Snowflake { + self.id + } + } + } + .into() +} diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index d8762e0..1572aa9 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use chorus::{ self, gateway::{Gateway, Observer}, @@ -15,9 +16,10 @@ pub struct ExampleObserver {} // This struct can observe GatewayReady events when subscribed, because it implements the trait Observer. // The Observer trait can be implemented for a struct for a given websocketevent to handle observing it // One struct can be an observer of multiple websocketevents, if needed +#[async_trait] impl Observer for ExampleObserver { // After we subscribe to an event this function is called every time we receive it - fn update(&self, _data: &GatewayReady) { + async fn update(&self, _data: &GatewayReady) { println!("Observed Ready!"); } } diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 9670d15..68604d9 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -2,64 +2,48 @@ use std::cell::RefCell; use std::rc::Rc; use reqwest::Client; -use serde_json::{from_str, json}; +use serde_json::to_string; -use crate::api::limits::LimitType; -use crate::errors::{ChorusLibError, ChorusResult}; +use crate::api::LimitType; +use crate::errors::ChorusResult; +use crate::gateway::Gateway; use crate::instance::{Instance, UserMeta}; -use crate::limit::LimitedRequester; -use crate::types::{ErrorResponse, LoginResult, LoginSchema}; +use crate::ratelimiter::ChorusRequest; +use crate::types::{GatewayIdentifyPayload, LoginResult, LoginSchema}; impl Instance { pub async fn login_account(&mut self, login_schema: &LoginSchema) -> ChorusResult { - let json_schema = json!(login_schema); - let client = Client::new(); let endpoint_url = self.urls.api.clone() + "/auth/login"; - let request_builder = client.post(endpoint_url).body(json_schema.to_string()); + let chorus_request = ChorusRequest { + request: Client::new() + .post(endpoint_url) + .body(to_string(login_schema).unwrap()), + limit_type: LimitType::AuthLogin, + }; // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since login is an instance wide limit), which is why we are just cloning the // instances' limits to pass them on as user_rate_limits later. - let mut cloned_limits = self.limits.clone(); - let response = LimitedRequester::send_request( - request_builder, - LimitType::AuthRegister, - self, - &mut cloned_limits, - ) - .await; - if response.is_err() { - return Err(ChorusLibError::NoResponse); + let mut shell = + UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await; + let login_result = chorus_request + .deserialize_response::(&mut shell) + .await?; + let object = self.get_user(login_result.token.clone(), None).await?; + if self.limits_information.is_some() { + self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); } - - let response_unwrap = response.unwrap(); - let status = response_unwrap.status(); - let response_text_string = response_unwrap.text().await.unwrap(); - if status.is_client_error() { - let json: ErrorResponse = serde_json::from_str(&response_text_string).unwrap(); - let error_type = json.errors.errors.iter().next().unwrap().0.to_owned(); - let mut error = "".to_string(); - for (_, value) in json.errors.errors.iter() { - for error_item in value._errors.iter() { - error += &(error_item.message.to_string() + " (" + &error_item.code + ")"); - } - } - return Err(ChorusLibError::InvalidFormBodyError { error_type, error }); - } - - let cloned_limits = self.limits.clone(); - let login_result: LoginResult = from_str(&response_text_string).unwrap(); - let object = self - .get_user(login_result.token.clone(), None) - .await - .unwrap(); + let mut identify = GatewayIdentifyPayload::common(); + let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + identify.token = login_result.token.clone(); + gateway.send_identify(identify).await; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), login_result.token, - cloned_limits, + self.clone_limits_if_some(), login_result.settings, object, + gateway, ); - Ok(user) } } diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index b22d6ce..e818d82 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -1,14 +1,16 @@ use std::{cell::RefCell, rc::Rc}; use reqwest::Client; -use serde_json::{from_str, json}; +use serde_json::to_string; +use crate::gateway::Gateway; +use crate::types::GatewayIdentifyPayload; use crate::{ - api::limits::LimitType, - errors::{ChorusLibError, ChorusResult}, + api::policies::instance::LimitType, + errors::ChorusResult, instance::{Instance, Token, UserMeta}, - limit::LimitedRequester, - types::{ErrorResponse, RegisterSchema}, + ratelimiter::ChorusRequest, + types::RegisterSchema, }; impl Instance { @@ -25,51 +27,38 @@ impl Instance { &mut self, register_schema: &RegisterSchema, ) -> ChorusResult { - let json_schema = json!(register_schema); - let client = Client::new(); let endpoint_url = self.urls.api.clone() + "/auth/register"; - let request_builder = client.post(endpoint_url).body(json_schema.to_string()); + let chorus_request = ChorusRequest { + request: Client::new() + .post(endpoint_url) + .body(to_string(register_schema).unwrap()), + limit_type: LimitType::AuthRegister, + }; // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since register is an instance wide limit), which is why we are just cloning // the instances' limits to pass them on as user_rate_limits later. - let mut cloned_limits = self.limits.clone(); - let response = LimitedRequester::send_request( - request_builder, - LimitType::AuthRegister, - self, - &mut cloned_limits, - ) - .await; - if response.is_err() { - return Err(ChorusLibError::NoResponse); - } - - let response_unwrap = response.unwrap(); - let status = response_unwrap.status(); - let response_unwrap_text = response_unwrap.text().await.unwrap(); - let token = from_str::(&response_unwrap_text).unwrap(); - let token = token.token; - if status.is_client_error() { - let json: ErrorResponse = serde_json::from_str(&token).unwrap(); - let error_type = json.errors.errors.iter().next().unwrap().0.to_owned(); - let mut error = "".to_string(); - for (_, value) in json.errors.errors.iter() { - for error_item in value._errors.iter() { - error += &(error_item.message.to_string() + " (" + &error_item.code + ")"); - } - } - return Err(ChorusLibError::InvalidFormBodyError { error_type, error }); + let mut shell = + UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await; + let token = chorus_request + .deserialize_response::(&mut shell) + .await? + .token; + if self.limits_information.is_some() { + self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap(); } let user_object = self.get_user(token.clone(), None).await.unwrap(); - let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self) - .await - .unwrap(); + let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?; + let mut identify = GatewayIdentifyPayload::common(); + let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + identify.token = token.clone(); + gateway.send_identify(identify).await; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), token.clone(), - cloned_limits, + self.clone_limits_if_some(), settings, user_object, + gateway, ); Ok(user) } diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index cbe481c..df8e290 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -1,33 +1,25 @@ use reqwest::Client; use serde_json::to_string; +use crate::types::AddChannelRecipientSchema; use crate::{ - api::common, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::UserMeta, + ratelimiter::ChorusRequest, types::{Channel, ChannelModifySchema, GetChannelMessagesSchema, Message, Snowflake}, }; impl Channel { pub async fn get(user: &mut UserMeta, channel_id: Snowflake) -> ChorusResult { - let url = user.belongs_to.borrow_mut().urls.api.clone(); - let request = Client::new() - .get(format!("{}/channels/{}/", url, channel_id)) - .bearer_auth(user.token()); - - let result = common::deserialize_response::( - request, - user, - crate::api::limits::LimitType::Channel, - ) - .await; - if result.is_err() { - return Err(ChorusLibError::RequestErrorError { - url: format!("{}/channels/{}/", url, channel_id), - error: result.err().unwrap().to_string(), - }); - } - Ok(result.unwrap()) + let url = user.belongs_to.borrow().urls.api.clone(); + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!("{}/channels/{}/", url, channel_id)) + .bearer_auth(user.token()), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.deserialize_response::(user).await } /// Deletes a channel. @@ -44,15 +36,17 @@ impl Channel { /// /// A `Result` that contains a `ChorusLibError` if an error occurred during the request, or `()` if the request was successful. pub async fn delete(self, user: &mut UserMeta) -> ChorusResult<()> { - let request = Client::new() - .delete(format!( - "{}/channels/{}/", - user.belongs_to.borrow_mut().urls.api, - self.id - )) - .bearer_auth(user.token()); - common::handle_request_as_result(request, user, crate::api::limits::LimitType::Channel) - .await + let chorus_request = ChorusRequest { + request: Client::new() + .delete(format!( + "{}/channels/{}/", + user.belongs_to.borrow().urls.api, + self.id + )) + .bearer_auth(user.token()), + limit_type: LimitType::Channel(self.id), + }; + chorus_request.handle_request_as_result(user).await } /// Modifies a channel. @@ -70,43 +64,94 @@ impl Channel { /// /// A `Result` that contains a `Channel` object if the request was successful, or an `ChorusLibError` if an error occurred during the request. pub async fn modify( - &mut self, + &self, modify_data: ChannelModifySchema, channel_id: Snowflake, user: &mut UserMeta, - ) -> ChorusResult<()> { - let request = Client::new() - .patch(format!( - "{}/channels/{}/", - user.belongs_to.borrow().urls.api, - channel_id - )) - .bearer_auth(user.token()) - .body(to_string(&modify_data).unwrap()); - let new_channel = common::deserialize_response::( - request, - user, - crate::api::limits::LimitType::Channel, - ) - .await?; - let _ = std::mem::replace(self, new_channel); - Ok(()) + ) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .patch(format!( + "{}/channels/{}/", + user.belongs_to.borrow().urls.api, + channel_id + )) + .bearer_auth(user.token()) + .body(to_string(&modify_data).unwrap()), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.deserialize_response::(user).await } pub async fn messages( range: GetChannelMessagesSchema, channel_id: Snowflake, user: &mut UserMeta, - ) -> Result, ChorusLibError> { - let request = Client::new() - .get(format!( - "{}/channels/{}/messages", - user.belongs_to.borrow().urls.api, - channel_id - )) - .bearer_auth(user.token()) - .query(&range); + ) -> Result, ChorusError> { + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/channels/{}/messages", + user.belongs_to.borrow().urls.api, + channel_id + )) + .bearer_auth(user.token()) + .query(&range), + limit_type: Default::default(), + }; - common::deserialize_response::>(request, user, Default::default()).await + chorus_request + .deserialize_response::>(user) + .await + } + + /// # Reference: + /// Read: + pub async fn add_channel_recipient( + &self, + recipient_id: Snowflake, + user: &mut UserMeta, + add_channel_recipient_schema: Option, + ) -> ChorusResult<()> { + let mut request = Client::new() + .put(format!( + "{}/channels/{}/recipients/{}/", + user.belongs_to.borrow().urls.api, + self.id, + recipient_id + )) + .bearer_auth(user.token()); + if let Some(schema) = add_channel_recipient_schema { + request = request.body(to_string(&schema).unwrap()); + } + ChorusRequest { + request, + limit_type: LimitType::Channel(self.id), + } + .handle_request_as_result(user) + .await + } + + /// # Reference: + /// Read: + pub async fn remove_channel_recipient( + &self, + recipient_id: Snowflake, + user: &mut UserMeta, + ) -> ChorusResult<()> { + let request = Client::new() + .delete(format!( + "{}/channels/{}/recipients/{}/", + user.belongs_to.borrow().urls.api, + self.id, + recipient_id + )) + .bearer_auth(user.token()); + ChorusRequest { + request, + limit_type: LimitType::Channel(self.id), + } + .handle_request_as_result(user) + .await } } diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index c61756a..6beec7f 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -3,48 +3,39 @@ use http::HeaderMap; use reqwest::{multipart, Client}; use serde_json::to_string; -use crate::api::deserialize_response; +use crate::api::LimitType; use crate::instance::UserMeta; -use crate::types::{Message, MessageSendSchema, PartialDiscordFileAttachment, Snowflake}; +use crate::ratelimiter::ChorusRequest; +use crate::types::{Message, MessageSendSchema, Snowflake}; impl Message { - /** - Sends a message to the Spacebar server. - # Arguments - * `url_api` - The URL of the Spacebar server's API. - * `message` - The [`Message`] that will be sent to the Spacebar server. - * `limits_user` - The [`Limits`] of the user. - * `limits_instance` - The [`Limits`] of the instance. - * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server. - # Errors - * [`ChorusLibError`] - If the message cannot be sent. - */ pub async fn send( user: &mut UserMeta, channel_id: Snowflake, - message: &mut MessageSendSchema, - files: Option>, - ) -> Result { + mut message: MessageSendSchema, + ) -> Result { let url_api = user.belongs_to.borrow().urls.api.clone(); - if files.is_none() { - let request = Client::new() - .post(format!("{}/channels/{}/messages/", url_api, channel_id)) - .bearer_auth(user.token()) - .body(to_string(message).unwrap()); - deserialize_response::(request, user, crate::api::limits::LimitType::Channel) - .await + if message.attachments.is_none() { + let chorus_request = ChorusRequest { + request: Client::new() + .post(format!("{}/channels/{}/messages/", url_api, channel_id)) + .bearer_auth(user.token()) + .body(to_string(&message).unwrap()), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.deserialize_response::(user).await } else { for (index, attachment) in message.attachments.iter_mut().enumerate() { attachment.get_mut(index).unwrap().set_id(index as i16); } let mut form = reqwest::multipart::Form::new(); - let payload_json = to_string(message).unwrap(); + let payload_json = to_string(&message).unwrap(); let payload_field = reqwest::multipart::Part::text(payload_json); form = form.part("payload_json", payload_field); - for (index, attachment) in files.unwrap().into_iter().enumerate() { + for (index, attachment) in message.attachments.unwrap().into_iter().enumerate() { let (attachment_content, current_attachment) = attachment.move_content(); let (attachment_filename, _) = current_attachment.move_filename(); let part_name = format!("files[{}]", index); @@ -62,36 +53,24 @@ impl Message { form = form.part(part_name, part); } - let request = Client::new() - .post(format!("{}/channels/{}/messages/", url_api, channel_id)) - .bearer_auth(user.token()) - .multipart(form); - - deserialize_response::(request, user, crate::api::limits::LimitType::Channel) - .await + let chorus_request = ChorusRequest { + request: Client::new() + .post(format!("{}/channels/{}/messages/", url_api, channel_id)) + .bearer_auth(user.token()) + .multipart(form), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.deserialize_response::(user).await } } } impl UserMeta { - /// Shorthand call for Message::send() - /** - Sends a message to the Spacebar server. - # Arguments - * `url_api` - The URL of the Spacebar server's API. - * `message` - The [`Message`] that will be sent to the Spacebar server. - * `limits_user` - The [`Limits`] of the user. - * `limits_instance` - The [`Limits`] of the instance. - * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server. - # Errors - * [`ChorusLibError`] - If the message cannot be sent. - */ pub async fn send_message( &mut self, - message: &mut MessageSendSchema, + message: MessageSendSchema, channel_id: Snowflake, - files: Option>, - ) -> Result { - Message::send(self, channel_id, message, files).await + ) -> Result { + Message::send(self, channel_id, message).await } } diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs index 0958821..bc666ff 100644 --- a/src/api/channels/permissions.rs +++ b/src/api/channels/permissions.rs @@ -2,9 +2,10 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::handle_request_as_result, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, PermissionOverwrite, Snowflake}, }; @@ -25,24 +26,25 @@ impl types::Channel { channel_id: Snowflake, overwrite: PermissionOverwrite, ) -> ChorusResult<()> { - let url = { - format!( - "{}/channels/{}/permissions/{}", - user.belongs_to.borrow_mut().urls.api, - channel_id, - overwrite.id - ) - }; + let url = format!( + "{}/channels/{}/permissions/{}", + user.belongs_to.borrow_mut().urls.api, + channel_id, + overwrite.id + ); let body = match to_string(&overwrite) { Ok(string) => string, Err(e) => { - return Err(ChorusLibError::FormCreationError { + return Err(ChorusError::FormCreation { error: e.to_string(), }); } }; - let request = Client::new().put(url).bearer_auth(user.token()).body(body); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().put(url).bearer_auth(user.token()).body(body), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.handle_request_as_result(user).await } /// Deletes a permission overwrite for a channel. @@ -67,7 +69,10 @@ impl types::Channel { channel_id, overwrite_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.handle_request_as_result(user).await } } diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index c445a8b..35dbb94 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -1,10 +1,11 @@ use reqwest::Client; use crate::{ - api::handle_request_as_result, + api::LimitType, errors::ChorusResult, instance::UserMeta, - types::{self, Snowflake}, + ratelimiter::ChorusRequest, + types::{self, PublicUser, Snowflake}, }; /** @@ -16,20 +17,15 @@ pub struct ReactionMeta { } impl ReactionMeta { - /** - Deletes all reactions for a message. - This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user. - - # Arguments - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong. - Fires a `Message Reaction Remove All` Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions) - */ + /// Deletes all reactions for a message. + /// This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user. + /// # Arguments + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong. + /// Fires a `Message Reaction Remove All` Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions) pub async fn delete_all(&self, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/", @@ -37,26 +33,24 @@ impl ReactionMeta { self.channel_id, self.message_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } - /** - Gets a list of users that reacted with a specific emoji to a message. - - # Arguments - * `emoji` - A string slice containing the emoji to search for. The emoji must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. - - # Reference - See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions) - */ - pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { + /// Gets a list of users that reacted with a specific emoji to a message. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to search for. The emoji must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions) + pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/", user.belongs_to.borrow().urls.api, @@ -64,27 +58,27 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().get(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request + .deserialize_response::>(user) + .await } - /** - Deletes all the reactions for a given `emoji` on a message. This endpoint requires the - MANAGE_MESSAGES permission to be present on the current user. - - # Arguments - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. - Fires a `Message Reaction Remove Emoji` Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji](https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji) - */ + /// Deletes all the reactions for a given `emoji` on a message. This endpoint requires the + /// MANAGE_MESSAGES permission to be present on the current user. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. + /// Fires a `Message Reaction Remove Emoji` Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji](https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji) pub async fn delete_emoji(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/", @@ -93,29 +87,28 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } - /** - Create a reaction for the message. - - This endpoint requires the READ_MESSAGE_HISTORY permission - to be present on the current user. Additionally, if nobody else has reacted to the message using - this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current - user. - # Arguments - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. - - # Reference - See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction) - */ + /// Create a reaction for the message. + /// This endpoint requires the READ_MESSAGE_HISTORY permission + /// to be present on the current user. Additionally, if nobody else has reacted to the message using + /// this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current + /// user. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction) + /// pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/@me/", @@ -124,26 +117,24 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().put(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().put(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } - /** - Delete a reaction the current user has made for the message. - - # Arguments - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. - Fires a `Message Reaction Remove` Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) - */ + /// Delete a reaction the current user has made for the message. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. + /// Fires a `Message Reaction Remove` Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/@me/", @@ -152,29 +143,26 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } - /** - Delete a user's reaction to a message. - - This endpoint requires the MANAGE_MESSAGES permission to be present on the current user. - - # Arguments - * `user_id` - ID of the user whose reaction is to be deleted. - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. - Fires a Message Reaction Remove Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) - */ + /// Delete a user's reaction to a message. + /// This endpoint requires the MANAGE_MESSAGES permission to be present on the current user. + /// # Arguments + /// * `user_id` - ID of the user whose reaction is to be deleted. + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. + /// Fires a Message Reaction Remove Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) pub async fn delete_user( &self, user_id: Snowflake, @@ -189,7 +177,10 @@ impl ReactionMeta { emoji, user_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } } diff --git a/src/api/common.rs b/src/api/common.rs deleted file mode 100644 index 86a972e..0000000 --- a/src/api/common.rs +++ /dev/null @@ -1,72 +0,0 @@ -use reqwest::RequestBuilder; -use serde::Deserialize; -use serde_json::from_str; - -use crate::{ - errors::{ChorusLibError, ChorusResult}, - instance::UserMeta, - limit::LimitedRequester, -}; - -use super::limits::LimitType; - -/// Sends a request to wherever it needs to go and performs some basic error handling. -pub async fn handle_request( - request: RequestBuilder, - user: &mut UserMeta, - limit_type: LimitType, -) -> Result { - LimitedRequester::send_request( - request, - limit_type, - &mut user.belongs_to.borrow_mut(), - &mut user.limits, - ) - .await -} - -/// Sends a request to wherever it needs to go. Returns [`Ok(())`] on success and -/// [`Err(ChorusLibError)`] on failure. -pub async fn handle_request_as_result( - request: RequestBuilder, - user: &mut UserMeta, - limit_type: LimitType, -) -> ChorusResult<()> { - match handle_request(request, user, limit_type).await { - Ok(_) => Ok(()), - Err(e) => Err(ChorusLibError::InvalidResponseError { - error: e.to_string(), - }), - } -} - -pub async fn deserialize_response Deserialize<'a>>( - request: RequestBuilder, - user: &mut UserMeta, - limit_type: LimitType, -) -> ChorusResult { - let response = handle_request(request, user, limit_type).await.unwrap(); - let response_text = match response.text().await { - Ok(string) => string, - Err(e) => { - return Err(ChorusLibError::InvalidResponseError { - error: format!( - "Error while trying to process the HTTP response into a String: {}", - e - ), - }); - } - }; - let object = match from_str::(&response_text) { - Ok(object) => object, - Err(e) => { - return Err(ChorusLibError::InvalidResponseError { - error: format!( - "Error while trying to deserialize the JSON response into T: {}", - e - ), - }) - } - }; - Ok(object) -} diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index ac00df1..3654698 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -2,15 +2,11 @@ use reqwest::Client; use serde_json::from_str; use serde_json::to_string; -use crate::api::deserialize_response; -use crate::api::handle_request; -use crate::api::handle_request_as_result; -use crate::api::limits::Limits; -use crate::errors::ChorusLibError; +use crate::api::LimitType; +use crate::errors::ChorusError; use crate::errors::ChorusResult; -use crate::instance::Instance; use crate::instance::UserMeta; -use crate::limit::LimitedRequester; +use crate::ratelimiter::ChorusRequest; use crate::types::Snowflake; use crate::types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema}; @@ -36,11 +32,14 @@ impl Guild { guild_create_schema: GuildCreateSchema, ) -> ChorusResult { let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api); - let request = reqwest::Client::new() - .post(url.clone()) - .bearer_auth(user.token.clone()) - .body(to_string(&guild_create_schema).unwrap()); - deserialize_response::(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()) + .body(to_string(&guild_create_schema).unwrap()), + limit_type: LimitType::Global, + }; + chorus_request.deserialize_response::(user).await } /// Deletes a guild. @@ -73,10 +72,13 @@ impl Guild { user.belongs_to.borrow().urls.api, guild_id ); - let request = reqwest::Client::new() - .post(url.clone()) - .bearer_auth(user.token.clone()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(user).await } /// Sends a request to create a new channel in the guild. @@ -97,14 +99,7 @@ impl Guild { user: &mut UserMeta, schema: ChannelCreateSchema, ) -> ChorusResult { - Channel::_create( - &user.token, - self.id, - schema, - &mut user.limits, - &mut user.belongs_to.borrow_mut(), - ) - .await + Channel::create(user, self.id, schema).await } /// Returns a `Result` containing a vector of `Channel` structs if the request was successful, or an `ChorusLibError` if there was an error. @@ -117,20 +112,21 @@ impl Guild { /// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits. /// pub async fn channels(&self, user: &mut UserMeta) -> ChorusResult> { - let request = Client::new() - .get(format!( - "{}/guilds/{}/channels/", - user.belongs_to.borrow().urls.api, - self.id - )) - .bearer_auth(user.token()); - let result = handle_request(request, user, crate::api::limits::LimitType::Channel) - .await - .unwrap(); + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/guilds/{}/channels/", + user.belongs_to.borrow().urls.api, + self.id + )) + .bearer_auth(user.token()), + limit_type: LimitType::Channel(self.id), + }; + let result = chorus_request.send_request(user).await?; let stringed_response = match result.text().await { Ok(value) => value, Err(e) => { - return Err(ChorusLibError::InvalidResponseError { + return Err(ChorusError::InvalidResponse { error: e.to_string(), }); } @@ -138,7 +134,7 @@ impl Guild { let _: Vec = match from_str(&stringed_response) { Ok(result) => return Ok(result), Err(e) => { - return Err(ChorusLibError::InvalidResponseError { + return Err(ChorusError::InvalidResponse { error: e.to_string(), }); } @@ -155,35 +151,19 @@ impl Guild { /// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits. /// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits. /// - pub async fn get(user: &mut UserMeta, guild_id: Snowflake) -> ChorusResult { - let mut belongs_to = user.belongs_to.borrow_mut(); - Guild::_get(guild_id, &user.token, &mut user.limits, &mut belongs_to).await - } - - /// For internal use. Does the same as the public get method, but does not require a second, mutable - /// borrow of `UserMeta::belongs_to`, when used in conjunction with other methods, which borrow `UserMeta::belongs_to`. - async fn _get( - guild_id: Snowflake, - token: &str, - limits_user: &mut Limits, - instance: &mut Instance, - ) -> ChorusResult { - let request = Client::new() - .get(format!("{}/guilds/{}/", instance.urls.api, guild_id)) - .bearer_auth(token); - let response = match LimitedRequester::send_request( - request, - crate::api::limits::LimitType::Guild, - instance, - limits_user, - ) - .await - { - Ok(response) => response, - Err(e) => return Err(e), + pub async fn get(guild_id: Snowflake, user: &mut UserMeta) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/guilds/{}/", + user.belongs_to.borrow().urls.api, + guild_id + )) + .bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), }; - let guild: Guild = from_str(&response.text().await.unwrap()).unwrap(); - Ok(guild) + let response = chorus_request.deserialize_response::(user).await?; + Ok(response) } } @@ -207,48 +187,17 @@ impl Channel { guild_id: Snowflake, schema: ChannelCreateSchema, ) -> ChorusResult { - let mut belongs_to = user.belongs_to.borrow_mut(); - Channel::_create( - &user.token, - guild_id, - schema, - &mut user.limits, - &mut belongs_to, - ) - .await - } - - async fn _create( - token: &str, - guild_id: Snowflake, - schema: ChannelCreateSchema, - limits_user: &mut Limits, - instance: &mut Instance, - ) -> ChorusResult { - let request = Client::new() - .post(format!( - "{}/guilds/{}/channels/", - instance.urls.api, guild_id - )) - .bearer_auth(token) - .body(to_string(&schema).unwrap()); - let result = match LimitedRequester::send_request( - request, - crate::api::limits::LimitType::Guild, - instance, - limits_user, - ) - .await - { - Ok(result) => result, - Err(e) => return Err(e), + let chorus_request = ChorusRequest { + request: Client::new() + .post(format!( + "{}/guilds/{}/channels/", + user.belongs_to.borrow().urls.api, + guild_id + )) + .bearer_auth(user.token()) + .body(to_string(&schema).unwrap()), + limit_type: LimitType::Guild(guild_id), }; - match from_str::(&result.text().await.unwrap()) { - Ok(object) => Ok(object), - Err(e) => Err(ChorusLibError::RequestErrorError { - url: format!("{}/guilds/{}/channels/", instance.urls.api, guild_id), - error: e.to_string(), - }), - } + chorus_request.deserialize_response::(user).await } } diff --git a/src/api/guilds/member.rs b/src/api/guilds/member.rs index e110cfa..5fa99cd 100644 --- a/src/api/guilds/member.rs +++ b/src/api/guilds/member.rs @@ -1,9 +1,10 @@ use reqwest::Client; use crate::{ - api::{deserialize_response, handle_request_as_result}, + api::LimitType, errors::ChorusResult, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, Snowflake}, }; @@ -30,13 +31,13 @@ impl types::GuildMember { guild_id, member_id ); - let request = Client::new().get(url).bearer_auth(user.token()); - deserialize_response::( - request, - user, - crate::api::limits::LimitType::Guild, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) + .await } /// Adds a role to a guild member. @@ -64,8 +65,11 @@ impl types::GuildMember { member_id, role_id ); - let request = Client::new().put(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().put(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request.handle_request_as_result(user).await } /// Removes a role from a guild member. @@ -85,7 +89,7 @@ impl types::GuildMember { guild_id: Snowflake, member_id: Snowflake, role_id: Snowflake, - ) -> Result<(), crate::errors::ChorusLibError> { + ) -> Result<(), crate::errors::ChorusError> { let url = format!( "{}/guilds/{}/members/{}/roles/{}/", user.belongs_to.borrow().urls.api, @@ -93,7 +97,10 @@ impl types::GuildMember { member_id, role_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request.handle_request_as_result(user).await } } diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index 80dddad..1d1bc68 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -2,9 +2,10 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::deserialize_response, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, RoleCreateModifySchema, RoleObject, Snowflake}, }; @@ -32,14 +33,14 @@ impl types::RoleObject { user.belongs_to.borrow().urls.api, guild_id ); - let request = Client::new().get(url).bearer_auth(user.token()); - let roles = deserialize_response::>( - request, - user, - crate::api::limits::LimitType::Guild, - ) - .await - .unwrap(); + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + let roles = chorus_request + .deserialize_response::>(user) + .await + .unwrap(); if roles.is_empty() { return Ok(None); } @@ -72,8 +73,13 @@ impl types::RoleObject { guild_id, role_id ); - let request = Client::new().get(url).bearer_auth(user.token()); - deserialize_response(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) + .await } /// Creates a new role for a given guild. @@ -102,12 +108,17 @@ impl types::RoleObject { guild_id ); let body = to_string::(&role_create_schema).map_err(|e| { - ChorusLibError::FormCreationError { + ChorusError::FormCreation { error: e.to_string(), } })?; - let request = Client::new().post(url).bearer_auth(user.token()).body(body); - deserialize_response(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().post(url).bearer_auth(user.token()).body(body), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) + .await } /// Updates the position of a role in the guild's hierarchy. @@ -135,16 +146,19 @@ impl types::RoleObject { user.belongs_to.borrow().urls.api, guild_id ); - let body = to_string(&role_position_update_schema).map_err(|e| { - ChorusLibError::FormCreationError { + let body = + to_string(&role_position_update_schema).map_err(|e| ChorusError::FormCreation { error: e.to_string(), - } - })?; - let request = Client::new() - .patch(url) - .bearer_auth(user.token()) - .body(body); - deserialize_response::(request, user, crate::api::limits::LimitType::Guild) + })?; + let chorus_request = ChorusRequest { + request: Client::new() + .patch(url) + .bearer_auth(user.token()) + .body(body), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) .await } @@ -177,15 +191,19 @@ impl types::RoleObject { role_id ); let body = to_string::(&role_create_schema).map_err(|e| { - ChorusLibError::FormCreationError { + ChorusError::FormCreation { error: e.to_string(), } })?; - let request = Client::new() - .patch(url) - .bearer_auth(user.token()) - .body(body); - deserialize_response::(request, user, crate::api::limits::LimitType::Guild) + let chorus_request = ChorusRequest { + request: Client::new() + .patch(url) + .bearer_auth(user.token()) + .body(body), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) .await } } diff --git a/src/api/invites/mod.rs b/src/api/invites/mod.rs new file mode 100644 index 0000000..b766a5b --- /dev/null +++ b/src/api/invites/mod.rs @@ -0,0 +1,73 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::errors::ChorusResult; +use crate::instance::UserMeta; +use crate::ratelimiter::ChorusRequest; +use crate::types::{CreateChannelInviteSchema, GuildInvite, Invite, Snowflake}; + +impl UserMeta { + /// # Arguments + /// - invite_code: The invite code to accept the invite for. + /// - session_id: The session ID that is accepting the invite, required for guest invites. + /// + /// # Reference: + /// Read + pub async fn accept_invite( + &mut self, + invite_code: &str, + session_id: Option<&str>, + ) -> ChorusResult { + let mut request = ChorusRequest { + request: Client::new() + .post(format!( + "{}/invites/{}/", + self.belongs_to.borrow().urls.api, + invite_code + )) + .bearer_auth(self.token()), + limit_type: super::LimitType::Global, + }; + if session_id.is_some() { + request.request = request + .request + .body(to_string(session_id.unwrap()).unwrap()); + } + request.deserialize_response::(self).await + } + /// Note: Spacebar does not yet implement this endpoint. + pub async fn create_user_invite(&mut self, code: Option<&str>) -> ChorusResult { + ChorusRequest { + request: Client::new() + .post(format!( + "{}/users/@me/invites/", + self.belongs_to.borrow().urls.api + )) + .body(to_string(&code).unwrap()) + .bearer_auth(self.token()), + limit_type: super::LimitType::Global, + } + .deserialize_response::(self) + .await + } + + pub async fn create_guild_invite( + &mut self, + create_channel_invite_schema: CreateChannelInviteSchema, + channel_id: Snowflake, + ) -> ChorusResult { + ChorusRequest { + request: Client::new() + .post(format!( + "{}/channels/{}/invites/", + self.belongs_to.borrow().urls.api, + channel_id + )) + .bearer_auth(self.token()) + .body(to_string(&create_channel_invite_schema).unwrap()), + limit_type: super::LimitType::Channel(channel_id), + } + .deserialize_response::(self) + .await + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 56abb5f..9fd954d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,12 +1,13 @@ pub use channels::messages::*; -pub use common::*; pub use guilds::*; +pub use invites::*; pub use policies::instance::instance::*; -pub use policies::instance::limits::*; +pub use policies::instance::ratelimits::*; +pub use users::*; pub mod auth; pub mod channels; -pub mod common; pub mod guilds; +pub mod invites; pub mod policies; pub mod users; diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index 0ea3699..75f832c 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -1,7 +1,6 @@ -use reqwest::Client; use serde_json::from_str; -use crate::errors::{ChorusLibError, ChorusResult}; +use crate::errors::{ChorusError, ChorusResult}; use crate::instance::Instance; use crate::types::GeneralConfiguration; @@ -10,21 +9,21 @@ impl Instance { /// # Errors /// [`ChorusLibError`] - If the request fails. pub async fn general_configuration_schema(&self) -> ChorusResult { - let client = Client::new(); let endpoint_url = self.urls.api.clone() + "/policies/instance/"; - let request = match client.get(&endpoint_url).send().await { + let request = match self.client.get(&endpoint_url).send().await { Ok(result) => result, Err(e) => { - return Err(ChorusLibError::RequestErrorError { + return Err(ChorusError::RequestFailed { url: endpoint_url, - error: e.to_string(), + error: e, }); } }; if !request.status().as_str().starts_with('2') { - return Err(ChorusLibError::ReceivedErrorCodeError { - error_code: request.status().to_string(), + return Err(ChorusError::ReceivedErrorCode { + error_code: request.status().as_u16(), + error: request.text().await.unwrap(), }); } diff --git a/src/api/policies/instance/limits.rs b/src/api/policies/instance/limits.rs deleted file mode 100644 index 3c06d29..0000000 --- a/src/api/policies/instance/limits.rs +++ /dev/null @@ -1,499 +0,0 @@ -pub mod limits { - use std::collections::HashMap; - - use reqwest::Client; - use serde::{Deserialize, Serialize}; - use serde_json::from_str; - - #[derive(Clone, Copy, Eq, Hash, PartialEq, Debug, Default)] - pub enum LimitType { - AuthRegister, - AuthLogin, - AbsoluteMessage, - AbsoluteRegister, - #[default] - Global, - Ip, - Channel, - Error, - Guild, - Webhook, - } - - impl ToString for LimitType { - fn to_string(&self) -> String { - match self { - LimitType::AuthRegister => "AuthRegister".to_string(), - LimitType::AuthLogin => "AuthLogin".to_string(), - LimitType::AbsoluteMessage => "AbsoluteMessage".to_string(), - LimitType::AbsoluteRegister => "AbsoluteRegister".to_string(), - LimitType::Global => "Global".to_string(), - LimitType::Ip => "Ip".to_string(), - LimitType::Channel => "Channel".to_string(), - LimitType::Error => "Error".to_string(), - LimitType::Guild => "Guild".to_string(), - LimitType::Webhook => "Webhook".to_string(), - } - } - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct User { - pub maxGuilds: u64, - pub maxUsername: u64, - pub maxFriends: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Guild { - pub maxRoles: u64, - pub maxEmojis: u64, - pub maxMembers: u64, - pub maxChannels: u64, - pub maxChannelsInCategory: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Message { - pub maxCharacters: u64, - pub maxTTSCharacters: u64, - pub maxReactions: u64, - pub maxAttachmentSize: u64, - pub maxBulkDelete: u64, - pub maxEmbedDownloadSize: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Channel { - pub maxPins: u64, - pub maxTopic: u64, - pub maxWebhooks: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct Rate { - pub enabled: bool, - pub ip: Window, - pub global: Window, - pub error: Window, - pub routes: Routes, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct Window { - pub count: u64, - pub window: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct Routes { - pub guild: Window, - pub webhook: Window, - pub channel: Window, - pub auth: AuthRoutes, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct AuthRoutes { - pub login: Window, - pub register: Window, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct AbsoluteRate { - pub register: AbsoluteWindow, - pub sendMessage: AbsoluteWindow, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct AbsoluteWindow { - pub limit: u64, - pub window: u64, - pub enabled: bool, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Config { - pub user: User, - pub guild: Guild, - pub message: Message, - pub channel: Channel, - pub rate: Rate, - pub absoluteRate: AbsoluteRate, - } - - #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] - pub struct Limit { - pub bucket: LimitType, - pub limit: u64, - pub remaining: u64, - pub reset: u64, - } - - impl std::fmt::Display for Limit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Bucket: {:?}, Limit: {}, Remaining: {}, Reset: {}", - self.bucket, self.limit, self.remaining, self.reset - ) - } - } - - impl Limit { - pub fn add_remaining(&mut self, remaining: i64) { - if remaining < 0 { - if (self.remaining as i64 + remaining) <= 0 { - self.remaining = 0; - return; - } - self.remaining -= remaining.unsigned_abs(); - return; - } - self.remaining += remaining.unsigned_abs(); - } - } - - pub struct LimitsMutRef<'a> { - pub limit_absolute_messages: &'a mut Limit, - pub limit_absolute_register: &'a mut Limit, - pub limit_auth_login: &'a mut Limit, - pub limit_auth_register: &'a mut Limit, - pub limit_ip: &'a mut Limit, - pub limit_global: &'a mut Limit, - pub limit_error: &'a mut Limit, - pub limit_guild: &'a mut Limit, - pub limit_webhook: &'a mut Limit, - pub limit_channel: &'a mut Limit, - } - - impl LimitsMutRef<'_> { - pub fn combine_mut_ref<'a>( - instance_rate_limits: &'a mut Limits, - user_rate_limits: &'a mut Limits, - ) -> LimitsMutRef<'a> { - LimitsMutRef { - limit_absolute_messages: &mut instance_rate_limits.limit_absolute_messages, - limit_absolute_register: &mut instance_rate_limits.limit_absolute_register, - limit_auth_login: &mut instance_rate_limits.limit_auth_login, - limit_auth_register: &mut instance_rate_limits.limit_auth_register, - limit_channel: &mut user_rate_limits.limit_channel, - limit_error: &mut user_rate_limits.limit_error, - limit_global: &mut instance_rate_limits.limit_global, - limit_guild: &mut user_rate_limits.limit_guild, - limit_ip: &mut instance_rate_limits.limit_ip, - limit_webhook: &mut user_rate_limits.limit_webhook, - } - } - - pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit { - match limit_type { - LimitType::AbsoluteMessage => self.limit_absolute_messages, - LimitType::AbsoluteRegister => self.limit_absolute_register, - LimitType::AuthLogin => self.limit_auth_login, - LimitType::AuthRegister => self.limit_auth_register, - LimitType::Channel => self.limit_channel, - LimitType::Error => self.limit_error, - LimitType::Global => self.limit_global, - LimitType::Guild => self.limit_guild, - LimitType::Ip => self.limit_ip, - LimitType::Webhook => self.limit_webhook, - } - } - - pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit { - match limit_type { - LimitType::AbsoluteMessage => self.limit_absolute_messages, - LimitType::AbsoluteRegister => self.limit_absolute_register, - LimitType::AuthLogin => self.limit_auth_login, - LimitType::AuthRegister => self.limit_auth_register, - LimitType::Channel => self.limit_channel, - LimitType::Error => self.limit_error, - LimitType::Global => self.limit_global, - LimitType::Guild => self.limit_guild, - LimitType::Ip => self.limit_ip, - LimitType::Webhook => self.limit_webhook, - } - } - } - - #[derive(Debug, Clone, Default)] - pub struct Limits { - pub limit_absolute_messages: Limit, - pub limit_absolute_register: Limit, - pub limit_auth_login: Limit, - pub limit_auth_register: Limit, - pub limit_ip: Limit, - pub limit_global: Limit, - pub limit_error: Limit, - pub limit_guild: Limit, - pub limit_webhook: Limit, - pub limit_channel: Limit, - } - - impl Limits { - pub fn combine(instance_rate_limits: &Limits, user_rate_limits: &Limits) -> Limits { - Limits { - limit_absolute_messages: instance_rate_limits.limit_absolute_messages, - limit_absolute_register: instance_rate_limits.limit_absolute_register, - limit_auth_login: instance_rate_limits.limit_auth_login, - limit_auth_register: instance_rate_limits.limit_auth_register, - limit_channel: user_rate_limits.limit_channel, - limit_error: user_rate_limits.limit_error, - limit_global: instance_rate_limits.limit_global, - limit_guild: user_rate_limits.limit_guild, - limit_ip: instance_rate_limits.limit_ip, - limit_webhook: user_rate_limits.limit_webhook, - } - } - - pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit { - match limit_type { - LimitType::AbsoluteMessage => &self.limit_absolute_messages, - LimitType::AbsoluteRegister => &self.limit_absolute_register, - LimitType::AuthLogin => &self.limit_auth_login, - LimitType::AuthRegister => &self.limit_auth_register, - LimitType::Channel => &self.limit_channel, - LimitType::Error => &self.limit_error, - LimitType::Global => &self.limit_global, - LimitType::Guild => &self.limit_guild, - LimitType::Ip => &self.limit_ip, - LimitType::Webhook => &self.limit_webhook, - } - } - - pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit { - match limit_type { - LimitType::AbsoluteMessage => &mut self.limit_absolute_messages, - LimitType::AbsoluteRegister => &mut self.limit_absolute_register, - LimitType::AuthLogin => &mut self.limit_auth_login, - LimitType::AuthRegister => &mut self.limit_auth_register, - LimitType::Channel => &mut self.limit_channel, - LimitType::Error => &mut self.limit_error, - LimitType::Global => &mut self.limit_global, - LimitType::Guild => &mut self.limit_guild, - LimitType::Ip => &mut self.limit_ip, - LimitType::Webhook => &mut self.limit_webhook, - } - } - - pub fn to_hash_map(&self) -> HashMap { - let mut map: HashMap = HashMap::new(); - map.insert(LimitType::AbsoluteMessage, self.limit_absolute_messages); - map.insert(LimitType::AbsoluteRegister, self.limit_absolute_register); - map.insert(LimitType::AuthLogin, self.limit_auth_login); - map.insert(LimitType::AuthRegister, self.limit_auth_register); - map.insert(LimitType::Ip, self.limit_ip); - map.insert(LimitType::Global, self.limit_global); - map.insert(LimitType::Error, self.limit_error); - map.insert(LimitType::Guild, self.limit_guild); - map.insert(LimitType::Webhook, self.limit_webhook); - map.insert(LimitType::Channel, self.limit_channel); - map - } - - pub fn get_as_mut(&mut self) -> &mut Limits { - self - } - - /// check_limits uses the API to get the current request limits of the instance. - /// It returns a `Limits` struct containing all the limits. - /// If the rate limit is disabled, then the limit is set to `u64::MAX`. - /// # Errors - /// This function will panic if the request fails or if the response body cannot be parsed. - /// TODO: Change this to return a Result and handle the errors properly. - pub async fn check_limits(api_url: String) -> Limits { - let client = Client::new(); - let url_parsed = crate::UrlBundle::parse_url(api_url) + "/policies/instance/limits"; - let result = client - .get(url_parsed) - .send() - .await - .unwrap_or_else(|e| panic!("An error occured while performing the request: {}", e)) - .text() - .await - .unwrap_or_else(|e| { - panic!( - "An error occured while parsing the request body string: {}", - e - ) - }); - let config: Config = from_str(&result).unwrap(); - // If config.rate.enabled is false, then add return a Limits struct with all limits set to u64::MAX - let mut limits: Limits; - if !config.rate.enabled { - limits = Limits { - limit_absolute_messages: Limit { - bucket: LimitType::AbsoluteMessage, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_absolute_register: Limit { - bucket: LimitType::AbsoluteRegister, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_auth_login: Limit { - bucket: LimitType::AuthLogin, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_auth_register: Limit { - bucket: LimitType::AuthRegister, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_ip: Limit { - bucket: LimitType::Ip, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_global: Limit { - bucket: LimitType::Global, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_error: Limit { - bucket: LimitType::Error, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_guild: Limit { - bucket: LimitType::Guild, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_webhook: Limit { - bucket: LimitType::Webhook, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_channel: Limit { - bucket: LimitType::Channel, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - }; - } else { - limits = Limits { - limit_absolute_messages: Limit { - bucket: LimitType::AbsoluteMessage, - limit: config.absoluteRate.sendMessage.limit, - remaining: config.absoluteRate.sendMessage.limit, - reset: config.absoluteRate.sendMessage.window, - }, - limit_absolute_register: Limit { - bucket: LimitType::AbsoluteRegister, - limit: config.absoluteRate.register.limit, - remaining: config.absoluteRate.register.limit, - reset: config.absoluteRate.register.window, - }, - limit_auth_login: Limit { - bucket: LimitType::AuthLogin, - limit: config.rate.routes.auth.login.count, - remaining: config.rate.routes.auth.login.count, - reset: config.rate.routes.auth.login.window, - }, - limit_auth_register: Limit { - bucket: LimitType::AuthRegister, - limit: config.rate.routes.auth.register.count, - remaining: config.rate.routes.auth.register.count, - reset: config.rate.routes.auth.register.window, - }, - limit_ip: Limit { - bucket: LimitType::Ip, - limit: config.rate.ip.count, - remaining: config.rate.ip.count, - reset: config.rate.ip.window, - }, - limit_global: Limit { - bucket: LimitType::Global, - limit: config.rate.global.count, - remaining: config.rate.global.count, - reset: config.rate.global.window, - }, - limit_error: Limit { - bucket: LimitType::Error, - limit: config.rate.error.count, - remaining: config.rate.error.count, - reset: config.rate.error.window, - }, - limit_guild: Limit { - bucket: LimitType::Guild, - limit: config.rate.routes.guild.count, - remaining: config.rate.routes.guild.count, - reset: config.rate.routes.guild.window, - }, - limit_webhook: Limit { - bucket: LimitType::Webhook, - limit: config.rate.routes.webhook.count, - remaining: config.rate.routes.webhook.count, - reset: config.rate.routes.webhook.window, - }, - limit_channel: Limit { - bucket: LimitType::Channel, - limit: config.rate.routes.channel.count, - remaining: config.rate.routes.channel.count, - reset: config.rate.routes.channel.window, - }, - }; - } - - if !config.absoluteRate.register.enabled { - limits.limit_absolute_register = Limit { - bucket: LimitType::AbsoluteRegister, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }; - } - - if !config.absoluteRate.sendMessage.enabled { - limits.limit_absolute_messages = Limit { - bucket: LimitType::AbsoluteMessage, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }; - } - - limits - } - } -} - -#[cfg(test)] -mod instance_limits { - use crate::api::limits::{Limit, LimitType}; - - #[test] - fn limit_below_zero() { - let mut limit = Limit { - bucket: LimitType::AbsoluteMessage, - limit: 0, - remaining: 1, - reset: 0, - }; - limit.add_remaining(-2); - assert_eq!(0_u64, limit.remaining); - limit.add_remaining(-2123123); - assert_eq!(0_u64, limit.remaining); - } -} diff --git a/src/api/policies/instance/mod.rs b/src/api/policies/instance/mod.rs index 7be9605..0a1f245 100644 --- a/src/api/policies/instance/mod.rs +++ b/src/api/policies/instance/mod.rs @@ -1,5 +1,5 @@ pub use instance::*; -pub use limits::*; +pub use ratelimits::*; pub mod instance; -pub mod limits; +pub mod ratelimits; diff --git a/src/api/policies/instance/ratelimits.rs b/src/api/policies/instance/ratelimits.rs new file mode 100644 index 0000000..e32a835 --- /dev/null +++ b/src/api/policies/instance/ratelimits.rs @@ -0,0 +1,37 @@ +use std::hash::Hash; + +use serde::{Deserialize, Serialize}; + +use crate::types::Snowflake; + +/// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants +/// for when the Snowflake is not yet known. +/// See for more information. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize)] +pub enum LimitType { + AuthRegister, + AuthLogin, + #[default] + Global, + Ip, + Channel(Snowflake), + ChannelBaseline, + Error, + Guild(Snowflake), + GuildBaseline, + Webhook(Snowflake), + WebhookBaseline, +} + +/// A struct that represents the current ratelimits, either instance-wide or user-wide. +/// Unlike [`RateLimits`], this struct shows the current ratelimits, not the rate limit +/// configuration for the instance. +/// See for more information. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Limit { + pub bucket: LimitType, + pub limit: u64, + pub remaining: u64, + pub reset: u64, + pub window: u64, +} diff --git a/src/api/policies/mod.rs b/src/api/policies/mod.rs index 3e25d8c..d0c29f1 100644 --- a/src/api/policies/mod.rs +++ b/src/api/policies/mod.rs @@ -1,3 +1,3 @@ -pub use instance::limits::*; +pub use instance::ratelimits::*; pub mod instance; diff --git a/src/api/users/channels.rs b/src/api/users/channels.rs new file mode 100644 index 0000000..806bd9f --- /dev/null +++ b/src/api/users/channels.rs @@ -0,0 +1,32 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::{ + api::LimitType, + errors::ChorusResult, + instance::UserMeta, + ratelimiter::ChorusRequest, + types::{Channel, PrivateChannelCreateSchema}, +}; + +impl UserMeta { + /// Creates a DM channel or group DM channel. + /// + /// # Reference: + /// Read + pub async fn create_private_channel( + &mut self, + create_private_channel_schema: PrivateChannelCreateSchema, + ) -> ChorusResult { + let url = format!("{}/users/@me/channels", self.belongs_to.borrow().urls.api); + ChorusRequest { + request: Client::new() + .post(url) + .bearer_auth(self.token()) + .body(to_string(&create_private_channel_schema).unwrap()), + limit_type: LimitType::Global, + } + .deserialize_response::(self) + .await + } +} diff --git a/src/api/users/guilds.rs b/src/api/users/guilds.rs new file mode 100644 index 0000000..735ed9e --- /dev/null +++ b/src/api/users/guilds.rs @@ -0,0 +1,30 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::errors::ChorusResult; +use crate::instance::UserMeta; +use crate::ratelimiter::ChorusRequest; +use crate::types::Snowflake; + +impl UserMeta { + /// # Arguments: + /// - lurking: Whether the user is lurking in the guild + /// + /// # Reference: + /// Read + pub async fn leave_guild(&mut self, guild_id: &Snowflake, lurking: bool) -> ChorusResult<()> { + ChorusRequest { + request: Client::new() + .delete(format!( + "{}/users/@me/guilds/{}/", + self.belongs_to.borrow().urls.api, + guild_id + )) + .bearer_auth(self.token()) + .body(to_string(&lurking).unwrap()), + limit_type: crate::api::LimitType::Guild(*guild_id), + } + .handle_request_as_result(self) + .await + } +} diff --git a/src/api/users/mod.rs b/src/api/users/mod.rs index 84ea0ed..ba789ba 100644 --- a/src/api/users/mod.rs +++ b/src/api/users/mod.rs @@ -1,5 +1,9 @@ +pub use channels::*; +pub use guilds::*; pub use relationships::*; pub use users::*; +pub mod channels; +pub mod guilds; pub mod relationships; pub mod users; diff --git a/src/api/users/relationships.rs b/src/api/users/relationships.rs index 36cabd2..39c75d8 100644 --- a/src/api/users/relationships.rs +++ b/src/api/users/relationships.rs @@ -2,9 +2,10 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::{deserialize_response, handle_request_as_result}, + api::LimitType, errors::ChorusResult, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, CreateUserRelationshipSchema, RelationshipType, Snowflake}, }; @@ -26,13 +27,13 @@ impl UserMeta { self.belongs_to.borrow().urls.api, user_id ); - let request = Client::new().get(url).bearer_auth(self.token()); - deserialize_response::>( - request, - self, - crate::api::limits::LimitType::Global, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request + .deserialize_response::>(self) + .await } /// Retrieves the authenticated user's relationships. @@ -44,13 +45,13 @@ impl UserMeta { "{}/users/@me/relationships/", self.belongs_to.borrow().urls.api ); - let request = Client::new().get(url).bearer_auth(self.token()); - deserialize_response::>( - request, - self, - crate::api::limits::LimitType::Global, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request + .deserialize_response::>(self) + .await } /// Sends a friend request to a user. @@ -70,8 +71,11 @@ impl UserMeta { self.belongs_to.borrow().urls.api ); let body = to_string(&schema).unwrap(); - let request = Client::new().post(url).bearer_auth(self.token()).body(body); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new().post(url).bearer_auth(self.token()).body(body), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } /// Modifies the relationship between the authenticated user and the specified user. @@ -96,10 +100,13 @@ impl UserMeta { let api_url = self.belongs_to.borrow().urls.api.clone(); match relationship_type { RelationshipType::None => { - let request = Client::new() - .delete(format!("{}/users/@me/relationships/{}/", api_url, user_id)) - .bearer_auth(self.token()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new() + .delete(format!("{}/users/@me/relationships/{}/", api_url, user_id)) + .bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } RelationshipType::Friends | RelationshipType::Incoming | RelationshipType::Outgoing => { let body = CreateUserRelationshipSchema { @@ -107,11 +114,14 @@ impl UserMeta { from_friend_suggestion: None, friend_token: None, }; - let request = Client::new() - .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) - .bearer_auth(self.token()) - .body(to_string(&body).unwrap()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new() + .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) + .bearer_auth(self.token()) + .body(to_string(&body).unwrap()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } RelationshipType::Blocked => { let body = CreateUserRelationshipSchema { @@ -119,11 +129,14 @@ impl UserMeta { from_friend_suggestion: None, friend_token: None, }; - let request = Client::new() - .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) - .bearer_auth(self.token()) - .body(to_string(&body).unwrap()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new() + .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) + .bearer_auth(self.token()) + .body(to_string(&body).unwrap()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } RelationshipType::Suggestion | RelationshipType::Implicit => Ok(()), } @@ -143,7 +156,10 @@ impl UserMeta { self.belongs_to.borrow().urls.api, user_id ); - let request = Client::new().delete(url).bearer_auth(self.token()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } } diff --git a/src/api/users/users.rs b/src/api/users/users.rs index cd777dc..af6d2ce 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -1,11 +1,13 @@ +use std::{cell::RefCell, rc::Rc}; + use reqwest::Client; use serde_json::to_string; use crate::{ - api::{deserialize_response, handle_request_as_result}, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::{Instance, UserMeta}, - limit::LimitedRequester, + ratelimiter::ChorusRequest, types::{User, UserModifySchema, UserSettings}, }; @@ -48,16 +50,20 @@ impl UserMeta { || modify_schema.email.is_some() || modify_schema.code.is_some() { - return Err(ChorusLibError::PasswordRequiredError); + return Err(ChorusError::PasswordRequired); } let request = Client::new() .patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api)) .body(to_string(&modify_schema).unwrap()) .bearer_auth(self.token()); - let user_updated = - deserialize_response::(request, self, crate::api::limits::LimitType::Ip) - .await - .unwrap(); + let chorus_request = ChorusRequest { + request, + limit_type: LimitType::default(), + }; + let user_updated = chorus_request + .deserialize_response::(self) + .await + .unwrap(); let _ = std::mem::replace(&mut self.object, user_updated.clone()); Ok(user_updated) } @@ -78,43 +84,28 @@ impl UserMeta { self.belongs_to.borrow().urls.api )) .bearer_auth(self.token()); - handle_request_as_result(request, &mut self, crate::api::limits::LimitType::Ip).await + let chorus_request = ChorusRequest { + request, + limit_type: LimitType::default(), + }; + chorus_request.handle_request_as_result(&mut self).await } } impl User { pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult { - let mut belongs_to = user.belongs_to.borrow_mut(); - User::_get( - &user.token(), - &format!("{}", belongs_to.urls.api), - &mut belongs_to, - id, - ) - .await - } - - async fn _get( - token: &str, - url_api: &str, - instance: &mut Instance, - id: Option<&String>, - ) -> ChorusResult { + let url_api = user.belongs_to.borrow().urls.api.clone(); let url = if id.is_none() { format!("{}/users/@me/", url_api) } else { format!("{}/users/{}", url_api, id.unwrap()) }; - let request = reqwest::Client::new().get(url).bearer_auth(token); - let mut cloned_limits = instance.limits.clone(); - match LimitedRequester::send_request( + let request = reqwest::Client::new().get(url).bearer_auth(user.token()); + let chorus_request = ChorusRequest { request, - crate::api::limits::LimitType::Ip, - instance, - &mut cloned_limits, - ) - .await - { + limit_type: LimitType::Global, + }; + match chorus_request.send_request(user).await { Ok(result) => { let result_text = result.text().await.unwrap(); Ok(serde_json::from_str::(&result_text).unwrap()) @@ -131,18 +122,21 @@ impl User { let request: reqwest::RequestBuilder = Client::new() .get(format!("{}/users/@me/settings/", url_api)) .bearer_auth(token); - let mut cloned_limits = instance.limits.clone(); - match LimitedRequester::send_request( + let mut user = + UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()).await; + let chorus_request = ChorusRequest { request, - crate::api::limits::LimitType::Ip, - instance, - &mut cloned_limits, - ) - .await - { + limit_type: LimitType::Global, + }; + let result = match chorus_request.send_request(&mut user).await { Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()), Err(e) => Err(e), + }; + if instance.limits_information.is_some() { + instance.limits_information.as_mut().unwrap().ratelimits = + user.belongs_to.borrow().clone_limits_if_some().unwrap(); } + result } } @@ -158,6 +152,12 @@ impl Instance { This function is a wrapper around [`User::get`]. */ pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult { - User::_get(&token, &self.urls.api.clone(), self, id).await + let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token).await; + let result = User::get(&mut user, id).await; + if self.limits_information.is_some() { + self.limits_information.as_mut().unwrap().ratelimits = + user.belongs_to.borrow().clone_limits_if_some().unwrap(); + } + result } } diff --git a/src/errors.rs b/src/errors.rs index 0a689f7..595b91f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,39 +1,50 @@ use custom_error::custom_error; +use reqwest::Error; custom_error! { #[derive(PartialEq, Eq)] - pub FieldFormatError - PasswordError = "Password must be between 1 and 72 characters.", - UsernameError = "Username must be between 2 and 32 characters.", - ConsentError = "Consent must be 'true' to register.", - EmailError = "The provided email address is in an invalid format.", + pub RegistrationError + Consent = "Consent must be 'true' to register.", } -pub type ChorusResult = std::result::Result; +pub type ChorusResult = std::result::Result; custom_error! { - #[derive(PartialEq, Eq)] - pub ChorusLibError + pub ChorusError + /// Server did not respond. NoResponse = "Did not receive a response from the Server.", - RequestErrorError{url:String, error:String} = "An error occured while trying to GET from {url}: {error}", - ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}", - CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", - InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", + /// Reqwest returned an Error instead of a Response object. + RequestFailed{url:String, error: Error} = "An error occured while trying to GET from {url}: {error}", + /// Response received, however, it was not of the successful responses type. Used when no other, special case applies. + ReceivedErrorCode{error_code: u16, error: String} = "Received the following error code while requesting from the route: {error_code}", + /// Used when there is likely something wrong with the instance, the request was directed to. + CantGetInformation{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", + /// The requests form body was malformed/invalid. + InvalidFormBody{error_type: String, error:String} = "The server responded with: {error_type}: {error}", + /// The request has not been processed by the server due to a relevant rate limit bucket being exhausted. RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}", - MultipartCreationError{error: String} = "Got an error whilst creating the form: {error}", - FormCreationError{error: String} = "Got an error whilst creating the form: {error}", + /// The multipart form could not be created. + MultipartCreation{error: String} = "Got an error whilst creating the form: {error}", + /// The regular form could not be created. + FormCreation{error: String} = "Got an error whilst creating the form: {error}", + /// The token is invalid. TokenExpired = "Token expired, invalid or not found.", + /// No permission NoPermission = "You do not have the permissions needed to perform this action.", + /// Resource not found NotFound{error: String} = "The provided resource hasn't been found: {error}", - PasswordRequiredError = "You need to provide your current password to authenticate for this action.", - InvalidResponseError{error: String} = "The response is malformed and cannot be processed. Error: {error}", - InvalidArgumentsError{error: String} = "Invalid arguments were provided. Error: {error}" + /// Used when you, for example, try to change your spacebar account password without providing your old password for verification. + PasswordRequired = "You need to provide your current password to authenticate for this action.", + /// Malformed or unexpected response. + InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}", + /// Invalid, insufficient or too many arguments provided. + InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}" } custom_error! { #[derive(PartialEq, Eq)] pub ObserverError - AlreadySubscribedError = "Each event can only be subscribed to once." + AlreadySubscribed = "Each event can only be subscribed to once." } custom_error! { @@ -45,27 +56,27 @@ custom_error! { #[derive(Clone, PartialEq, Eq)] pub GatewayError // Errors we have received from the gateway - UnknownError = "We're not sure what went wrong. Try reconnecting?", - UnknownOpcodeError = "You sent an invalid Gateway opcode or an invalid payload for an opcode", - DecodeError = "Gateway server couldn't decode payload", - NotAuthenticatedError = "You sent a payload prior to identifying", - AuthenticationFailedError = "The account token sent with your identify payload is invalid", - AlreadyAuthenticatedError = "You've already identified, no need to reauthenticate", - InvalidSequenceNumberError = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session", - RateLimitedError = "You are being rate limited!", - SessionTimedOutError = "Your session timed out. Reconnect and start a new one", - InvalidShardError = "You sent us an invalid shard when identifying", - ShardingRequiredError = "The session would have handled too many guilds - you are required to shard your connection in order to connect", - InvalidAPIVersionError = "You sent an invalid Gateway version", - InvalidIntentsError = "You sent an invalid intent", - DisallowedIntentsError = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for", + Unknown = "We're not sure what went wrong. Try reconnecting?", + UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode", + Decode = "Gateway server couldn't decode payload", + NotAuthenticated = "You sent a payload prior to identifying", + AuthenticationFailed = "The account token sent with your identify payload is invalid", + AlreadyAuthenticated = "You've already identified, no need to reauthenticate", + InvalidSequenceNumber = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session", + RateLimited = "You are being rate limited!", + SessionTimedOut = "Your session timed out. Reconnect and start a new one", + InvalidShard = "You sent us an invalid shard when identifying", + ShardingRequired = "The session would have handled too many guilds - you are required to shard your connection in order to connect", + InvalidAPIVersion = "You sent an invalid Gateway version", + InvalidIntents = "You sent an invalid intent", + DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for", // Errors when initiating a gateway connection - CannotConnectError{error: String} = "Cannot connect due to a tungstenite error: {error}", - NonHelloOnInitiateError{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", + CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", + NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", // Other misc errors - UnexpectedOpcodeReceivedError{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", + UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", } custom_error! { diff --git a/src/gateway.rs b/src/gateway.rs index badf845..8e60dd0 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -1,16 +1,23 @@ use crate::errors::GatewayError; use crate::gateway::events::Events; -use crate::types; -use crate::types::WebSocketEvent; +use crate::types::{self, Channel, ChannelUpdate, Snowflake}; +use crate::types::{UpdateMessage, WebSocketEvent}; +use async_trait::async_trait; +use std::any::Any; +use std::collections::HashMap; +use std::fmt::Debug; use std::sync::Arc; +use std::time::Duration; +use tokio::sync::watch; +use tokio::time::sleep_until; use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; use futures_util::SinkExt; use futures_util::StreamExt; +use log::{info, trace, warn}; use native_tls::TlsConnector; use tokio::net::TcpStream; -use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; use tokio::task; @@ -69,7 +76,7 @@ const GATEWAY_CALL_SYNC: u8 = 13; const GATEWAY_LAZY_REQUEST: u8 = 14; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -pub const HEARTBEAT_ACK_TIMEOUT: u128 = 2000; +pub(crate) const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; /// Represents a messsage received from the gateway. This will be either a [GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. @@ -94,25 +101,21 @@ impl GatewayMessage { let processed_content = content.to_lowercase().replace('.', ""); match processed_content.as_str() { - "unknown error" | "4000" => Some(GatewayError::UnknownError), - "unknown opcode" | "4001" => Some(GatewayError::UnknownOpcodeError), - "decode error" | "error while decoding payload" | "4002" => { - Some(GatewayError::DecodeError) - } - "not authenticated" | "4003" => Some(GatewayError::NotAuthenticatedError), - "authentication failed" | "4004" => Some(GatewayError::AuthenticationFailedError), - "already authenticated" | "4005" => Some(GatewayError::AlreadyAuthenticatedError), - "invalid seq" | "4007" => Some(GatewayError::InvalidSequenceNumberError), - "rate limited" | "4008" => Some(GatewayError::RateLimitedError), - "session timed out" | "4009" => Some(GatewayError::SessionTimedOutError), - "invalid shard" | "4010" => Some(GatewayError::InvalidShardError), - "sharding required" | "4011" => Some(GatewayError::ShardingRequiredError), - "invalid api version" | "4012" => Some(GatewayError::InvalidAPIVersionError), - "invalid intent(s)" | "invalid intent" | "4013" => { - Some(GatewayError::InvalidIntentsError) - } + "unknown error" | "4000" => Some(GatewayError::Unknown), + "unknown opcode" | "4001" => Some(GatewayError::UnknownOpcode), + "decode error" | "error while decoding payload" | "4002" => Some(GatewayError::Decode), + "not authenticated" | "4003" => Some(GatewayError::NotAuthenticated), + "authentication failed" | "4004" => Some(GatewayError::AuthenticationFailed), + "already authenticated" | "4005" => Some(GatewayError::AlreadyAuthenticated), + "invalid seq" | "4007" => Some(GatewayError::InvalidSequenceNumber), + "rate limited" | "4008" => Some(GatewayError::RateLimited), + "session timed out" | "4009" => Some(GatewayError::SessionTimedOut), + "invalid shard" | "4010" => Some(GatewayError::InvalidShard), + "sharding required" | "4011" => Some(GatewayError::ShardingRequired), + "invalid api version" | "4012" => Some(GatewayError::InvalidAPIVersion), + "invalid intent(s)" | "invalid intent" | "4013" => Some(GatewayError::InvalidIntents), "disallowed intent(s)" | "disallowed intents" | "4014" => { - Some(GatewayError::DisallowedIntentsError) + Some(GatewayError::DisallowedIntents) } _ => None, } @@ -164,6 +167,12 @@ pub struct GatewayHandle { pub handle: JoinHandle<()>, /// Tells gateway tasks to close kill_send: tokio::sync::broadcast::Sender<()>, + store: Arc>>>, +} + +/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake. +pub trait Updateable: 'static + Send + Sync { + fn id(&self) -> Snowflake; } impl GatewayHandle { @@ -187,11 +196,32 @@ impl GatewayHandle { .unwrap(); } + pub async fn observe(&self, object: T) -> watch::Receiver { + let mut store = self.store.lock().await; + if let Some(channel) = store.get(&object.id()) { + let (_, rx) = channel + .downcast_ref::<(watch::Sender, watch::Receiver)>() + .unwrap_or_else(|| { + panic!( + "Snowflake {} already exists in the store, but it is not of type T.", + object.id() + ) + }); + rx.clone() + } else { + let id = object.id(); + let channel = watch::channel(object); + let receiver = channel.1.clone(); + store.insert(id, Box::new(channel)); + receiver + } + } + /// Sends an identify event to the gateway pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Identify.."); + trace!("GW: Sending Identify.."); self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; } @@ -200,7 +230,7 @@ impl GatewayHandle { pub async fn send_resume(&self, to_send: types::GatewayResume) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Resume.."); + trace!("GW: Sending Resume.."); self.send_json_event(GATEWAY_RESUME, to_send_value).await; } @@ -209,7 +239,7 @@ impl GatewayHandle { pub async fn send_update_presence(&self, to_send: types::UpdatePresence) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Update Presence.."); + trace!("GW: Sending Update Presence.."); self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) .await; @@ -219,7 +249,7 @@ impl GatewayHandle { pub async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Request Guild Members.."); + trace!("GW: Sending Request Guild Members.."); self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) .await; @@ -229,7 +259,7 @@ impl GatewayHandle { pub async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Update Voice State.."); + trace!("GW: Sending Update Voice State.."); self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) .await; @@ -239,7 +269,7 @@ impl GatewayHandle { pub async fn send_call_sync(&self, to_send: types::CallSync) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Call Sync.."); + trace!("GW: Sending Call Sync.."); self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; } @@ -248,7 +278,7 @@ impl GatewayHandle { pub async fn send_lazy_request(&self, to_send: types::LazyRequest) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Lazy Request.."); + trace!("GW: Sending Lazy Request.."); self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) .await; @@ -264,9 +294,9 @@ impl GatewayHandle { } pub struct Gateway { - pub events: Arc>, + events: Arc>, heartbeat_handler: HeartbeatHandler, - pub websocket_send: Arc< + websocket_send: Arc< Mutex< SplitSink< WebSocketStream>, @@ -274,8 +304,9 @@ pub struct Gateway { >, >, >, - pub websocket_receive: SplitStream>>, + websocket_receive: SplitStream>>, kill_send: tokio::sync::broadcast::Sender<()>, + store: Arc>>>, } impl Gateway { @@ -293,7 +324,7 @@ impl Gateway { { Ok(websocket_stream) => websocket_stream, Err(e) => { - return Err(GatewayError::CannotConnectError { + return Err(GatewayError::CannotConnect { error: e.to_string(), }) } @@ -313,12 +344,12 @@ impl Gateway { serde_json::from_str(msg.to_text().unwrap()).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { - return Err(GatewayError::NonHelloOnInitiateError { + return Err(GatewayError::NonHelloOnInitiate { opcode: gateway_payload.op_code, }); } - println!("GW: Received Hello"); + info!("GW: Received Hello"); let gateway_hello: types::HelloData = serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); @@ -326,16 +357,19 @@ impl Gateway { let events = Events::default(); let shared_events = Arc::new(Mutex::new(events)); + let store = Arc::new(Mutex::new(HashMap::new())); + let mut gateway = Gateway { events: shared_events.clone(), heartbeat_handler: HeartbeatHandler::new( - gateway_hello.heartbeat_interval, + Duration::from_millis(gateway_hello.heartbeat_interval), shared_websocket_send.clone(), kill_send.subscribe(), ), websocket_send: shared_websocket_send.clone(), websocket_receive, kill_send: kill_send.clone(), + store: store.clone(), }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello @@ -349,6 +383,7 @@ impl Gateway { websocket_send: shared_websocket_send.clone(), handle, kill_send: kill_send.clone(), + store, }) } @@ -367,7 +402,7 @@ impl Gateway { } // We couldn't receive the next message or it was an error, something is wrong with the websocket, close - println!("GW: Websocket is broken, stopping gateway"); + warn!("GW: Websocket is broken, stopping gateway"); break; } } @@ -380,6 +415,7 @@ impl Gateway { /// Deserializes and updates a dispatched event, when we already know its type; /// (Called for every event in handle_message) + #[allow(dead_code)] // TODO: Remove this allow annotation async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( data: &'a str, event: &mut GatewayEvent, @@ -401,21 +437,19 @@ impl Gateway { } if !msg.is_error() && !msg.is_payload() { - println!( + warn!( "Message unrecognised: {:?}, please open an issue on the chorus github", msg.message.to_string() ); return; } - // To:do: handle errors in a good way, maybe observers like events? + // Todo: handle errors in a good way, maybe observers like events? if msg.is_error() { - println!("GW: Received error, connection will close.."); + warn!("GW: Received error, connection will close.."); let _error = msg.error(); - {} - self.close().await; return; } @@ -426,1026 +460,140 @@ impl Gateway { match gateway_payload.op_code { // An event was dispatched, we need to look at the gateway event name t GATEWAY_DISPATCH => { - let gateway_payload_t = gateway_payload.clone().event_name.unwrap(); + let Some(event_name) = gateway_payload.event_name else { + warn!("Gateway dispatch op without event_name"); + return + }; - println!("GW: Received {}..", gateway_payload_t); + trace!("Gateway: Received {event_name}"); - //println!("Event data dump: {}", gateway_payload.d.clone().unwrap().get()); + macro_rules! handle { + ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { + match event_name.as_str() { + $($name => { + let event = &mut self.events.lock().await.$($path).+; + match serde_json::from_str(gateway_payload.event_data.unwrap().get()) { + Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), + Ok(message) => { + $( + let message: $message_type = message; + if let Some(to_update) = self.store.lock().await.get(&message.id()) { + if let Some((tx, _)) = to_update.downcast_ref::<(watch::Sender<$update_type>, watch::Receiver<$update_type>)>() { + tx.send_modify(|object| message.update(object)); + } else { + warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id()) + } + } + )? + event.notify(message).await; + } + } + },)* + "RESUMED" => (), + "SESSIONS_REPLACE" => { + let result: Result, serde_json::Error> = + serde_json::from_str(gateway_payload.event_data.unwrap().get()); + match result { + Err(err) => { + warn!( + "Failed to parse gateway event {} ({})", + event_name, + err + ); + return; + } + Ok(sessions) => { + self.events.lock().await.session.replace.notify( + types::SessionsReplace {sessions} + ).await; + } + } + }, + _ => { + warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); + } + } + }; + } // See https://discord.com/developers/docs/topics/gateway-events#receive-events // "Some" of these are undocumented - match gateway_payload_t.as_str() { - "READY" => { - let event = &mut self.events.lock().await.session.ready; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "READY_SUPPLEMENTAL" => { - let event = &mut self.events.lock().await.session.ready_supplemental; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "RESUMED" => {} - "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => { - let event = &mut self - .events - .lock() - .await - .application - .command_permissions_update; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_RULE_CREATE" => { - let event = &mut self.events.lock().await.auto_moderation.rule_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_RULE_UPDATE" => { - let event = &mut self.events.lock().await.auto_moderation.rule_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_RULE_DELETE" => { - let event = &mut self.events.lock().await.auto_moderation.rule_delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_ACTION_EXECUTION" => { - let event = &mut self.events.lock().await.auto_moderation.action_execution; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_CREATE" => { - let event = &mut self.events.lock().await.channel.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_UPDATE" => { - let event = &mut self.events.lock().await.channel.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_UNREAD_UPDATE" => { - let event = &mut self.events.lock().await.channel.unread_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_DELETE" => { - let event = &mut self.events.lock().await.channel.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_PINS_UPDATE" => { - let event = &mut self.events.lock().await.channel.pins_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CALL_CREATE" => { - let event = &mut self.events.lock().await.call.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CALL_UPDATE" => { - let event = &mut self.events.lock().await.call.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CALL_DELETE" => { - let event = &mut self.events.lock().await.call.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_CREATE" => { - let event = &mut self.events.lock().await.thread.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_UPDATE" => { - let event = &mut self.events.lock().await.thread.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_DELETE" => { - let event = &mut self.events.lock().await.thread.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_LIST_SYNC" => { - let event = &mut self.events.lock().await.thread.list_sync; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_MEMBER_UPDATE" => { - let event = &mut self.events.lock().await.thread.member_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_MEMBERS_UPDATE" => { - let event = &mut self.events.lock().await.thread.members_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_CREATE" => { - let event = &mut self.events.lock().await.guild.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_UPDATE" => { - let event = &mut self.events.lock().await.guild.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_DELETE" => { - let event = &mut self.events.lock().await.guild.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_AUDIT_LOG_ENTRY_CREATE" => { - let event = &mut self.events.lock().await.guild.audit_log_entry_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_BAN_ADD" => { - let event = &mut self.events.lock().await.guild.ban_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_BAN_REMOVE" => { - let event = &mut self.events.lock().await.guild.ban_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_EMOJIS_UPDATE" => { - let event = &mut self.events.lock().await.guild.emojis_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_STICKERS_UPDATE" => { - let event = &mut self.events.lock().await.guild.stickers_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_INTEGRATIONS_UPDATE" => { - let event = &mut self.events.lock().await.guild.integrations_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBER_ADD" => { - let event = &mut self.events.lock().await.guild.member_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBER_REMOVE" => { - let event = &mut self.events.lock().await.guild.member_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBER_UPDATE" => { - let event = &mut self.events.lock().await.guild.member_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBERS_CHUNK" => { - let event = &mut self.events.lock().await.guild.members_chunk; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_ROLE_CREATE" => { - let event = &mut self.events.lock().await.guild.role_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_ROLE_UPDATE" => { - let event = &mut self.events.lock().await.guild.role_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_ROLE_DELETE" => { - let event = &mut self.events.lock().await.guild.role_delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_CREATE" => { - let event = &mut self.events.lock().await.guild.role_scheduled_event_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_UPDATE" => { - let event = &mut self.events.lock().await.guild.role_scheduled_event_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_DELETE" => { - let event = &mut self.events.lock().await.guild.role_scheduled_event_delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_USER_ADD" => { - let event = - &mut self.events.lock().await.guild.role_scheduled_event_user_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_USER_REMOVE" => { - let event = &mut self - .events - .lock() - .await - .guild - .role_scheduled_event_user_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "PASSIVE_UPDATE_V1" => { - let event = &mut self.events.lock().await.guild.passive_update_v1; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTEGRATION_CREATE" => { - let event = &mut self.events.lock().await.integration.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTEGRATION_UPDATE" => { - let event = &mut self.events.lock().await.integration.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTEGRATION_DELETE" => { - let event = &mut self.events.lock().await.integration.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTERACTION_CREATE" => { - let event = &mut self.events.lock().await.interaction.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INVITE_CREATE" => { - let event = &mut self.events.lock().await.invite.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INVITE_DELETE" => { - let event = &mut self.events.lock().await.invite.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_CREATE" => { - let event = &mut self.events.lock().await.message.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_UPDATE" => { - let event = &mut self.events.lock().await.message.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_DELETE" => { - let event = &mut self.events.lock().await.message.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_DELETE_BULK" => { - let event = &mut self.events.lock().await.message.delete_bulk; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_ADD" => { - let event = &mut self.events.lock().await.message.reaction_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_REMOVE" => { - let event = &mut self.events.lock().await.message.reaction_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_REMOVE_ALL" => { - let event = &mut self.events.lock().await.message.reaction_remove_all; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_REMOVE_EMOJI" => { - let event = &mut self.events.lock().await.message.reaction_remove_emoji; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_ACK" => { - let event = &mut self.events.lock().await.message.ack; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "PRESENCE_UPDATE" => { - let event = &mut self.events.lock().await.user.presence_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "RELATIONSHIP_ADD" => { - let event = &mut self.events.lock().await.relationship.add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "RELATIONSHIP_REMOVE" => { - let event = &mut self.events.lock().await.relationship.remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "STAGE_INSTANCE_CREATE" => { - let event = &mut self.events.lock().await.stage_instance.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "STAGE_INSTANCE_UPDATE" => { - let event = &mut self.events.lock().await.stage_instance.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "STAGE_INSTANCE_DELETE" => { - let event = &mut self.events.lock().await.stage_instance.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "SESSIONS_REPLACE" => { - let result: Result, serde_json::Error> = - serde_json::from_str(gateway_payload.event_data.unwrap().get()); - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - - let data = types::SessionsReplace { - sessions: result.unwrap(), - }; - - self.events.lock().await.session.replace.notify(data).await; - } - "USER_UPDATE" => { - let event = &mut self.events.lock().await.user.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "USER_GUILD_SETTINGS_UPDATE" => { - let event = &mut self.events.lock().await.user.guild_settings_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "VOICE_STATE_UPDATE" => { - let event = &mut self.events.lock().await.voice.state_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "VOICE_SERVER_UPDATE" => { - let event = &mut self.events.lock().await.voice.server_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "WEBHOOKS_UPDATE" => { - let event = &mut self.events.lock().await.webhooks.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - println!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - _ => { - println!("Received unrecognized gateway event ({})! Please open an issue on the chorus github so we can implement it", &gateway_payload_t); - } - } + handle!( + "READY" => session.ready, + "READY_SUPPLEMENTAL" => session.ready_supplemental, + "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, + "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, + "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update, + "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, + "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, + "CHANNEL_CREATE" => channel.create, + "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, + "CHANNEL_UNREAD_UPDATE" => channel.unread_update, + "CHANNEL_DELETE" => channel.delete, + "CHANNEL_PINS_UPDATE" => channel.pins_update, + "CALL_CREATE" => call.create, + "CALL_UPDATE" => call.update, + "CALL_DELETE" => call.delete, + "THREAD_CREATE" => thread.create, + "THREAD_UPDATE" => thread.update, + "THREAD_DELETE" => thread.delete, + "THREAD_LIST_SYNC" => thread.list_sync, + "THREAD_MEMBER_UPDATE" => thread.member_update, + "THREAD_MEMBERS_UPDATE" => thread.members_update, + "GUILD_CREATE" => guild.create, + "GUILD_UPDATE" => guild.update, + "GUILD_DELETE" => guild.delete, + "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, + "GUILD_BAN_ADD" => guild.ban_add, + "GUILD_BAN_REMOVE" => guild.ban_remove, + "GUILD_EMOJIS_UPDATE" => guild.emojis_update, + "GUILD_STICKERS_UPDATE" => guild.stickers_update, + "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, + "GUILD_MEMBER_ADD" => guild.member_add, + "GUILD_MEMBER_REMOVE" => guild.member_remove, + "GUILD_MEMBER_UPDATE" => guild.member_update, + "GUILD_MEMBERS_CHUNK" => guild.members_chunk, + "GUILD_ROLE_CREATE" => guild.role_create, + "GUILD_ROLE_UPDATE" => guild.role_update, + "GUILD_ROLE_DELETE" => guild.role_delete, + "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, + "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, + "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, + "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, + "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, + "PASSIVE_UPDATE_V1" => guild.passive_update_v1, + "INTEGRATION_CREATE" => integration.create, + "INTEGRATION_UPDATE" => integration.update, + "INTEGRATION_DELETE" => integration.delete, + "INTERACTION_CREATE" => interaction.create, + "INVITE_CREATE" => invite.create, + "INVITE_DELETE" => invite.delete, + "MESSAGE_CREATE" => message.create, + "MESSAGE_UPDATE" => message.update, + "MESSAGE_DELETE" => message.delete, + "MESSAGE_DELETE_BULK" => message.delete_bulk, + "MESSAGE_REACTION_ADD" => message.reaction_add, + "MESSAGE_REACTION_REMOVE" => message.reaction_remove, + "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, + "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, + "MESSAGE_ACK" => message.ack, + "PRESENCE_UPDATE" => user.presence_update, + "RELATIONSHIP_ADD" => relationship.add, + "RELATIONSHIP_REMOVE" => relationship.remove, + "STAGE_INSTANCE_CREATE" => stage_instance.create, + "STAGE_INSTANCE_UPDATE" => stage_instance.update, + "STAGE_INSTANCE_DELETE" => stage_instance.delete, + "USER_UPDATE" => user.update, + "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, + "VOICE_STATE_UPDATE" => voice.state_update, + "VOICE_SERVER_UPDATE" => voice.server_update, + "WEBHOOKS_UPDATE" => webhooks.update + ); } // We received a heartbeat from the server // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." GATEWAY_HEARTBEAT => { - println!("GW: Received Heartbeat // Heartbeat Request"); + trace!("GW: Received Heartbeat // Heartbeat Request"); // Tell the heartbeat handler it should send a heartbeat right away @@ -1469,10 +617,10 @@ impl Gateway { // Starts our heartbeat // We should have already handled this in gateway init GATEWAY_HELLO => { - panic!("Received hello when it was unexpected"); + warn!("Received hello when it was unexpected"); } GATEWAY_HEARTBEAT_ACK => { - println!("GW: Received Heartbeat ACK"); + trace!("GW: Received Heartbeat ACK"); // Tell the heartbeat handler we received an ack @@ -1494,20 +642,20 @@ impl Gateway { | GATEWAY_REQUEST_GUILD_MEMBERS | GATEWAY_CALL_SYNC | GATEWAY_LAZY_REQUEST => { - let error = GatewayError::UnexpectedOpcodeReceivedError { + let error = GatewayError::UnexpectedOpcodeReceived { opcode: gateway_payload.op_code, }; Err::<(), GatewayError>(error).unwrap(); } _ => { - println!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); + warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); } } // If we we received a seq number we should let it know - if gateway_payload.sequence_number.is_some() { + if let Some(seq_num) = gateway_payload.sequence_number { let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: Some(gateway_payload.sequence_number.unwrap()), + sequence_number: Some(seq_num), // Op code is irrelevant here op_code: None, }; @@ -1522,9 +670,10 @@ impl Gateway { } /// Handles sending heartbeats to the gateway in another thread +#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used struct HeartbeatHandler { - /// The heartbeat interval in milliseconds - pub heartbeat_interval: u128, + /// How ofter heartbeats need to be sent at a minimum + pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread pub send: Sender, /// The handle of the thread @@ -1533,7 +682,7 @@ struct HeartbeatHandler { impl HeartbeatHandler { pub fn new( - heartbeat_interval: u128, + heartbeat_interval: Duration, websocket_tx: Arc< Mutex< SplitSink< @@ -1577,7 +726,7 @@ impl HeartbeatHandler { >, >, >, - heartbeat_interval: u128, + heartbeat_interval: Duration, mut receive: tokio::sync::mpsc::Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, ) { @@ -1586,52 +735,48 @@ impl HeartbeatHandler { let mut last_seq_number: Option = None; loop { - let should_shutdown = kill_receive.try_recv().is_ok(); - if should_shutdown { + if kill_receive.try_recv().is_ok() { + trace!("GW: Closing heartbeat task"); break; } - let mut should_send; + let timeout = if last_heartbeat_acknowledged { + heartbeat_interval + } else { + // If the server hasn't acknowledged our heartbeat we should resend it + Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) + }; - let time_to_send = last_heartbeat_timestamp.elapsed().as_millis() >= heartbeat_interval; + let mut should_send = false; - should_send = time_to_send; - - let received_communication: Result = - receive.try_recv(); - if received_communication.is_ok() { - let communication = received_communication.unwrap(); - - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = Some(communication.sequence_number.unwrap()); + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; } + Some(communication) = receive.recv() => { + // If we received a seq number update, use that as the last seq number + if communication.sequence_number.is_some() { + last_seq_number = communication.sequence_number; + } - if communication.op_code.is_some() { - match communication.op_code.unwrap() { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; + if let Some(op_code) = communication.op_code { + match op_code { + GATEWAY_HEARTBEAT => { + // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately + should_send = true; + } + GATEWAY_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} } } } - // If the server hasn't acknowledged our heartbeat we should resend it - if !last_heartbeat_acknowledged - && last_heartbeat_timestamp.elapsed().as_millis() > HEARTBEAT_ACK_TIMEOUT - { - should_send = true; - println!("GW: Timed out waiting for a heartbeat ack, resending"); - } - if should_send { - println!("GW: Sending Heartbeat.."); + trace!("GW: Sending Heartbeat.."); let heartbeat = types::GatewayHeartbeat { op: GATEWAY_HEARTBEAT, @@ -1645,7 +790,7 @@ impl HeartbeatHandler { let send_result = websocket_tx.lock().await.send(msg).await; if send_result.is_err() { // We couldn't send, the websocket is broken - println!("GW: Couldnt send heartbeat, websocket seems broken"); + warn!("GW: Couldnt send heartbeat, websocket seems broken"); break; } @@ -1670,8 +815,9 @@ struct HeartbeatThreadCommunication { /// an Observable. The Observer is notified when the Observable's data changes. /// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent. /// Note that `Debug` is used to tell `Observer`s apart when unsubscribing. +#[async_trait] pub trait Observer: Sync + Send + std::fmt::Debug { - fn update(&self, data: &T); + async fn update(&self, data: &T); } /// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a @@ -1706,7 +852,7 @@ impl GatewayEvent { /// Notifies the observers of the GatewayEvent. pub async fn notify(&self, new_event_data: T) { for observer in &self.observers { - observer.update(&new_event_data); + observer.update(&new_event_data).await; } } } @@ -1879,12 +1025,13 @@ mod example { #[derive(Debug)] struct Consumer { - name: String, + _name: String, events_received: AtomicI32, } + #[async_trait] impl Observer for Consumer { - fn update(&self, _data: &types::GatewayResume) { + async fn update(&self, _data: &types::GatewayResume) { self.events_received.fetch_add(1, Relaxed); } } @@ -1900,13 +1047,13 @@ mod example { }; let consumer = Arc::new(Consumer { - name: "first".into(), + _name: "first".into(), events_received: 0.into(), }); event.subscribe(consumer.clone()); let second_consumer = Arc::new(Consumer { - name: "second".into(), + _name: "second".into(), events_received: 0.into(), }); event.subscribe(second_consumer.clone()); diff --git a/src/instance.rs b/src/instance.rs index 2477217..9795557 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,26 +1,37 @@ use std::cell::RefCell; +use std::collections::HashMap; use std::fmt; use std::rc::Rc; use reqwest::Client; use serde::{Deserialize, Serialize}; -use crate::api::limits::Limits; -use crate::errors::{ChorusLibError, ChorusResult, FieldFormatError}; +use crate::api::{Limit, LimitType}; +use crate::errors::ChorusResult; +use crate::gateway::{Gateway, GatewayHandle}; +use crate::ratelimiter::ChorusRequest; +use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, User, UserSettings}; use crate::UrlBundle; #[derive(Debug, Clone)] /** The [`Instance`] what you will be using to perform all sorts of actions on the Spacebar server. +If `limits_information` is `None`, then the instance will not be rate limited. */ pub struct Instance { pub urls: UrlBundle, pub instance_info: GeneralConfiguration, - pub limits: Limits, + pub limits_information: Option, pub client: Client, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LimitsInformation { + pub ratelimits: HashMap, + pub configuration: RateLimits, +} + impl Instance { /// Creates a new [`Instance`]. /// # Arguments @@ -28,24 +39,43 @@ impl Instance { /// * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server. /// # Errors /// * [`InstanceError`] - If the instance cannot be created. - pub async fn new(urls: UrlBundle) -> ChorusResult { + pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult { + let limits_information; + if limited { + let limits_configuration = + Some(ChorusRequest::get_limits_config(&urls.api).await?.rate); + let limits = Some(ChorusRequest::limits_config_to_hashmap( + limits_configuration.as_ref().unwrap(), + )); + limits_information = Some(LimitsInformation { + ratelimits: limits.unwrap(), + configuration: limits_configuration.unwrap(), + }); + } else { + limits_information = None; + } let mut instance = Instance { urls: urls.clone(), // Will be overwritten in the next step instance_info: GeneralConfiguration::default(), - limits: Limits::check_limits(urls.api).await, + limits_information, client: Client::new(), }; instance.instance_info = match instance.general_configuration_schema().await { Ok(schema) => schema, Err(e) => { - return Err(ChorusLibError::CantGetInfoError { - error: e.to_string(), - }); + log::warn!("Could not get instance configuration schema: {}", e); + GeneralConfiguration::default() } }; Ok(instance) } + pub(crate) fn clone_limits_if_some(&self) -> Option> { + if self.limits_information.is_some() { + return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); + } + None + } } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -59,32 +89,14 @@ impl fmt::Display for Token { } } -#[derive(Debug, PartialEq, Eq)] -pub struct Username { - pub username: String, -} - -impl Username { - /// Creates a new [`Username`]. - /// # Arguments - /// * `username` - The username that will be used to create the [`Username`]. - /// # Errors - /// * [`UsernameFormatError`] - If the username is not between 2 and 32 characters. - pub fn new(username: String) -> Result { - if username.len() < 2 || username.len() > 32 { - return Err(FieldFormatError::UsernameError); - } - Ok(Username { username }) - } -} - #[derive(Debug)] pub struct UserMeta { pub belongs_to: Rc>, pub token: String, - pub limits: Limits, + pub limits: Option>, pub settings: UserSettings, pub object: User, + pub gateway: GatewayHandle, } impl UserMeta { @@ -99,9 +111,10 @@ impl UserMeta { pub fn new( belongs_to: Rc>, token: String, - limits: Limits, + limits: Option>, settings: UserSettings, object: User, + gateway: GatewayHandle, ) -> UserMeta { UserMeta { belongs_to, @@ -109,6 +122,32 @@ impl UserMeta { limits, settings, object, + gateway, + } + } + + /// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have + /// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like + /// registering or logging in to the Instance, where you do not yet have a User object, but still + /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify + /// first. + pub(crate) async fn shell(instance: Rc>, token: String) -> UserMeta { + let settings = UserSettings::default(); + let object = User::default(); + let wss_url = instance.borrow().urls.wss.clone(); + // Dummy gateway object + let gateway = Gateway::new(wss_url).await.unwrap(); + UserMeta { + token, + belongs_to: instance.clone(), + limits: instance + .borrow() + .limits_information + .as_ref() + .map(|info| info.ratelimits.clone()), + settings, + object, + gateway, } } } diff --git a/src/lib.rs b/src/lib.rs index bfda613..77425b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ pub mod gateway; #[cfg(feature = "client")] pub mod instance; #[cfg(feature = "client")] -pub mod limit; +pub mod ratelimiter; pub mod types; #[cfg(feature = "client")] pub mod voice; diff --git a/src/limit.rs b/src/limit.rs deleted file mode 100644 index 4415e0c..0000000 --- a/src/limit.rs +++ /dev/null @@ -1,304 +0,0 @@ -use reqwest::{RequestBuilder, Response}; - -use crate::{ - api::limits::{Limit, LimitType, Limits, LimitsMutRef}, - errors::{ChorusLibError, ChorusResult}, - instance::Instance, -}; - -#[derive(Debug)] -pub struct LimitedRequester; - -impl LimitedRequester { - /// Checks if a request can be sent without hitting API rate limits and sends it, if true. - /// Will automatically update the rate limits of the LimitedRequester the request has been - /// sent with. - /// - /// # Arguments - /// - /// * `request`: A `RequestBuilder` that contains a request ready to be sent. Unfinished or - /// invalid requests will result in the method panicing. - /// * `limit_type`: Because this library does not yet implement a way to check for which rate - /// limit will be used when the request gets send, you will have to specify this manually using - /// a `LimitType` enum. - /// - /// # Returns - /// - /// * `Response`: The `Response` gotten from sending the request to the server. This will be - /// returned if the Request was built and send successfully. Is wrapped in an `Option`. - /// * `None`: `None` will be returned if the rate limit has been hit, and the request could - /// therefore not have been sent. - /// - /// # Errors - /// - /// This method will error if: - /// - /// * The request does not return a success status code (200-299) - /// * The supplied `RequestBuilder` contains invalid or incomplete information - /// * There has been an error with processing (unwrapping) the `Response` - /// * The call to `update_limits` yielded errors. Read the methods' Errors section for more - /// information. - pub async fn send_request( - request: RequestBuilder, - limit_type: LimitType, - instance: &mut Instance, - user_rate_limits: &mut Limits, - ) -> ChorusResult { - if LimitedRequester::can_send_request(limit_type, &instance.limits, user_rate_limits) { - let built_request = match request.build() { - Ok(request) => request, - Err(e) => { - return Err(ChorusLibError::RequestErrorError { - url: "".to_string(), - error: e.to_string(), - }); - } - }; - let result = instance.client.execute(built_request).await; - let response = match result { - Ok(is_response) => is_response, - Err(e) => { - return Err(ChorusLibError::ReceivedErrorCodeError { - error_code: e.to_string(), - }); - } - }; - LimitedRequester::update_limits( - &response, - limit_type, - &mut instance.limits, - user_rate_limits, - ); - if !response.status().is_success() { - match response.status().as_u16() { - 401 => Err(ChorusLibError::TokenExpired), - 403 => Err(ChorusLibError::TokenExpired), - _ => Err(ChorusLibError::ReceivedErrorCodeError { - error_code: response.status().as_str().to_string(), - }), - } - } else { - Ok(response) - } - } else { - Err(ChorusLibError::RateLimited { - bucket: limit_type.to_string(), - }) - } - } - - fn update_limit_entry(entry: &mut Limit, reset: u64, remaining: u64, limit: u64) { - if reset != entry.reset { - entry.reset = reset; - entry.remaining = limit; - entry.limit = limit; - } else { - entry.remaining = remaining; - entry.limit = limit; - } - } - - fn can_send_request( - limit_type: LimitType, - instance_rate_limits: &Limits, - user_rate_limits: &Limits, - ) -> bool { - // Check if all of the limits in this vec have at least one remaining request - - let rate_limits = Limits::combine(instance_rate_limits, user_rate_limits); - - let constant_limits: Vec<&LimitType> = [ - &LimitType::Error, - &LimitType::Global, - &LimitType::Ip, - &limit_type, - ] - .to_vec(); - for limit in constant_limits.iter() { - match rate_limits.to_hash_map().get(limit) { - Some(limit) => { - if limit.remaining == 0 { - return false; - } - // AbsoluteRegister and AuthRegister can cancel each other out. - if limit.bucket == LimitType::AbsoluteRegister - && rate_limits - .to_hash_map() - .get(&LimitType::AuthRegister) - .unwrap() - .remaining - == 0 - { - return false; - } - if limit.bucket == LimitType::AuthRegister - && rate_limits - .to_hash_map() - .get(&LimitType::AbsoluteRegister) - .unwrap() - .remaining - == 0 - { - return false; - } - } - None => return false, - } - } - true - } - - fn update_limits( - response: &Response, - limit_type: LimitType, - instance_rate_limits: &mut Limits, - user_rate_limits: &mut Limits, - ) { - let mut rate_limits = LimitsMutRef::combine_mut_ref(instance_rate_limits, user_rate_limits); - - let remaining = match response.headers().get("X-RateLimit-Remaining") { - Some(remaining) => remaining.to_str().unwrap().parse::().unwrap(), - None => rate_limits.get_limit_mut_ref(&limit_type).remaining - 1, - }; - let limit = match response.headers().get("X-RateLimit-Limit") { - Some(limit) => limit.to_str().unwrap().parse::().unwrap(), - None => rate_limits.get_limit_mut_ref(&limit_type).limit, - }; - let reset = match response.headers().get("X-RateLimit-Reset") { - Some(reset) => reset.to_str().unwrap().parse::().unwrap(), - None => rate_limits.get_limit_mut_ref(&limit_type).reset, - }; - - let status = response.status(); - let status_str = status.as_str(); - - if status_str.starts_with('4') { - rate_limits - .get_limit_mut_ref(&LimitType::Error) - .add_remaining(-1); - } - - rate_limits - .get_limit_mut_ref(&LimitType::Global) - .add_remaining(-1); - - rate_limits - .get_limit_mut_ref(&LimitType::Ip) - .add_remaining(-1); - - match limit_type { - LimitType::Error => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Error); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Global => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Global); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Ip => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Ip); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::AuthLogin => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AuthLogin); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::AbsoluteRegister => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AbsoluteRegister); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - // AbsoluteRegister and AuthRegister both need to be updated, if a Register event - // happens. - rate_limits - .get_limit_mut_ref(&LimitType::AuthRegister) - .remaining -= 1; - } - LimitType::AuthRegister => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AuthRegister); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - // AbsoluteRegister and AuthRegister both need to be updated, if a Register event - // happens. - rate_limits - .get_limit_mut_ref(&LimitType::AbsoluteRegister) - .remaining -= 1; - } - LimitType::AbsoluteMessage => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AbsoluteMessage); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Channel => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Channel); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Guild => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Guild); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Webhook => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Webhook); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - } - } -} - -#[cfg(test)] -mod rate_limit { - use serde_json::from_str; - - use crate::{api::limits::Config, UrlBundle}; - - use super::*; - - #[tokio::test] - async fn run_into_limit() { - let urls = UrlBundle::new( - String::from("http://localhost:3001/api/"), - String::from("wss://localhost:3001/"), - String::from("http://localhost:3001/cdn"), - ); - let mut request: Option> = None; - let mut instance = Instance::new(urls.clone()).await.unwrap(); - let mut user_rate_limits = Limits::check_limits(urls.api.clone()).await; - - for _ in 0..=50 { - let request_path = urls.api.clone() + "/some/random/nonexisting/path"; - let request_builder = instance.client.get(request_path); - request = Some( - LimitedRequester::send_request( - request_builder, - LimitType::Channel, - &mut instance, - &mut user_rate_limits, - ) - .await, - ); - } - assert!(matches!(request, Some(Err(_)))); - } - - #[tokio::test] - async fn test_send_request() { - let urls = UrlBundle::new( - String::from("http://localhost:3001/api/"), - String::from("wss://localhost:3001/"), - String::from("http://localhost:3001/cdn"), - ); - let mut instance = Instance::new(urls.clone()).await.unwrap(); - let mut user_rate_limits = Limits::check_limits(urls.api.clone()).await; - let _requester = LimitedRequester; - let request_path = urls.api.clone() + "/policies/instance/limits"; - let request_builder = instance.client.get(request_path); - let request = LimitedRequester::send_request( - request_builder, - LimitType::Channel, - &mut instance, - &mut user_rate_limits, - ) - .await; - let result = match request { - Ok(result) => result, - Err(_) => panic!("Request failed"), - }; - let _config: Config = from_str(result.text().await.unwrap().as_str()).unwrap(); - } -} diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs new file mode 100644 index 0000000..9227737 --- /dev/null +++ b/src/ratelimiter.rs @@ -0,0 +1,466 @@ +use std::collections::HashMap; + +use log::{self, debug}; +use reqwest::{Client, RequestBuilder, Response}; +use serde::Deserialize; +use serde_json::from_str; + +use crate::{ + api::{Limit, LimitType}, + errors::{ChorusError, ChorusResult}, + instance::UserMeta, + types::{types::subconfigs::limits::rates::RateLimits, LimitsConfiguration}, +}; + +/// Chorus' request struct. This struct is used to send rate-limited requests to the Spacebar server. +/// See for more information. +pub struct ChorusRequest { + pub request: RequestBuilder, + pub limit_type: LimitType, +} + +impl ChorusRequest { + /// Sends a [`ChorusRequest`]. Checks if the user is rate limited, and if not, sends the request. + /// If the user is not rate limited and the instance has rate limits enabled, it will update the + /// rate limits. + #[allow(clippy::await_holding_refcell_ref)] + pub(crate) async fn send_request(self, user: &mut UserMeta) -> ChorusResult { + if !ChorusRequest::can_send_request(user, &self.limit_type) { + log::info!("Rate limit hit. Bucket: {:?}", self.limit_type); + return Err(ChorusError::RateLimited { + bucket: format!("{:?}", self.limit_type), + }); + } + let belongs_to = user.belongs_to.borrow(); + let result = match belongs_to + .client + .execute(self.request.build().unwrap()) + .await + { + Ok(result) => { + debug!("Request successful: {:?}", result); + result + } + Err(error) => { + log::warn!("Request failed: {:?}", error); + return Err(ChorusError::RequestFailed { + url: error.url().unwrap().to_string(), + error, + }); + } + }; + drop(belongs_to); + if !result.status().is_success() { + if result.status().as_u16() == 429 { + log::warn!("Rate limit hit unexpectedly. Bucket: {:?}. Setting the instances' remaining global limit to 0 to have cooldown.", self.limit_type); + user.belongs_to + .borrow_mut() + .limits_information + .as_mut() + .unwrap() + .ratelimits + .get_mut(&LimitType::Global) + .unwrap() + .remaining = 0; + return Err(ChorusError::RateLimited { + bucket: format!("{:?}", self.limit_type), + }); + } + log::warn!("Request failed: {:?}", result); + return Err(ChorusRequest::interpret_error(result).await); + } + ChorusRequest::update_rate_limits(user, &self.limit_type, !result.status().is_success()); + Ok(result) + } + + fn can_send_request(user: &mut UserMeta, limit_type: &LimitType) -> bool { + log::trace!("Checking if user or instance is rate-limited..."); + let mut belongs_to = user.belongs_to.borrow_mut(); + if belongs_to.limits_information.is_none() { + log::trace!("Instance indicates no rate limits are configured. Continuing."); + return true; + } + let instance_dictated_limits = [ + &LimitType::AuthLogin, + &LimitType::AuthRegister, + &LimitType::Global, + &LimitType::Ip, + ]; + let limits = match instance_dictated_limits.contains(&limit_type) { + true => { + log::trace!( + "Limit type {:?} is dictated by the instance. Continuing.", + limit_type + ); + belongs_to + .limits_information + .as_mut() + .unwrap() + .ratelimits + .clone() + } + false => { + log::trace!( + "Limit type {:?} is dictated by the user. Continuing.", + limit_type + ); + ChorusRequest::ensure_limit_in_map( + &belongs_to + .limits_information + .as_ref() + .unwrap() + .configuration, + user.limits.as_mut().unwrap(), + limit_type, + ); + user.limits.as_mut().unwrap().clone() + } + }; + let global = belongs_to + .limits_information + .as_ref() + .unwrap() + .ratelimits + .get(&LimitType::Global) + .unwrap(); + let ip = belongs_to + .limits_information + .as_ref() + .unwrap() + .ratelimits + .get(&LimitType::Ip) + .unwrap(); + let limit_type_limit = limits.get(limit_type).unwrap(); + global.remaining > 0 && ip.remaining > 0 && limit_type_limit.remaining > 0 + } + + fn ensure_limit_in_map( + rate_limits_config: &RateLimits, + map: &mut HashMap, + limit_type: &LimitType, + ) { + log::trace!("Ensuring limit type {:?} is in the map.", limit_type); + let time: u64 = chrono::Utc::now().timestamp() as u64; + match limit_type { + LimitType::Channel(snowflake) => { + if map.get(&LimitType::Channel(*snowflake)).is_some() { + log::trace!( + "Limit type {:?} is already in the map. Returning.", + limit_type + ); + return; + } + log::trace!("Limit type {:?} is not in the map. Adding it.", limit_type); + let channel_limit = &rate_limits_config.routes.channel; + map.insert( + LimitType::Channel(*snowflake), + Limit { + bucket: LimitType::Channel(*snowflake), + limit: channel_limit.count, + remaining: channel_limit.count, + reset: channel_limit.window + time, + window: channel_limit.window, + }, + ); + } + LimitType::Guild(snowflake) => { + if map.get(&LimitType::Guild(*snowflake)).is_some() { + return; + } + let guild_limit = &rate_limits_config.routes.guild; + map.insert( + LimitType::Guild(*snowflake), + Limit { + bucket: LimitType::Guild(*snowflake), + limit: guild_limit.count, + remaining: guild_limit.count, + reset: guild_limit.window + time, + window: guild_limit.window, + }, + ); + } + LimitType::Webhook(snowflake) => { + if map.get(&LimitType::Webhook(*snowflake)).is_some() { + return; + } + let webhook_limit = &rate_limits_config.routes.webhook; + map.insert( + LimitType::Webhook(*snowflake), + Limit { + bucket: LimitType::Webhook(*snowflake), + limit: webhook_limit.count, + remaining: webhook_limit.count, + reset: webhook_limit.window + time, + window: webhook_limit.window, + }, + ); + } + other_limit => { + if map.get(other_limit).is_some() { + return; + } + let limits_map = ChorusRequest::limits_config_to_hashmap(rate_limits_config); + map.insert( + *other_limit, + Limit { + bucket: *other_limit, + limit: limits_map.get(other_limit).as_ref().unwrap().limit, + remaining: limits_map.get(other_limit).as_ref().unwrap().remaining, + reset: limits_map.get(other_limit).as_ref().unwrap().reset, + window: limits_map.get(other_limit).as_ref().unwrap().window, + }, + ); + } + } + } + + async fn interpret_error(response: reqwest::Response) -> ChorusError { + match response.status().as_u16() { + 401..=403 | 407 => ChorusError::NoPermission, + 404 => ChorusError::NotFound { + error: response.text().await.unwrap(), + }, + 405 | 408 | 409 => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap() }, + 411..=421 | 426 | 428 | 431 => ChorusError::InvalidArguments { + error: response.text().await.unwrap(), + }, + 429 => panic!("Illegal state: Rate limit exception should have been caught before this function call."), + 451 => ChorusError::NoResponse, + 500..=599 => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap() }, + _ => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap()}, + } + } + + /// Updates the rate limits of the user. The following steps are performed: + /// 1. If the current unix timestamp is greater than the reset timestamp, the reset timestamp is + /// set to the current unix timestamp + the rate limit window. The remaining rate limit is + /// reset to the rate limit limit. + /// 2. The remaining rate limit is decreased by 1. + fn update_rate_limits(user: &mut UserMeta, limit_type: &LimitType, response_was_err: bool) { + let instance_dictated_limits = [ + &LimitType::AuthLogin, + &LimitType::AuthRegister, + &LimitType::Global, + &LimitType::Ip, + ]; + // modify this to store something to look up the value with later, instead of storing a reference to the actual data itself. + let mut relevant_limits = Vec::new(); + if instance_dictated_limits.contains(&limit_type) { + relevant_limits.push((LimitOrigin::Instance, *limit_type)); + } else { + relevant_limits.push((LimitOrigin::User, *limit_type)); + } + relevant_limits.push((LimitOrigin::Instance, LimitType::Global)); + relevant_limits.push((LimitOrigin::Instance, LimitType::Ip)); + if response_was_err { + relevant_limits.push((LimitOrigin::User, LimitType::Error)); + } + let time: u64 = chrono::Utc::now().timestamp() as u64; + for relevant_limit in relevant_limits.iter() { + let mut belongs_to = user.belongs_to.borrow_mut(); + let limit = match relevant_limit.0 { + LimitOrigin::Instance => { + log::trace!( + "Updating instance rate limit. Bucket: {:?}", + relevant_limit.1 + ); + belongs_to + .limits_information + .as_mut() + .unwrap() + .ratelimits + .get_mut(&relevant_limit.1) + .unwrap() + } + LimitOrigin::User => { + log::trace!("Updating user rate limit. Bucket: {:?}", relevant_limit.1); + user.limits + .as_mut() + .unwrap() + .get_mut(&relevant_limit.1) + .unwrap() + } + }; + if time > limit.reset { + // Spacebar does not yet return rate limit information in its response headers. We + // therefore have to guess the next rate limit window. This is not ideal. Oh well! + log::trace!("Rate limit replenished. Bucket: {:?}", limit.bucket); + limit.reset += limit.window; + limit.remaining = limit.limit; + } + limit.remaining -= 1; + } + } + + pub(crate) async fn get_limits_config(url_api: &str) -> ChorusResult { + let request = Client::new() + .get(format!("{}/policies/instance/limits/", url_api)) + .send() + .await; + let request = match request { + Ok(request) => request, + Err(e) => { + return Err(ChorusError::RequestFailed { + url: url_api.to_string(), + error: e, + }) + } + }; + let limits_configuration = match request.status().as_u16() { + 200 => from_str::(&request.text().await.unwrap()).unwrap(), + 429 => { + return Err(ChorusError::RateLimited { + bucket: format!("{:?}", LimitType::Ip), + }) + } + 404 => return Err(ChorusError::NotFound { error: "Route \"/policies/instance/limits/\" not found. Are you perhaps trying to request the Limits configuration from an unsupported server?".to_string() }), + 400..=u16::MAX => { + return Err(ChorusError::ReceivedErrorCode { error_code: request.status().as_u16(), error: request.text().await.unwrap() }) + } + _ => { + return Err(ChorusError::InvalidResponse { + error: request.text().await.unwrap(), + }) + } + }; + + Ok(limits_configuration) + } + + pub(crate) fn limits_config_to_hashmap( + limits_configuration: &RateLimits, + ) -> HashMap { + let config = limits_configuration.clone(); + let routes = config.routes; + let mut map: HashMap = HashMap::new(); + let time: u64 = chrono::Utc::now().timestamp() as u64; + map.insert( + LimitType::AuthLogin, + Limit { + bucket: LimitType::AuthLogin, + limit: routes.auth.login.count, + remaining: routes.auth.login.count, + reset: routes.auth.login.window + time, + window: routes.auth.login.window, + }, + ); + map.insert( + LimitType::AuthRegister, + Limit { + bucket: LimitType::AuthRegister, + limit: routes.auth.register.count, + remaining: routes.auth.register.count, + reset: routes.auth.register.window + time, + window: routes.auth.register.window, + }, + ); + map.insert( + LimitType::ChannelBaseline, + Limit { + bucket: LimitType::ChannelBaseline, + limit: routes.channel.count, + remaining: routes.channel.count, + reset: routes.channel.window + time, + window: routes.channel.window, + }, + ); + map.insert( + LimitType::Error, + Limit { + bucket: LimitType::Error, + limit: config.error.count, + remaining: config.error.count, + reset: config.error.window + time, + window: config.error.window, + }, + ); + map.insert( + LimitType::Global, + Limit { + bucket: LimitType::Global, + limit: config.global.count, + remaining: config.global.count, + reset: config.global.window + time, + window: config.global.window, + }, + ); + map.insert( + LimitType::Ip, + Limit { + bucket: LimitType::Ip, + limit: config.ip.count, + remaining: config.ip.count, + reset: config.ip.window + time, + window: config.ip.window, + }, + ); + map.insert( + LimitType::GuildBaseline, + Limit { + bucket: LimitType::GuildBaseline, + limit: routes.guild.count, + remaining: routes.guild.count, + reset: routes.guild.window + time, + window: routes.guild.window, + }, + ); + map.insert( + LimitType::WebhookBaseline, + Limit { + bucket: LimitType::WebhookBaseline, + limit: routes.webhook.count, + remaining: routes.webhook.count, + reset: routes.webhook.window + time, + window: routes.webhook.window, + }, + ); + map + } + + /// Sends a [`ChorusRequest`] and returns a [`ChorusResult`] that contains nothing if the request + /// was successful, or a [`ChorusError`] if the request failed. + pub(crate) async fn handle_request_as_result(self, user: &mut UserMeta) -> ChorusResult<()> { + match self.send_request(user).await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + /// Sends a [`ChorusRequest`] and returns a [`ChorusResult`] that contains a [`T`] if the request + /// was successful, or a [`ChorusError`] if the request failed. + pub(crate) async fn deserialize_response Deserialize<'a>>( + self, + user: &mut UserMeta, + ) -> ChorusResult { + let response = self.send_request(user).await?; + debug!("Got response: {:?}", response); + let response_text = match response.text().await { + Ok(string) => string, + Err(e) => { + return Err(ChorusError::InvalidResponse { + error: format!( + "Error while trying to process the HTTP response into a String: {}", + e + ), + }); + } + }; + let object = match from_str::(&response_text) { + Ok(object) => object, + Err(e) => { + return Err(ChorusError::InvalidResponse { + error: format!( + "Error while trying to deserialize the JSON response into requested type T: {}. JSON Response: {}", + e, response_text + ), + }) + } + }; + Ok(object) + } +} + +enum LimitOrigin { + Instance, + User, +} diff --git a/src/types/config/types/general_configuration.rs b/src/types/config/types/general_configuration.rs index 07444b0..13b3aa8 100644 --- a/src/types/config/types/general_configuration.rs +++ b/src/types/config/types/general_configuration.rs @@ -18,10 +18,8 @@ pub struct GeneralConfiguration { impl Default for GeneralConfiguration { fn default() -> Self { Self { - instance_name: String::from("Spacebar Instance"), - instance_description: Some(String::from( - "This is a Spacebar instance made in the pre-release days", - )), + instance_name: String::from("Spacebar-compatible Instance"), + instance_description: Some(String::from("This is a spacebar-compatible instance.")), front_page: None, tos_page: None, correspondence_email: None, diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs index a854460..96e6ea8 100644 --- a/src/types/config/types/guild_configuration.rs +++ b/src/types/config/types/guild_configuration.rs @@ -10,7 +10,7 @@ use sqlx::{ database::{HasArguments, HasValueRef}, encode::IsNull, error::BoxDynError, - Decode, Encode, MySql, + Decode, MySql, }; use crate::types::config::types::subconfigs::guild::{ @@ -139,7 +139,7 @@ pub enum GuildFeatures { InvitesClosed, } -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq)] pub struct GuildFeaturesList(Vec); impl Deref for GuildFeaturesList { diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs index 9d0cab1..ce1ea60 100644 --- a/src/types/config/types/subconfigs/limits/rates.rs +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -1,7 +1,12 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; -use crate::types::config::types::subconfigs::limits::ratelimits::{ - route::RouteRateLimit, RateLimitOptions, +use crate::{ + api::LimitType, + types::config::types::subconfigs::limits::ratelimits::{ + route::RouteRateLimit, RateLimitOptions, + }, }; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -39,3 +44,18 @@ impl Default for RateLimits { } } } + +impl RateLimits { + pub fn to_hash_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert(LimitType::AuthLogin, self.routes.auth.login.clone()); + map.insert(LimitType::AuthRegister, self.routes.auth.register.clone()); + map.insert(LimitType::ChannelBaseline, self.routes.channel.clone()); + map.insert(LimitType::Error, self.error.clone()); + map.insert(LimitType::Global, self.global.clone()); + map.insert(LimitType::Ip, self.ip.clone()); + map.insert(LimitType::WebhookBaseline, self.routes.webhook.clone()); + map.insert(LimitType::GuildBaseline, self.routes.guild.clone()); + map + } +} diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 5471e22..154b83c 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -1,68 +1,70 @@ +use chorus_macros::Updateable; use chrono::Utc; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_string_from_number; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Updateable; use crate::types::{ entities::{GuildMember, User}, utils::Snowflake, }; -#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Channel { - pub id: Snowflake, - pub created_at: Option>, - #[serde(rename = "type")] - pub channel_type: ChannelType, - pub guild_id: Option, - pub position: Option, - #[cfg(feature = "sqlx")] - pub permission_overwrites: Option>>, - #[cfg(not(feature = "sqlx"))] - pub permission_overwrites: Option>, - pub name: Option, - pub topic: Option, - pub nsfw: Option, - pub last_message_id: Option, - pub bitrate: Option, - pub user_limit: Option, - pub rate_limit_per_user: Option, - #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub recipients: Option>, - pub icon: Option, - pub owner_id: Option, pub application_id: Option, - pub managed: Option, - pub parent_id: Option, - pub last_pin_timestamp: Option, - pub rtc_region: Option, - pub video_quality_mode: Option, - pub message_count: Option, - pub member_count: Option, - #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub thread_metadata: Option, - #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub member: Option, - pub default_auto_archive_duration: Option, - pub permissions: Option, - pub flags: Option, - pub total_message_sent: Option, - #[cfg(feature = "sqlx")] - pub available_tags: Option>>, - #[cfg(not(feature = "sqlx"))] - pub available_tags: Option>, #[cfg(feature = "sqlx")] pub applied_tags: Option>>, #[cfg(not(feature = "sqlx"))] pub applied_tags: Option>, #[cfg(feature = "sqlx")] + pub available_tags: Option>>, + #[cfg(not(feature = "sqlx"))] + pub available_tags: Option>, + pub bitrate: Option, + #[serde(rename = "type")] + pub channel_type: ChannelType, + pub created_at: Option>, + pub default_auto_archive_duration: Option, + pub default_forum_layout: Option, + #[cfg(feature = "sqlx")] pub default_reaction_emoji: Option>, #[cfg(not(feature = "sqlx"))] pub default_reaction_emoji: Option, - pub default_thread_rate_limit_per_user: Option, pub default_sort_order: Option, - pub default_forum_layout: Option, + pub default_thread_rate_limit_per_user: Option, + pub flags: Option, + pub guild_id: Option, + pub icon: Option, + pub id: Snowflake, + pub last_message_id: Option, + pub last_pin_timestamp: Option, + pub managed: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] + pub member: Option, + pub member_count: Option, + pub message_count: Option, + pub name: Option, + pub nsfw: Option, + pub owner_id: Option, + pub parent_id: Option, + #[cfg(feature = "sqlx")] + pub permission_overwrites: Option>>, + #[cfg(not(feature = "sqlx"))] + pub permission_overwrites: Option>, + pub permissions: Option, + pub position: Option, + pub rate_limit_per_user: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] + pub recipients: Option>, + pub rtc_region: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] + pub thread_metadata: Option, + pub topic: Option, + pub total_message_sent: Option, + pub user_limit: Option, + pub video_quality_mode: Option, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] @@ -74,7 +76,7 @@ pub struct Tag { pub emoji_name: Option, } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd)] pub struct PermissionOverwrite { pub id: Snowflake, #[serde(rename = "type")] diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index dbe998b..a0e8d14 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::User; use crate::types::Snowflake; -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Emoji { pub id: Option, diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 857ed2a..67e7f44 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -91,7 +91,7 @@ pub struct Guild { } /// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user- -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct GuildBan { pub user_id: Snowflake, diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs new file mode 100644 index 0000000..6d7b570 --- /dev/null +++ b/src/types/entities/invite.rs @@ -0,0 +1,75 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Snowflake, WelcomeScreenObject}; + +use super::guild::GuildScheduledEvent; +use super::{Application, Channel, GuildMember, User}; + +/// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users. +/// See +#[derive(Debug, Serialize, Deserialize)] +pub struct Invite { + pub approximate_member_count: Option, + pub approximate_presence_count: Option, + pub channel: Option, + pub code: String, + pub created_at: Option>, + pub expires_at: Option>, + pub flags: Option, + pub guild: Option, + pub guild_id: Option, + pub guild_scheduled_event: Option, + #[serde(rename = "type")] + pub invite_type: Option, + pub inviter: Option, + pub max_age: Option, + pub max_uses: Option, + pub stage_instance: Option, + pub target_application: Option, + pub target_type: Option, + pub target_user: Option, + pub temporary: Option, + pub uses: Option, +} + +/// The guild an invite is for. +/// See +#[derive(Debug, Serialize, Deserialize)] +pub struct InviteGuild { + pub id: Snowflake, + pub name: String, + pub icon: Option, + pub splash: Option, + pub verification_level: i32, + pub features: Vec, + pub vanity_url_code: Option, + pub description: Option, + pub banner: Option, + pub premium_subscription_count: Option, + #[serde(rename = "nsfw")] + #[serde(skip_serializing_if = "Option::is_none")] + pub nsfw_deprecated: Option, + pub nsfw_level: NSFWLevel, + pub welcome_screen: Option, +} + +/// See for an explanation on what +/// the levels mean. +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NSFWLevel { + Default = 0, + Explicit = 1, + Safe = 2, + AgeRestricted = 3, +} + +/// See +#[derive(Debug, Serialize, Deserialize)] +pub struct InviteStageInstance { + pub members: Vec, + pub participant_count: i32, + pub speaker_count: i32, + pub topic: String, +} diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index b63bb9a..6f1f58b 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -8,6 +8,7 @@ pub use emoji::*; pub use guild::*; pub use guild_member::*; pub use integration::*; +pub use invite::*; pub use message::*; pub use relationship::*; pub use role::*; @@ -31,6 +32,7 @@ mod emoji; mod guild; mod guild_member; mod integration; +mod invite; mod message; mod relationship; mod role; diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index d0059fc..469c45e 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -1,9 +1,8 @@ +use crate::types::utils::Snowflake; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_option_number_from_string; -use crate::types::utils::Snowflake; - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] pub struct UserData { @@ -16,7 +15,6 @@ impl User { PublicUser::from(self) } } - #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct User { @@ -45,9 +43,9 @@ pub struct User { pub bio: Option, pub theme_colors: Option>, pub phone: Option, - pub nsfw_allowed: bool, - pub premium: bool, - pub purchased_flags: i32, + pub nsfw_allowed: Option, + pub premium: Option, + pub purchased_flags: Option, pub premium_usage_flags: Option, pub disabled: Option, } @@ -89,6 +87,7 @@ impl From for PublicUser { } } +#[allow(dead_code)] // FIXME: Remove this when we actually use this const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32; bitflags::bitflags! { diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index 99c7640..b595d57 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -3,6 +3,8 @@ use crate::types::{entities::Channel, Snowflake}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use super::UpdateMessage; + #[derive(Debug, Default, Deserialize, Serialize)] /// See https://discord.com/developers/docs/topics/gateway-events#channel-pins-update pub struct ChannelPinsUpdate { @@ -31,6 +33,15 @@ pub struct ChannelUpdate { impl WebSocketEvent for ChannelUpdate {} +impl UpdateMessage for ChannelUpdate { + fn update(&self, object_to_update: &mut Channel) { + *object_to_update = self.channel.clone(); + } + fn id(&self) -> Snowflake { + self.channel.id + } +} + #[derive(Debug, Default, Deserialize, Serialize, Clone)] /// Officially undocumented. /// Sends updates to client about a new message with its id diff --git a/src/types/events/hello.rs b/src/types/events/hello.rs index e370f9c..44f1e4f 100644 --- a/src/types/events/hello.rs +++ b/src/types/events/hello.rs @@ -14,8 +14,7 @@ impl WebSocketEvent for GatewayHello {} /// Contains info on how often the client should send heartbeats to the server; pub struct HelloData { /// How often a client should send heartbeats, in milliseconds - // u128 because std used u128s for milliseconds - pub heartbeat_interval: u128, + pub heartbeat_interval: u64, } impl WebSocketEvent for HelloData {} diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index e3547e9..26dcd80 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +use crate::gateway::Updateable; pub use application::*; pub use auto_moderation::*; pub use call::*; @@ -25,9 +26,10 @@ pub use thread::*; pub use user::*; pub use voice::*; pub use webhooks::*; - pub use webrtc::*; +use super::Snowflake; + mod application; mod auto_moderation; mod call; @@ -99,3 +101,23 @@ pub struct GatewayReceivePayload<'a> { } impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {} + +/// An [`UpdateMessage`] represents a received Gateway Message which contains updated +/// information for an [`Updateable`] of Type T. +/// # Example: +/// ```rs +/// impl UpdateMessage for ChannelUpdate { +/// fn update(...) {...} +/// fn id(...) {...} +/// } +/// ``` +/// This would imply, that the [`WebSocketEvent`] "[`ChannelUpdate`]" contains new/updated information +/// about a [`Channel`]. The update method describes how this new information will be turned into +/// a [`Channel`] object. +pub(crate) trait UpdateMessage: Clone +where + T: Updateable, +{ + fn update(&self, object_to_update: &mut T); + fn id(&self) -> Snowflake; +} diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs index 8b8a601..9a3b9d6 100644 --- a/src/types/schema/auth.rs +++ b/src/types/schema/auth.rs @@ -1,122 +1,8 @@ -use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::errors::FieldFormatError; - -/** -A struct that represents a well-formed email address. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthEmail { - pub email: String, -} - -impl AuthEmail { - /** - Returns a new [`Result`]. - ## Arguments - The email address you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The email address is not in a valid format. - - */ - pub fn new(email: String) -> Result { - let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); - if !regex.is_match(email.as_str()) { - return Err(FieldFormatError::EmailError); - } - Ok(AuthEmail { email }) - } -} - -/** -A struct that represents a well-formed username. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthUsername { - pub username: String, -} - -impl AuthUsername { - /** - Returns a new [`Result`]. - ## Arguments - The username you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is not between 2 and 32 characters. - */ - pub fn new(username: String) -> Result { - if username.len() < 2 || username.len() > 32 { - Err(FieldFormatError::UsernameError) - } else { - Ok(AuthUsername { username }) - } - } -} - -/** -A struct that represents a well-formed password. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The password is not between 1 and 72 characters. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthPassword { - pub password: String, -} - -impl AuthPassword { - /** - Returns a new [`Result`]. - ## Arguments - The password you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The password is not between 1 and 72 characters. - */ - pub fn new(password: String) -> Result { - if password.is_empty() || password.len() > 72 { - Err(FieldFormatError::PasswordError) - } else { - Ok(AuthPassword { password }) - } - } -} - -/** -A struct that represents a well-formed register request. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. -- The password is not between 1 and 72 characters. - */ -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct RegisterSchema { - username: String, - password: Option, - consent: bool, - email: Option, - fingerprint: Option, - invite: Option, - date_of_birth: Option, - gift_code_sku_id: Option, - captcha_key: Option, - promotional_email_opt_in: Option, -} - -pub struct RegisterSchemaOptions { pub username: String, pub password: Option, pub consent: bool, @@ -129,123 +15,21 @@ pub struct RegisterSchemaOptions { pub promotional_email_opt_in: Option, } -impl RegisterSchema { - pub fn builder(username: impl Into, consent: bool) -> RegisterSchemaOptions { - RegisterSchemaOptions { - username: username.into(), - password: None, - consent, - email: None, - fingerprint: None, - invite: None, - date_of_birth: None, - gift_code_sku_id: None, - captcha_key: None, - promotional_email_opt_in: None, - } - } -} - -impl RegisterSchemaOptions { - /** - Create a new [`RegisterSchema`]. - ## Arguments - All but "String::username" and "bool::consent" are optional. - - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is less than 2 or more than 32 characters in length - - You supply a `password` which is less than 1 or more than 72 characters in length. - - These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) - */ - pub fn build(self) -> Result { - let username = AuthUsername::new(self.username)?.username; - - let email = if let Some(email) = self.email { - Some(AuthEmail::new(email)?.email) - } else { - None - }; - - let password = if let Some(password) = self.password { - Some(AuthPassword::new(password)?.password) - } else { - None - }; - - if !self.consent { - return Err(FieldFormatError::ConsentError); - } - - Ok(RegisterSchema { - username, - password, - consent: self.consent, - email, - fingerprint: self.fingerprint, - invite: self.invite, - date_of_birth: self.date_of_birth, - gift_code_sku_id: self.gift_code_sku_id, - captcha_key: self.captcha_key, - promotional_email_opt_in: self.promotional_email_opt_in, - }) - } -} - -/** -A struct that represents a well-formed login request. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. -- The password is not between 1 and 72 characters. - */ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct LoginSchema { + /// For Discord, usernames must be between 2 and 32 characters, + /// but other servers may have different limits. pub login: String, - pub password: Option, + /// For Discord, must be between 1 and 72 characters, + /// but other servers may have different limits. + pub password: String, pub undelete: Option, pub captcha_key: Option, pub login_source: Option, pub gift_code_sku_id: Option, } -impl LoginSchema { - /** - Returns a new [`Result`]. - ## Arguments - login: The username you want to login with. - password: The password you want to login with. - undelete: Honestly no idea what this is for. - captcha_key: The captcha key you want to login with. - login_source: The login source. - gift_code_sku_id: The gift code sku id. - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is less than 2 or more than 32 characters in length - */ - pub fn new( - login: String, - password: Option, - undelete: Option, - captcha_key: Option, - login_source: Option, - gift_code_sku_id: Option, - ) -> Result { - Ok(LoginSchema { - login, - password, - undelete, - captcha_key, - login_source, - gift_code_sku_id, - }) - } -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct TotpSchema { diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index 2a78142..27c78ee 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -1,8 +1,9 @@ +use bitflags::bitflags; use serde::{Deserialize, Serialize}; use crate::types::{entities::PermissionOverwrite, Snowflake}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)] #[serde(rename_all = "snake_case")] pub struct ChannelCreateSchema { pub name: String, @@ -26,7 +27,7 @@ pub struct ChannelCreateSchema { pub video_quality_mode: Option, } -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, PartialOrd)] #[serde(rename_all = "snake_case")] pub struct ChannelModifySchema { pub name: Option, @@ -48,7 +49,7 @@ pub struct ChannelModifySchema { pub video_quality_mode: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct GetChannelMessagesSchema { /// Between 1 and 100, defaults to 50. pub limit: Option, @@ -56,7 +57,7 @@ pub struct GetChannelMessagesSchema { pub anchor: ChannelMessagesAnchor, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[serde(rename_all = "snake_case")] pub enum ChannelMessagesAnchor { Before(Snowflake), @@ -94,3 +95,56 @@ impl GetChannelMessagesSchema { } } } + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +pub struct CreateChannelInviteSchema { + pub flags: Option, + pub max_age: Option, + pub max_uses: Option, + pub temporary: Option, + pub unique: Option, + pub validate: Option, + pub target_type: Option, + pub target_user_id: Option, + pub target_application_id: Option, +} + +impl Default for CreateChannelInviteSchema { + fn default() -> Self { + Self { + flags: None, + max_age: Some(86400), + max_uses: Some(0), + temporary: Some(false), + unique: Some(false), + validate: None, + target_type: None, + target_user_id: None, + target_application_id: None, + } + } +} + +bitflags! { + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] + pub struct InviteFlags: u64 { + const GUEST = 1 << 0; + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InviteType { + #[default] + Stream = 1, + EmbeddedApplication = 2, + RoleSubscriptions = 3, + CreatorPage = 4, +} + +/// See +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq)] +pub struct AddChannelRecipientSchema { + pub access_token: Option, + pub nick: Option, +} diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs index 1069428..08dae05 100644 --- a/src/types/schema/mod.rs +++ b/src/types/schema/mod.rs @@ -15,76 +15,3 @@ mod message; mod relationship; mod role; mod user; - -#[cfg(test)] -mod schemas_tests { - use crate::errors::FieldFormatError; - - use super::*; - - #[test] - fn password_too_short() { - assert_eq!( - AuthPassword::new("".to_string()), - Err(FieldFormatError::PasswordError) - ); - } - - #[test] - fn password_too_long() { - let mut long_pw = String::new(); - for _ in 0..73 { - long_pw += "a"; - } - assert_eq!( - AuthPassword::new(long_pw), - Err(FieldFormatError::PasswordError) - ); - } - - #[test] - fn username_too_short() { - assert_eq!( - AuthUsername::new("T".to_string()), - Err(FieldFormatError::UsernameError) - ); - } - - #[test] - fn username_too_long() { - let mut long_un = String::new(); - for _ in 0..33 { - long_un += "a"; - } - assert_eq!( - AuthUsername::new(long_un), - Err(FieldFormatError::UsernameError) - ); - } - - #[test] - fn consent_false() { - assert_eq!( - RegisterSchema::builder("Test", false).build(), - Err(FieldFormatError::ConsentError) - ); - } - - #[test] - fn invalid_email() { - assert_eq!( - AuthEmail::new("p@p.p".to_string()), - Err(FieldFormatError::EmailError) - ) - } - - #[test] - fn valid_email() { - let reg = RegisterSchemaOptions { - email: Some("me@mail.de".to_string()), - ..RegisterSchema::builder("Testy", true) - } - .build(); - assert_ne!(reg, Err(FieldFormatError::EmailError)); - } -} diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index ab85a16..c8cf5bb 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -1,5 +1,9 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; +use crate::types::Snowflake; + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub struct UserModifySchema { @@ -14,3 +18,17 @@ pub struct UserModifySchema { pub email: Option, pub discriminator: Option, } + +/// # Attributes: +/// - recipients: The users to include in the private channel +/// - access_tokens: The access tokens of users that have granted your app the `gdm.join` scope. Only usable for OAuth2 requests (which can only create group DMs). +/// - nicks: A mapping of user IDs to their respective nicknames. Only usable for OAuth2 requests (which can only create group DMs). +/// +/// # Reference: +/// Read: +#[derive(Debug, Deserialize, Serialize)] +pub struct PrivateChannelCreateSchema { + pub recipients: Option>, + pub access_tokens: Option>, + pub nicks: Option>, +} diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs index 0198af6..fecf268 100644 --- a/src/types/utils/rights.rs +++ b/src/types/utils/rights.rs @@ -73,6 +73,7 @@ impl Rights { } } +#[allow(dead_code)] // FIXME: Remove this when we use this fn all_rights() -> Rights { Rights::OPERATOR | Rights::MANAGE_APPLICATIONS diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs index 8502275..77514a1 100644 --- a/src/types/utils/snowflake.rs +++ b/src/types/utils/snowflake.rs @@ -12,7 +12,7 @@ const EPOCH: i64 = 1420070400000; /// Unique identifier including a timestamp. /// See https://discord.com/developers/docs/reference#snowflakes -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "sqlx", derive(Type))] #[cfg_attr(feature = "sqlx", sqlx(transparent))] pub struct Snowflake(u64); diff --git a/tests/auth.rs b/tests/auth.rs index 6972ace..c26552f 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,16 +1,16 @@ -use chorus::types::{RegisterSchema, RegisterSchemaOptions}; +use chorus::types::RegisterSchema; mod common; #[tokio::test] async fn test_registration() { let mut bundle = common::setup().await; - let reg = RegisterSchemaOptions { + let reg = RegisterSchema { + username: "Hiiii".into(), date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("Hiiii", true) - } - .build() - .unwrap(); + consent: true, + ..Default::default() + }; bundle.instance.register_account(®).await.unwrap(); common::teardown(bundle).await; } diff --git a/tests/channel.rs b/tests/channels.rs similarity index 52% rename from tests/channel.rs rename to tests/channels.rs index 06a3330..c8564d7 100644 --- a/tests/channel.rs +++ b/tests/channels.rs @@ -1,6 +1,6 @@ use chorus::types::{ self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, - PermissionOverwrite, Snowflake, + PermissionOverwrite, PrivateChannelCreateSchema, RelationshipType, Snowflake, }; mod common; @@ -28,10 +28,11 @@ async fn delete_channel() { #[tokio::test] async fn modify_channel() { + const CHANNEL_NAME: &str = "beepboop"; let mut bundle = common::setup().await; let channel = &mut bundle.channel; let modify_data: types::ChannelModifySchema = types::ChannelModifySchema { - name: Some("beepboop".to_string()), + name: Some(CHANNEL_NAME.to_string()), channel_type: None, topic: None, icon: None, @@ -49,10 +50,10 @@ async fn modify_channel() { default_thread_rate_limit_per_user: None, video_quality_mode: None, }; - Channel::modify(channel, modify_data, channel.id, &mut bundle.user) + let modified_channel = Channel::modify(channel, modify_data, channel.id, &mut bundle.user) .await .unwrap(); - assert_eq!(channel.name, Some("beepboop".to_string())); + assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string())); let permission_override = PermissionFlags::from_vec(Vec::from([ PermissionFlags::MANAGE_CHANNELS, @@ -89,12 +90,11 @@ async fn get_channel_messages() { let _ = bundle .user .send_message( - &mut MessageSendSchema { + MessageSendSchema { content: Some("A Message!".to_string()), ..Default::default() }, bundle.channel.id, - None, ) .await .unwrap(); @@ -136,3 +136,81 @@ async fn get_channel_messages() { common::teardown(bundle).await } + +#[tokio::test] +async fn create_dm() { + let mut bundle = common::setup().await; + let other_user = bundle.create_user("integrationtestuser2").await; + let user = &mut bundle.user; + let private_channel_create_schema = PrivateChannelCreateSchema { + recipients: Some(Vec::from([other_user.object.id])), + access_tokens: None, + nicks: None, + }; + let dm_channel = user + .create_private_channel(private_channel_create_schema) + .await + .unwrap(); + assert!(dm_channel.recipients.is_some()); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id, + other_user.object.id + ); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id, + user.object.id + ); + common::teardown(bundle).await; +} + +// #[tokio::test] +// This test currently is broken due to an issue with the Spacebar Server. +#[allow(dead_code)] +async fn remove_add_person_from_to_dm() { + let mut bundle = common::setup().await; + let mut other_user = bundle.create_user("integrationtestuser2").await; + let mut third_user = bundle.create_user("integrationtestuser3").await; + let user = &mut bundle.user; + let private_channel_create_schema = PrivateChannelCreateSchema { + recipients: Some(Vec::from([other_user.object.id, third_user.object.id])), + access_tokens: None, + nicks: None, + }; + let dm_channel = user + .create_private_channel(private_channel_create_schema) + .await + .unwrap(); // Creates the Channel and stores the response Channel object + dm_channel + .remove_channel_recipient(other_user.object.id, user) + .await + .unwrap(); + assert!(dm_channel.recipients.as_ref().unwrap().get(1).is_none()); + other_user + .modify_user_relationship(user.object.id, RelationshipType::Friends) + .await + .unwrap(); + user.modify_user_relationship(other_user.object.id, RelationshipType::Friends) + .await + .unwrap(); + third_user + .modify_user_relationship(user.object.id, RelationshipType::Friends) + .await + .unwrap(); + user.modify_user_relationship(third_user.object.id, RelationshipType::Friends) + .await + .unwrap(); + // Users 1-2 and 1-3 are now friends + dm_channel + .add_channel_recipient(other_user.object.id, user, None) + .await + .unwrap(); + assert!(dm_channel.recipients.is_some()); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id, + other_user.object.id + ); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id, + user.object.id + ); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 900e31e..747a8cd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,15 +1,16 @@ +use chorus::gateway::Gateway; use chorus::{ - errors::ChorusResult, instance::{Instance, UserMeta}, types::{ Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema, - RegisterSchemaOptions, RoleCreateModifySchema, RoleObject, + RoleCreateModifySchema, RoleObject, }, UrlBundle, }; +#[allow(dead_code)] #[derive(Debug)] -pub struct TestBundle { +pub(crate) struct TestBundle { pub urls: UrlBundle, pub user: UserMeta, pub instance: Instance, @@ -18,21 +19,47 @@ pub struct TestBundle { pub channel: Channel, } +#[allow(unused)] +impl TestBundle { + pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta { + let register_schema = RegisterSchema { + username: username.to_string(), + consent: true, + date_of_birth: Some("2000-01-01".to_string()), + ..Default::default() + }; + self.instance + .register_account(®ister_schema) + .await + .unwrap() + } + pub(crate) async fn clone_user_without_gateway(&self) -> UserMeta { + UserMeta { + belongs_to: self.user.belongs_to.clone(), + token: self.user.token.clone(), + limits: self.user.limits.clone(), + settings: self.user.settings.clone(), + object: self.user.object.clone(), + gateway: Gateway::new(self.instance.urls.wss.clone()).await.unwrap(), + } + } +} + // Set up a test by creating an Instance and a User. Reduces Test boilerplate. -pub async fn setup() -> TestBundle { +pub(crate) async fn setup() -> TestBundle { let urls = UrlBundle::new( "http://localhost:3001/api".to_string(), "ws://localhost:3001".to_string(), "http://localhost:3001".to_string(), ); - let mut instance = Instance::new(urls.clone()).await.unwrap(); + let mut instance = Instance::new(urls.clone(), true).await.unwrap(); // Requires the existance of the below user. - let reg = RegisterSchemaOptions { + let reg = RegisterSchema { + username: "integrationtestuser".into(), + consent: true, date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser", true) - } - .build() - .unwrap(); + ..Default::default() + }; let guild_create_schema = GuildCreateSchema { name: Some("Test-Guild!".to_string()), region: None, @@ -94,7 +121,7 @@ pub async fn setup() -> TestBundle { // Teardown method to clean up after a test. #[allow(dead_code)] -pub async fn teardown(mut bundle: TestBundle) { +pub(crate) async fn teardown(mut bundle: TestBundle) { Guild::delete(&mut bundle.user, bundle.guild.id) .await .unwrap(); diff --git a/tests/gateway.rs b/tests/gateway.rs index c6f46dd..21a2018 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -1,13 +1,15 @@ mod common; + use chorus::gateway::*; -use chorus::types; +use chorus::types::{self, Channel}; #[tokio::test] /// Tests establishing a connection (hello and heartbeats) on the local gateway; async fn test_gateway_establish() { let bundle = common::setup().await; - Gateway::new(bundle.urls.wss).await.unwrap(); + Gateway::new(bundle.urls.wss.clone()).await.unwrap(); + common::teardown(bundle).await } #[tokio::test] @@ -15,10 +17,30 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway = Gateway::new(bundle.urls.wss).await.unwrap(); + let gateway = Gateway::new(bundle.urls.wss.clone()).await.unwrap(); let mut identify = types::GatewayIdentifyPayload::common(); - identify.token = bundle.user.token; + identify.token = bundle.user.token.clone(); gateway.send_identify(identify).await; + common::teardown(bundle).await +} + +#[tokio::test] +async fn test_self_updating_structs() { + let mut bundle = common::setup().await; + let channel_updater = bundle.user.gateway.observe(bundle.channel.clone()).await; + let received_channel = channel_updater.borrow().clone(); + assert_eq!(received_channel, bundle.channel); + let channel = &mut bundle.channel; + let modify_data = types::ChannelModifySchema { + name: Some("beepboop".to_string()), + ..Default::default() + }; + Channel::modify(channel, modify_data, channel.id, &mut bundle.user) + .await + .unwrap(); + let received_channel = channel_updater.borrow(); + assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop"); + common::teardown(bundle).await } diff --git a/tests/guild.rs b/tests/guilds.rs similarity index 100% rename from tests/guild.rs rename to tests/guilds.rs diff --git a/tests/invites.rs b/tests/invites.rs new file mode 100644 index 0000000..d19be61 --- /dev/null +++ b/tests/invites.rs @@ -0,0 +1,23 @@ +mod common; +use chorus::types::CreateChannelInviteSchema; +#[tokio::test] +async fn create_accept_invite() { + let mut bundle = common::setup().await; + let channel = bundle.channel.clone(); + let mut other_user = bundle.create_user("testuser1312").await; + let user = &mut bundle.user; + let create_channel_invite_schema = CreateChannelInviteSchema::default(); + assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) + .await + .is_err()); + let invite = user + .create_guild_invite(create_channel_invite_schema, channel.id) + .await + .unwrap(); + + other_user.accept_invite(&invite.code, None).await.unwrap(); + assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) + .await + .is_ok()); + common::teardown(bundle).await; +} diff --git a/tests/member.rs b/tests/members.rs similarity index 100% rename from tests/member.rs rename to tests/members.rs diff --git a/tests/message.rs b/tests/messages.rs similarity index 82% rename from tests/message.rs rename to tests/messages.rs index 94f3734..af6ee5b 100644 --- a/tests/message.rs +++ b/tests/messages.rs @@ -8,13 +8,13 @@ mod common; #[tokio::test] async fn send_message() { let mut bundle = common::setup().await; - let mut message = types::MessageSendSchema { + let message = types::MessageSendSchema { content: Some("A Message!".to_string()), ..Default::default() }; let _ = bundle .user - .send_message(&mut message, bundle.channel.id, None) + .send_message(message, bundle.channel.id) .await .unwrap(); common::teardown(bundle).await @@ -45,7 +45,7 @@ async fn send_message_attachment() { content: buffer, }; - let mut message = types::MessageSendSchema { + let message = types::MessageSendSchema { content: Some("trans rights now".to_string()), attachments: Some(vec![attachment.clone()]), ..Default::default() @@ -55,11 +55,7 @@ async fn send_message_attachment() { let _arg = Some(&vec_attach); bundle .user - .send_message( - &mut message, - bundle.channel.id, - Some(vec![attachment.clone()]), - ) + .send_message(message, bundle.channel.id) .await .unwrap(); common::teardown(bundle).await diff --git a/tests/relationships.rs b/tests/relationships.rs index 81f3230..00ef9cf 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,25 +1,17 @@ -use chorus::types::{self, RegisterSchema, RegisterSchemaOptions, Relationship, RelationshipType}; +use chorus::types::{self, Relationship, RelationshipType}; mod common; #[tokio::test] async fn test_get_mutual_relationships() { - let register_schema = RegisterSchemaOptions { - date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); let friend_request_schema = types::FriendRequestSendSchema { username: user.object.username.clone(), discriminator: Some(user.object.discriminator.clone()), }; - other_user.send_friend_request(friend_request_schema).await; + let _ = other_user.send_friend_request(friend_request_schema).await; let relationships = user .get_mutual_relationships(other_user.object.id) .await @@ -30,22 +22,17 @@ async fn test_get_mutual_relationships() { #[tokio::test] async fn test_get_relationships() { - let register_schema = RegisterSchemaOptions { - date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); let friend_request_schema = types::FriendRequestSendSchema { username: user.object.username.clone(), discriminator: Some(user.object.discriminator.clone()), }; - other_user.send_friend_request(friend_request_schema).await; + other_user + .send_friend_request(friend_request_schema) + .await + .unwrap(); let relationships = user.get_relationships().await.unwrap(); assert_eq!(relationships.get(0).unwrap().id, other_user.object.id); common::teardown(bundle).await @@ -53,18 +40,10 @@ async fn test_get_relationships() { #[tokio::test] async fn test_modify_relationship_friends() { - let register_schema = RegisterSchemaOptions { - date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); - other_user + let _ = other_user .modify_user_relationship(user.object.id, types::RelationshipType::Friends) .await; let relationships = user.get_relationships().await.unwrap(); @@ -79,7 +58,8 @@ async fn test_modify_relationship_friends() { relationships.get(0).unwrap().relationship_type, RelationshipType::Outgoing ); - user.modify_user_relationship(other_user.object.id, RelationshipType::Friends) + let _ = user + .modify_user_relationship(other_user.object.id, RelationshipType::Friends) .await; assert_eq!( other_user @@ -91,7 +71,7 @@ async fn test_modify_relationship_friends() { .relationship_type, RelationshipType::Friends ); - user.remove_relationship(other_user.object.id).await; + let _ = user.remove_relationship(other_user.object.id).await; assert_eq!( other_user.get_relationships().await.unwrap(), Vec::::new() @@ -101,18 +81,10 @@ async fn test_modify_relationship_friends() { #[tokio::test] async fn test_modify_relationship_block() { - let register_schema = RegisterSchemaOptions { - date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); - other_user + let _ = other_user .modify_user_relationship(user.object.id, types::RelationshipType::Blocked) .await; let relationships = user.get_relationships().await.unwrap(); @@ -123,7 +95,7 @@ async fn test_modify_relationship_block() { relationships.get(0).unwrap().relationship_type, RelationshipType::Blocked ); - other_user.remove_relationship(user.object.id).await; + let _ = other_user.remove_relationship(user.object.id).await; assert_eq!( other_user.get_relationships().await.unwrap(), Vec::::new()