diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0cf6fd9..0f680cc 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -13,13 +13,14 @@ jobs: linux: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Clone spacebar server run: | git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' @@ -52,7 +53,7 @@ jobs: # - name: Clone spacebar server # run: | # git clone https://github.com/bitfl0wer/server.git - # - uses: actions/setup-node@v3 + # - uses: actions/setup-node@v4 # with: # node-version: 18 # cache: 'npm' @@ -75,12 +76,13 @@ jobs: # SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast wasm-gecko: runs-on: macos-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Clone spacebar server run: | git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' @@ -100,15 +102,16 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" wasm-chrome: runs-on: macos-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Clone spacebar server run: | git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' @@ -128,4 +131,4 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 4c45b0b..b12c0ed 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -29,12 +29,9 @@ jobs: uses: actions/checkout@v4 - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable components: clippy - override: true - name: Install required cargo run: cargo install clippy-sarif sarif-fmt @@ -47,7 +44,7 @@ jobs: continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: rust-clippy-results.sarif wait-for-processing: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..34a1153 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ + +# Contributing + +**Please refer to the [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) and [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md) before making a contribution.** + +Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement, +ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401). + +If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility. +Please feel free to open an Issue with the idea you have, or a Pull Request. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 036dc73..46b87f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,10 +18,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "ahash" -version = "0.8.6" +name = "aead" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom", @@ -62,13 +72,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -130,9 +140,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -199,14 +209,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chorus" -version = "0.13.0" +version = "0.15.0" dependencies = [ "async-trait", - "base64 0.21.5", + "base64 0.21.7", "bitflags 2.4.1", "chorus-macros", "chrono", + "crypto_secretbox", "custom_error", + "discortp", "futures-util", "getrandom", "hostname", @@ -221,7 +233,6 @@ dependencies = [ "reqwest", "rustls", "rustls-native-certs", - "safina-timer", "serde", "serde-aux", "serde_json", @@ -235,18 +246,19 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", + "wasmtimer", "ws_stream_wasm", ] [[package]] name = "chorus-macros" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81545a60b926f815517dadbbd40cd502294ae2baea25fa8194d854d607512b0" +checksum = "de4221700bc486c6e6bc261fdea478936d33067a06325895f5d2a8cde5917272" dependencies = [ "async-trait", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -264,6 +276,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -276,9 +299,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" @@ -298,9 +321,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -322,22 +345,18 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-common" @@ -346,9 +365,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "custom_error" version = "1.9.2" @@ -376,7 +411,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -387,7 +422,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -409,9 +444,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -429,6 +464,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "discortp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b9439c09174aede2c88d58cfc6b83575b06569d1af4d07562f76595b2896b" +dependencies = [ + "pnet_macros", + "pnet_macros_support", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -541,9 +586,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -556,9 +601,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -566,15 +611,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -594,38 +639,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -647,13 +692,14 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", @@ -670,9 +716,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -718,7 +764,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "headers-core", "http", @@ -759,9 +805,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -777,11 +823,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -831,9 +877,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -846,7 +892,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -868,9 +914,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -927,6 +973,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -972,7 +1027,7 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "pem", "ring 0.16.20", "serde", @@ -991,9 +1046,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libm" @@ -1052,9 +1107,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" @@ -1089,9 +1144,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1127,6 +1182,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "nom" version = "7.1.3" @@ -1208,9 +1269,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -1222,10 +1283,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "openssl" -version = "0.10.61" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -1244,7 +1311,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -1255,9 +1322,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.97" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -1363,9 +1430,39 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "pnet_base" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d3a993d49e5fd5d4d854d6999d4addca1f72d86c65adf224a36757161c02b6" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_macros" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd52a5211fac27e7acb14cfc9f30ae16ae0e956b7b779c8214c74559cef4c3" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "pnet_macros_support" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89de095dc7739349559913aed1ef6a11e73ceade4897dadc77c5e09de6740750" +dependencies = [ + "pnet_base", +] [[package]] name = "poem" @@ -1407,7 +1504,18 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", ] [[package]] @@ -1434,18 +1542,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1520,10 +1628,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" -source = "git+https://github.com/bitfl0wer/reqwest.git?branch=wasm-headers#858197c528f074f00397f7e2675d3eb15cc10a75" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -1672,7 +1781,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -1692,21 +1801,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] -name = "safina-timer" -version = "0.1.11" +name = "salsa20" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1081a264d1a3e81b75c4bcd5696094fb6ce470c2ded14cbd47bcb5229079b9df" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "once_cell", + "cipher", ] [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1756,9 +1865,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "send_wrapper" @@ -1768,9 +1877,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] @@ -1788,20 +1897,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -1810,13 +1919,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -1837,7 +1946,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", @@ -1857,7 +1966,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -1919,16 +2028,6 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -2078,7 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" dependencies = [ "atoi", - "base64 0.21.5", + "base64 0.21.7", "bitflags 2.4.1", "byteorder", "bytes", @@ -2121,7 +2220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ "atoi", - "base64 0.21.5", + "base64 0.21.7", "bitflags 2.4.1", "byteorder", "chrono", @@ -2215,9 +2314,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -2247,42 +2346,42 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", @@ -2300,9 +2399,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -2324,9 +2423,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -2334,7 +2433,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -2347,7 +2446,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -2453,7 +2552,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -2548,6 +2647,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -2610,6 +2719,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -2631,7 +2746,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -2665,7 +2780,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2698,7 +2813,21 @@ checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", +] + +[[package]] +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", ] [[package]] @@ -2713,15 +2842,19 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +dependencies = [ + "redox_syscall", + "wasite", +] [[package]] name = "wildmatch" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee583bdc5ff1cf9db20e9db5bb3ff4c3089a8f6b8b31aff265c9aba85812db86" +checksum = "495ec47bf3c1345005f40724f0269362c8556cbc43aed0526ed44cae1d35fceb" [[package]] name = "winapi" @@ -2747,11 +2880,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -2888,9 +3021,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.26" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] @@ -2926,22 +3059,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.30" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.30" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fd7dffe..a31a9da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "chorus" description = "A library for interacting with multiple Spacebar-compatible Instances at once." -version = "0.13.0" -license = "AGPL-3.0" +version = "0.15.0" +license = "MPL-2.0" edition = "2021" repository = "https://github.com/polyphony-chat/chorus" readme = "README.md" keywords = ["spacebar", "discord", "polyphony"] website = ["https://discord.com/invite/m3FpcapGDD"] +rust-version = "1.67.1" [features] @@ -16,34 +17,34 @@ backend = ["dep:poem", "dep:sqlx"] rt-multi-thread = ["tokio/rt-multi-thread"] rt = ["tokio/rt"] client = [] +voice = ["voice_udp", "voice_gateway"] +voice_udp = ["dep:discortp", "dep:crypto_secretbox"] +voice_gateway = [] [dependencies] -tokio = { version = "1.34.0", features = ["macros", "sync"] } -serde = { version = "1.0.188", features = ["derive", "rc"] } -serde_json = { version = "1.0.105", features = ["raw_value"] } -serde-aux = "4.2.0" -serde_with = "3.3.0" -serde_repr = "0.1.16" -reqwest = { git = "https://github.com/bitfl0wer/reqwest.git", branch = "wasm-headers", features = [ - "multipart", - "json", -], version = "0.11.22" } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22 -url = "2.4.0" -chrono = { version = "0.4.26", features = ["serde"] } -regex = "1.9.4" +tokio = { version = "1.35.1", features = ["macros", "sync"] } +serde = { version = "1.0.195", features = ["derive", "rc"] } +serde_json = { version = "1.0.111", features = ["raw_value"] } +serde-aux = "4.3.1" +serde_with = "3.4.0" +serde_repr = "0.1.18" +reqwest = { features = ["multipart", "json"], version = "0.11.23" } +url = "2.5.0" +chrono = { version = "0.4.31", features = ["serde"] } +regex = "1.10.2" custom_error = "1.9.2" -futures-util = "0.3.28" -http = "0.2.9" -base64 = "0.21.3" -bitflags = { version = "2.4.0", features = ["serde"] } +futures-util = "0.3.30" +http = "0.2.11" +base64 = "0.21.7" +bitflags = { version = "2.4.1", features = ["serde"] } lazy_static = "1.4.0" -poem = { version = "1.3.57", optional = true } -thiserror = "1.0.47" +poem = { version = "1.3.59", optional = true } +thiserror = "1.0.56" jsonwebtoken = "8.3.0" log = "0.4.20" -async-trait = "0.1.73" -chorus-macros = "0.2.0" -sqlx = { version = "0.7.1", features = [ +async-trait = "0.1.77" +chorus-macros = "0.3.0" +sqlx = { version = "0.7.3", features = [ "mysql", "sqlite", "json", @@ -52,11 +53,12 @@ sqlx = { version = "0.7.1", features = [ "runtime-tokio-native-tls", "any", ], optional = true } -safina-timer = "0.1.11" +discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] } +crypto_secretbox = { version = "0.1.1", optional = true } rand = "0.8.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -rustls = "0.21.8" +rustls = "0.21.10" rustls-native-certs = "0.6.3" tokio-tungstenite = { version = "0.20.1", features = [ "rustls-tls-native-roots", @@ -64,13 +66,15 @@ tokio-tungstenite = { version = "0.20.1", features = [ ] } native-tls = "0.2.11" hostname = "0.3.1" +getrandom = { version = "0.2.12" } [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2.11", features = ["js"] } +getrandom = { version = "0.2.12", features = ["js"] } ws_stream_wasm = "0.7.4" -wasm-bindgen-futures = "0.4.38" +wasm-bindgen-futures = "0.4.39" +wasmtimer = "0.2.0" [dev-dependencies] lazy_static = "1.4.0" -wasm-bindgen-test = "0.3.38" -wasm-bindgen = "0.2.88" +wasm-bindgen-test = "0.3.39" +wasm-bindgen = "0.2.89" diff --git a/LICENSE b/LICENSE index 0ad25db..a612ad9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,661 +1,373 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index c02da77..dc49230 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ To get started with Chorus, import it into your project by adding the following ```toml [dependencies] -chorus = "0.13.0" +chorus = "0.15.0" ``` ### Establishing a Connection @@ -53,16 +53,10 @@ To connect to a Spacebar compatible server, you need to create an [`Instance`](h ```rs use chorus::instance::Instance; -use chorus::UrlBundle; #[tokio::main] async fn main() { - let bundle = UrlBundle::new( - "https://example.com/api".to_string(), - "wss://example.com/".to_string(), - "https://example.com/cdn".to_string(), - ); - let instance = Instance::new(bundle) + let instance = Instance::new("https://example.com") .await .expect("Failed to connect to the Spacebar server"); // You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique. @@ -87,7 +81,7 @@ let login_schema = LoginSchema { password: "Correct-Horse-Battery-Staple".to_string(), ..Default::default() }; -// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on +// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // the runtime feature you choose, this can potentially take advantage of all of your computers' threads. let user = instance .login_account(login_schema) @@ -125,7 +119,7 @@ like "proxy connection checking" are already disabled on this version, which oth ### wasm To test for wasm, you will need to `cargo install wasm-pack`. You can then run -`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features` +`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client, voice_gateway" --no-default-features` to run the tests for wasm. ## Versioning @@ -134,11 +128,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read ## Contributing -Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement, -ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401). - -If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility. -Please feel free to open an Issue with the idea you have, or a Pull Request. Please keep our [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) in mind. Your contribution might not be accepted if it violates these guidelines or [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md). +See [CONTRIBUTING.md](./CONTRIBUTING.md).
Progress Tracker/Roadmap diff --git a/chorus-macros/Cargo.lock b/chorus-macros/Cargo.lock index 17404a2..9f14619 100644 --- a/chorus-macros/Cargo.lock +++ b/chorus-macros/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ [[package]] name = "chorus-macros" -version = "0.1.0" +version = "0.2.1" dependencies = [ "async-trait", "quote", diff --git a/chorus-macros/Cargo.toml b/chorus-macros/Cargo.toml index 272d99f..530ad7e 100644 --- a/chorus-macros/Cargo.toml +++ b/chorus-macros/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "chorus-macros" -version = "0.2.0" +version = "0.3.0" edition = "2021" -license = "AGPL-3.0" +license = "MPL-2.0" description = "Macros for the chorus crate." [lib] diff --git a/chorus-macros/src/lib.rs b/chorus-macros/src/lib.rs index fd68df0..436fd68 100644 --- a/chorus-macros/src/lib.rs +++ b/chorus-macros/src/lib.rs @@ -1,7 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed}; +#[proc_macro_derive(WebSocketEvent)] +pub fn websocket_event_macro_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + + quote! { + impl WebSocketEvent for #name {} + } + .into() +} + #[proc_macro_derive(Updateable)] pub fn updateable_macro_derive(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(input).unwrap(); diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index a13c935..0f15759 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,3 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This example showcase how to properly use gateway observers. +// +// To properly run it, you will need to change the token below. + +const TOKEN: &str = ""; + +/// Find the gateway websocket url of the server we want to connect to +const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; + use async_trait::async_trait; use chorus::gateway::Gateway; use chorus::{ @@ -8,6 +21,11 @@ use chorus::{ use std::{sync::Arc, time::Duration}; use tokio::{self}; +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep; + // This example creates a simple gateway connection and a basic observer struct // Due to certain limitations all observers must impl debug @@ -27,11 +45,10 @@ impl Observer for ExampleObserver { #[tokio::main(flavor = "current_thread")] async fn main() { - // Find the gateway websocket url of the server we want to connect to - let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); + let gateway_websocket_url = GATEWAY_URL.to_string(); // Initiate the gateway connection - let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; @@ -50,14 +67,13 @@ async fn main() { .subscribe(shared_observer); // Authenticate so we will receive any events - let token = "SecretToken".to_string(); + let token = TOKEN.to_string(); let mut identify = GatewayIdentifyPayload::common(); identify.token = token; gateway.send_identify(identify).await; - safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - safina_timer::sleep_for(Duration::MAX).await + sleep(Duration::from_secs(3600)).await; } } diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 2996283..e8ff59a 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,21 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This example showcases how to initiate a gateway connection manually +// (e. g. not through ChorusUser) +// +// To properly run it, you will need to modify the token below. + +const TOKEN: &str = ""; + +/// Find the gateway websocket url of the server we want to connect to +const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; + use std::time::Duration; use chorus::gateway::Gateway; use chorus::{self, types::GatewayIdentifyPayload}; +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep; + /// This example creates a simple gateway connection and a session with an Identify event #[tokio::main(flavor = "current_thread")] async fn main() { - // Find the gateway websocket url of the server we want to connect to - let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); + let gateway_websocket_url = GATEWAY_URL.to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated // Get a token for an account on the server - let token = "SecretToken".to_string(); + let token = TOKEN.to_string(); // Create an identify event // An Identify event is how the server authenticates us and gets info about our os and browser, along with our intents / capabilities @@ -26,10 +44,10 @@ async fn main() { identify.token = token; // Send off the event - safina_timer::start_timer_thread(); + gateway.send_identify(identify).await; // Do something on the main thread so we don't quit loop { - safina_timer::sleep_for(Duration::MAX).await + sleep(Duration::from_secs(3600)).await; } } diff --git a/examples/instance.rs b/examples/instance.rs index b8f8518..0bbdc17 100644 --- a/examples/instance.rs +++ b/examples/instance.rs @@ -1,14 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::instance::Instance; -use chorus::UrlBundle; #[tokio::main(flavor = "current_thread")] async fn main() { - let bundle = UrlBundle::new( - "https://example.com/api".to_string(), - "wss://example.com/".to_string(), - "https://example.com/cdn".to_string(), - ); - let instance = Instance::new(bundle) + let instance = Instance::new("https://example.com/") .await .expect("Failed to connect to the Spacebar server"); dbg!(instance.instance_info); diff --git a/examples/login.rs b/examples/login.rs index 18b5db4..e89d8d2 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,15 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::instance::Instance; use chorus::types::LoginSchema; -use chorus::UrlBundle; #[tokio::main(flavor = "current_thread")] async fn main() { - let bundle = UrlBundle::new( - "https://example.com/api".to_string(), - "wss://example.com/".to_string(), - "https://example.com/cdn".to_string(), - ); - let mut instance = Instance::new(bundle) + let mut instance = Instance::new("https://example.com/") .await .expect("Failed to connect to the Spacebar server"); // Assume, you already have an account created on this instance. Registering an account works @@ -19,7 +17,7 @@ async fn main() { password: "Correct-Horse-Battery-Staple".to_string(), ..Default::default() }; - // Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on + // Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // the runtime feature you choose, this can potentially take advantage of all of your computers' threads. let user = instance .login_account(login_schema) diff --git a/examples/voice_simple/Cargo.toml b/examples/voice_simple/Cargo.toml new file mode 100644 index 0000000..dc488e6 --- /dev/null +++ b/examples/voice_simple/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "voice_simple" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "*" +chorus = { path = "../../", features = ["rt", "client", "voice"] } +tokio = { version = "*", features = ["full"] } +simplelog = "*" +log = "*" diff --git a/examples/voice_simple/src/main.rs b/examples/voice_simple/src/main.rs new file mode 100644 index 0000000..a6f77db --- /dev/null +++ b/examples/voice_simple/src/main.rs @@ -0,0 +1,311 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This example showcases how to use the voice udp channel. +// +// To use this to properly communicate with voice, you will need to bring your own opus bindings +// along with potentially sending some other events, like Speaking +// +// To properly run this example, you will need to change some values below, +// like the token, guild and channel ids. + +const TOKEN: &str = ""; + +const VOICE_GUILD_ID: Option = None; +const VOICE_CHANNEL_ID: Option = Some(Snowflake(0_u64)); + +const GATEWAY_URL: &str = "wss://gateway.discord.gg"; + +use async_trait::async_trait; +use simplelog::{TermLogger, Config, WriteLogger}; +use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration}; + +use chorus::{ + gateway::{Observer, Gateway}, + types::{ + GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking, + SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol, + VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState, + }, + voice::{ + gateway::{VoiceGateway, VoiceGatewayHandle}, + udp::{UdpHandle, UdpHandler}, + voice_data::VoiceData, + }, +}; +use log::{info, LevelFilter}; +use tokio::sync::{Mutex, RwLock}; + +extern crate chorus; +extern crate tokio; + +/// Handles in between connections between the gateway and UDP modules +#[derive(Debug, Clone)] +pub struct VoiceHandler { + pub voice_gateway_connection: Arc>>, + pub voice_udp_connection: Arc>>, + pub data: Arc>, +} + +impl VoiceHandler { + /// Creates a new [VoiceHandler], only initializing the data + pub fn new() -> VoiceHandler { + Self { + data: Arc::new(RwLock::new(VoiceData::default())), + voice_gateway_connection: Arc::new(Mutex::new(None)), + voice_udp_connection: Arc::new(Mutex::new(None)), + } + } +} + +impl Default for VoiceHandler { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +// On [VoiceServerUpdate] we get our starting data and URL for the voice gateway server. +impl Observer for VoiceHandler { + async fn update(&self, data: &VoiceServerUpdate) { + let mut data_lock = self.data.write().await; + + data_lock.server_data = Some(data.clone()); + let user_id = data_lock.user_id; + let session_id = data_lock.session_id.clone(); + + drop(data_lock); + + // Create and connect to the voice gateway + let voice_gateway_handle = VoiceGateway::spawn(data.endpoint.clone().unwrap()) + .await + .unwrap(); + + let server_id: Snowflake; + + if data.guild_id.is_some() { + server_id = data.guild_id.unwrap(); + } else { + server_id = data.channel_id.unwrap(); + } + + let voice_identify = VoiceIdentify { + server_id, + user_id, + session_id, + token: data.token.clone(), + video: Some(false), + }; + + voice_gateway_handle.send_identify(voice_identify).await; + + let cloned_gateway_handle = voice_gateway_handle.clone(); + + let mut voice_events = cloned_gateway_handle.events.lock().await; + + let self_reference = Arc::new(self.clone()); + + // Subscribe to voice gateway events + voice_events.voice_ready.subscribe(self_reference.clone()); + voice_events + .session_description + .subscribe(self_reference.clone()); + voice_events.speaking.subscribe(self_reference.clone()); + voice_events + .ssrc_definition + .subscribe(self_reference.clone()); + + *self.voice_gateway_connection.lock().await = Some(voice_gateway_handle); + } +} + +#[async_trait] +// On [VoiceReady] we get info for establishing a UDP connection, and we immediately need said UDP +// connection for ip discovery. +impl Observer for VoiceHandler { + async fn update(&self, data: &VoiceReady) { + let mut data_lock = self.data.write().await; + + data_lock.ready_data = Some(data.clone()); + + drop(data_lock); + + // Create a udp connection and perform ip discovery + let udp_handle = UdpHandler::spawn( + self.data.clone(), + std::net::SocketAddr::V4(SocketAddrV4::new(data.ip, data.port)), + data.ssrc, + ) + .await + .unwrap(); + + // Subscribe ourself to receiving rtp data + udp_handle + .events + .lock() + .await + .rtp + .subscribe(Arc::new(self.clone())); + + let ip_discovery = self.data.read().await.ip_discovery.clone().unwrap(); + + *self.voice_udp_connection.lock().await = Some(udp_handle.clone()); + + let string_ip_address = + String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip"); + + // Send a select protocol, which tells the server where we'll be receiving data and what + // mode to encrypt data in + self.voice_gateway_connection + .lock() + .await + .clone() + .unwrap() + .send_select_protocol(SelectProtocol { + protocol: VoiceProtocol::Udp, + data: SelectProtocolData { + address: string_ip_address, + port: ip_discovery.port, + // There are several other voice encryption modes available, though not all are + // implemented in chorus + mode: VoiceEncryptionMode::Xsalsa20Poly1305, + }, + ..Default::default() + }) + .await; + } +} + +#[async_trait] +// Session descryption gives us final info regarding codecs and our encryption key +impl Observer for VoiceHandler { + async fn update(&self, data: &SessionDescription) { + let mut data_write = self.data.write().await; + + data_write.session_description = Some(data.clone()); + + drop(data_write); + } +} + +#[async_trait] +// Ready is used just to obtain some info, like the user id and session id +impl Observer for VoiceHandler { + async fn update(&self, data: &GatewayReady) { + let mut lock = self.data.write().await; + lock.user_id = data.user.id; + lock.session_id = data.session_id.clone(); + drop(lock); + } +} + +#[async_trait] +// This is the received voice data +impl Observer for VoiceHandler { + async fn update(&self, data: &chorus::voice::discortp::rtp::Rtp) { + info!( + "Received decrypted voice data! {:?} (SSRC: {})", + data.payload.clone(), + data.ssrc, + ); + } +} + +#[async_trait] +// This event gives extra info about who is speaking +impl Observer for VoiceHandler { + async fn update(&self, data: &Speaking) { + println!( + "Received Speaking! (SRRC: {}, flags: {:?})", + data.ssrc, + SpeakingBitflags::from_bits(data.speaking).unwrap() + ); + } +} + +#[async_trait] +// This event gives some info about which user has which ssrc +impl Observer for VoiceHandler { + async fn update(&self, data: &SsrcDefinition) { + println!( + "Received SSRC Definition! (User {} has audio ssrc {})", + data.user_id.unwrap(), + data.audio_ssrc + ); + } +} + +#[tokio::main] +async fn main() { + simplelog::CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Debug, + Config::default(), + simplelog::TerminalMode::Mixed, + simplelog::ColorChoice::Auto, + ), + WriteLogger::new( + LevelFilter::Trace, + Config::default(), + File::create("latest.log").unwrap(), + ), + ]) + .unwrap(); + + let gateway = Gateway::spawn(GATEWAY_URL.to_string()) + .await + .unwrap(); + + let mut identify = GatewayIdentifyPayload::common(); + identify.token = TOKEN.to_string(); + + gateway.send_identify(identify).await; + + let voice_handler = Arc::new(VoiceHandler::new()); + + // Voice handler needs voice server update + gateway + .events + .lock() + .await + .voice + .server_update + .subscribe(voice_handler.clone()); + + // It also needs a bit of the data in ready + gateway + .events + .lock() + .await + .session + .ready + .subscribe(voice_handler.clone()); + + // Data which channel to update the local user to be joined into. + // + // guild_id and channel_id can be some to join guild voice channels + // + // guild_id can be none and channel id some to join dm calls + // + // both can be none to leave all voice channels + let voice_state_update = UpdateVoiceState { + guild_id: VOICE_GUILD_ID, + channel_id: VOICE_CHANNEL_ID, + self_mute: false, + self_deaf: false, + ..Default::default() + }; + + gateway.send_update_voice_state(voice_state_update).await; + + loop { + tokio::time::sleep(Duration::from_millis(1000)).await; + + // Potentially send some data here + /*let voice_udp_option = voice_handler.voice_udp_connection.lock().await.clone(); + if voice_udp_option.is_some() { + voice_udp_option.unwrap().send_opus_data(0, vec![1, 2, 3, 4, 5]).await.unwrap(); + }*/ + } +} diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index ff99be8..7689af7 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use reqwest::Client; diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index ae3b219..5bd539f 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -1,6 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; +#[allow(unused_imports)] pub use login::*; + +#[allow(unused_imports)] pub use register::*; use crate::gateway::Gateway; diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index aa0b483..deece4d 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use reqwest::Client; diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index 9560d74..6c41576 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 6dfdfbf..feabc37 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use http::header::CONTENT_DISPOSITION; use http::HeaderMap; use reqwest::{multipart, Client}; @@ -36,7 +40,7 @@ impl Message { 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); + attachment.get_mut(index).unwrap().id = Some(index as i16); } let mut form = reqwest::multipart::Form::new(); let payload_json = to_string(&message).unwrap(); @@ -45,8 +49,8 @@ impl Message { form = form.part("payload_json", payload_field); 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 attachment_content = attachment.content; + let attachment_filename = attachment.filename; let part_name = format!("files[{}]", index); let content_disposition = format!( "form-data; name=\"{}\"'; filename=\"{}\"", diff --git a/src/api/channels/mod.rs b/src/api/channels/mod.rs index b72861e..a8c3047 100644 --- a/src/api/channels/mod.rs +++ b/src/api/channels/mod.rs @@ -1,3 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused_imports)] pub use channels::*; pub use messages::*; pub use permissions::*; diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs index 5360890..03465b8 100644 --- a/src/api/channels/permissions.rs +++ b/src/api/channels/permissions.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index 6be95d0..a149d71 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::{ errors::ChorusResult, instance::ChorusUser, diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index 47b3fdd..b1a579b 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::from_str; use serde_json::to_string; @@ -14,6 +18,25 @@ use crate::types::{ use crate::types::{GuildBan, Snowflake}; impl Guild { + /// Fetches a guild by its id. + /// + /// # Reference + /// See + pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/guilds/{}", + user.belongs_to.read().unwrap().urls.api, + guild_id + )) + .header("Authorization", user.token()), + limit_type: LimitType::Guild(guild_id), + }; + let response = chorus_request.deserialize_response::(user).await?; + Ok(response) + } + /// Creates a new guild. /// /// # Reference @@ -34,6 +57,35 @@ impl Guild { chorus_request.deserialize_response::(user).await } + /// Modify a guild's settings. + /// + /// Requires the [MANAGE_GUILD](crate::types::PermissionFlags::MANAGE_GUILD) permission. + /// + /// Returns the updated guild. + /// + /// # Reference + /// + pub async fn modify( + guild_id: Snowflake, + schema: GuildModifySchema, + user: &mut ChorusUser, + ) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .patch(format!( + "{}/guilds/{}", + user.belongs_to.read().unwrap().urls.api, + guild_id, + )) + .header("Authorization", user.token()) + .header("Content-Type", "application/json") + .body(to_string(&schema).unwrap()), + limit_type: LimitType::Guild(guild_id), + }; + let response = chorus_request.deserialize_response::(user).await?; + Ok(response) + } + /// Deletes a guild by its id. /// /// User must be the owner. @@ -123,77 +175,9 @@ impl Guild { }; } - /// Fetches a guild by its id. - /// - /// # Reference - /// See - pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult { - let chorus_request = ChorusRequest { - request: Client::new() - .get(format!( - "{}/guilds/{}", - user.belongs_to.read().unwrap().urls.api, - guild_id - )) - .header("Authorization", user.token()), - limit_type: LimitType::Guild(guild_id), - }; - let response = chorus_request.deserialize_response::(user).await?; - Ok(response) - } - - pub async fn create_ban( - guild_id: Snowflake, - user_id: Snowflake, - audit_log_reason: Option, - schema: GuildBanCreateSchema, - user: &mut ChorusUser, - ) -> ChorusResult<()> { - // FIXME: Return GuildBan instead of (). Requires to be resolved. - let request = ChorusRequest::new( - http::Method::PUT, - format!( - "{}/guilds/{}/bans/{}", - user.belongs_to.read().unwrap().urls.api, - guild_id, - user_id - ) - .as_str(), - Some(to_string(&schema).unwrap()), - audit_log_reason.as_deref(), - None, - Some(user), - LimitType::Guild(guild_id), - ); - request.handle_request_as_result(user).await - } - - /// # Reference - /// - pub async fn modify( - guild_id: Snowflake, - schema: GuildModifySchema, - user: &mut ChorusUser, - ) -> ChorusResult { - let chorus_request = ChorusRequest { - request: Client::new() - .patch(format!( - "{}/guilds/{}", - user.belongs_to.read().unwrap().urls.api, - guild_id, - )) - .header("Authorization", user.token()) - .header("Content-Type", "application/json") - .body(to_string(&schema).unwrap()), - limit_type: LimitType::Guild(guild_id), - }; - let response = chorus_request.deserialize_response::(user).await?; - Ok(response) - } - /// Fetches a guild preview object for the given guild ID. If the user is not in the guild, the guild must be discoverable. - /// # Reference: /// + /// # Reference: /// See // RAGC: These aren't just getters, they fetch something on the server. // I am going to excuse these namings, since .preview() indicates we are just returning @@ -273,7 +257,9 @@ impl Guild { request.deserialize_response::>(user).await } - /// Removes a member from a guild. Requires the KICK_MEMBERS permission. Returns a 204 empty response on success. + /// Removes a member from a guild. + /// + /// Requires the [KICK_MEMBERS](crate::types::PermissionFlags::KICK_MEMBERS) permission. /// /// # Reference /// See @@ -386,7 +372,9 @@ impl Guild { .await } - /// Fetches a list of ban objects for the guild. Requires the `BAN_MEMBERS` permission. + /// Returns a list of ban objects for the guild. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. /// /// # Reference: /// See @@ -416,7 +404,9 @@ impl Guild { request.deserialize_response::>(user).await } - /// Fetches a ban object for the given user. Requires the `BAN_MEMBERS` permission. + /// Returns a ban object for the given user. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. /// /// # Reference: /// See @@ -444,7 +434,39 @@ impl Guild { request.deserialize_response::(user).await } - /// Removes the ban for a user. Requires the BAN_MEMBERS permissions. Returns a 204 empty response on success. + /// Creates a ban from the guild. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. + /// + pub async fn create_ban( + guild_id: Snowflake, + user_id: Snowflake, + audit_log_reason: Option, + schema: GuildBanCreateSchema, + user: &mut ChorusUser, + ) -> ChorusResult<()> { + // FIXME: Return GuildBan instead of (). Requires to be resolved. + let request = ChorusRequest::new( + http::Method::PUT, + format!( + "{}/guilds/{}/bans/{}", + user.belongs_to.read().unwrap().urls.api, + guild_id, + user_id + ) + .as_str(), + Some(to_string(&schema).unwrap()), + audit_log_reason.as_deref(), + None, + Some(user), + LimitType::Guild(guild_id), + ); + request.handle_request_as_result(user).await + } + + /// Removes the ban for a user. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. /// /// # Reference: /// See diff --git a/src/api/guilds/member.rs b/src/api/guilds/member.rs index 885ddf9..0037ec7 100644 --- a/src/api/guilds/member.rs +++ b/src/api/guilds/member.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use crate::{ diff --git a/src/api/guilds/messages.rs b/src/api/guilds/messages.rs index 60fd4e3..b45f3e3 100644 --- a/src/api/guilds/messages.rs +++ b/src/api/guilds/messages.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::errors::ChorusResult; use crate::instance::ChorusUser; use crate::types::{Guild, Message, MessageSearchQuery, Snowflake}; @@ -9,7 +13,7 @@ impl Guild { /// permission to be present on the current user. /// /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. - /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. + /// In this case, the method will return a [`ChorusError::InvalidResponse`](crate::errors::ChorusError::InvalidResponse) error. /// /// # Reference: /// See diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs index f1fa039..e8304a1 100644 --- a/src/api/guilds/mod.rs +++ b/src/api/guilds/mod.rs @@ -1,3 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused_imports)] pub use guilds::*; pub use messages::*; pub use roles::*; diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index f131367..6100a48 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/invites/mod.rs b/src/api/invites/mod.rs index 80b47d2..68f1417 100644 --- a/src/api/invites/mod.rs +++ b/src/api/invites/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/mod.rs b/src/api/mod.rs index ab3f9b9..c9ca279 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! All of the API's endpoints. + +#![allow(unused_imports)] pub use channels::messages::*; pub use guilds::*; pub use invites::*; diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index 4de5fd8..584db33 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde_json::from_str; use crate::errors::{ChorusError, ChorusResult}; diff --git a/src/api/policies/instance/mod.rs b/src/api/policies/instance/mod.rs index b3a9148..e2b97e1 100644 --- a/src/api/policies/instance/mod.rs +++ b/src/api/policies/instance/mod.rs @@ -1,3 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused_imports)] pub use instance::*; pub mod instance; diff --git a/src/api/policies/mod.rs b/src/api/policies/mod.rs index 1d5ea99..377ec30 100644 --- a/src/api/policies/mod.rs +++ b/src/api/policies/mod.rs @@ -1 +1,5 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod instance; diff --git a/src/api/users/channels.rs b/src/api/users/channels.rs index 330b3e3..44ed897 100644 --- a/src/api/users/channels.rs +++ b/src/api/users/channels.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; @@ -9,6 +13,26 @@ use crate::{ }; impl ChorusUser { + /// Fetches a list of private channels the user is in. + /// + /// # Reference: + /// See + pub async fn get_private_channels(&mut self) -> ChorusResult> { + let url = format!( + "{}/users/@me/channels", + self.belongs_to.read().unwrap().urls.api + ); + ChorusRequest { + request: Client::new() + .get(url) + .header("Authorization", self.token()) + .header("Content-Type", "application/json"), + limit_type: LimitType::Global, + } + .deserialize_response::>(self) + .await + } + /// Creates a DM channel or group DM channel. /// /// One recipient creates or returns an existing DM channel, diff --git a/src/api/users/guilds.rs b/src/api/users/guilds.rs index b97687f..ca2fa8e 100644 --- a/src/api/users/guilds.rs +++ b/src/api/users/guilds.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/users/mod.rs b/src/api/users/mod.rs index ba789ba..b11772a 100644 --- a/src/api/users/mod.rs +++ b/src/api/users/mod.rs @@ -1,3 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused_imports)] pub use channels::*; pub use guilds::*; pub use relationships::*; diff --git a/src/api/users/relationships.rs b/src/api/users/relationships.rs index 4f9602c..c77db44 100644 --- a/src/api/users/relationships.rs +++ b/src/api/users/relationships.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; @@ -15,7 +19,7 @@ impl ChorusUser { /// Retrieves a list of mutual friends between the authenticated user and a given user. /// /// # Reference - /// See + /// See pub async fn get_mutual_relationships( &mut self, user_id: Snowflake, @@ -37,7 +41,7 @@ impl ChorusUser { /// Retrieves the user's relationships. /// /// # Reference - /// See + /// See pub async fn get_relationships(&mut self) -> ChorusResult> { let url = format!( "{}/users/@me/relationships", @@ -55,7 +59,7 @@ impl ChorusUser { /// Sends a friend request to a user. /// /// # Reference - /// See + /// See pub async fn send_friend_request( &mut self, schema: FriendRequestSendSchema, @@ -132,7 +136,7 @@ impl ChorusUser { /// Removes the relationship between the authenticated user and a given user. /// /// # Reference - /// See + /// See pub async fn remove_relationship(&mut self, user_id: Snowflake) -> ChorusResult<()> { let url = format!( "{}/users/@me/relationships/{}", diff --git a/src/api/users/users.rs b/src/api/users/users.rs index ddb068d..9d489da 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use reqwest::Client; @@ -113,7 +117,7 @@ impl User { /// Fetches the user's settings. /// /// # Reference - /// See + /// See pub async fn get_settings( token: &String, url_api: &String, diff --git a/src/errors.rs b/src/errors.rs index c20ac64..722921a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,7 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Contains all the errors that can be returned by the library. use custom_error::custom_error; use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; custom_error! { #[derive(PartialEq, Eq, Clone, Hash)] @@ -17,7 +22,7 @@ custom_error! { /// Server did not respond. NoResponse = "Did not receive a response from the Server.", /// Reqwest returned an Error instead of a Response object. - RequestFailed{url:String, error: String} = "An error occured while trying to GET from {url}: {error}", + RequestFailed{url:String, error: String} = "An error occurred 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. @@ -63,12 +68,12 @@ custom_error! { } custom_error! { - /// For errors we receive from the gateway, see https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#gateway-close-event-codes; + /// For errors we receive from the gateway, see ; /// /// Supposed to be sent as numbers, though they are sent as string most of the time? /// /// Also includes errors when initiating a connection and unexpected opcodes - #[derive(PartialEq, Eq, Default, Clone)] + #[derive(PartialEq, Eq, Default, Clone, WebSocketEvent)] pub GatewayError // Errors we have received from the gateway #[default] @@ -95,4 +100,55 @@ custom_error! { UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", } -impl WebSocketEvent for GatewayError {} +custom_error! { + /// Voice Gateway errors + /// + /// Similar to [GatewayError]. + /// + /// See ; + #[derive(Clone, Default, PartialEq, Eq, WebSocketEvent)] + pub VoiceGatewayError + // Errors we receive + #[default] + UnknownOpcode = "You sent an invalid opcode", + FailedToDecodePayload = "You sent an invalid payload in your identifying to the (Voice) Gateway", + NotAuthenticated = "You sent a payload before identifying with the (Voice) Gateway", + AuthenticationFailed = "The token you sent in your identify payload is incorrect", + AlreadyAuthenticated = "You sent more than one identify payload", + SessionNoLongerValid = "Your session is no longer valid", + SessionTimeout = "Your session has timed out", + ServerNotFound = "We can't find the server you're trying to connect to", + UnknownProtocol = "We didn't recognize the protocol you sent", + Disconnected = "Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.", + VoiceServerCrashed = "The server crashed, try resuming", + UnknownEncryptionMode = "Server failed to decrypt data", + + // Errors when initiating a gateway connection + 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 + UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", +} + +custom_error! { + /// Voice UDP errors. + #[derive(Clone, PartialEq, Eq, WebSocketEvent)] + pub VoiceUdpError + + // General errors + BrokenSocket{error: String} = "Could not write / read from UDP socket: {error}", + NoData = "We have not set received the necessary data to perform this operation.", + + // Encryption errors + EncryptionModeNotImplemented{encryption_mode: String} = "Voice encryption mode {encryption_mode} is not yet implemented.", + NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet", + FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new", + FailedDecryption = "Tried to decrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new", + FailedNonceGeneration{error: String} = "Tried to generate nonce, but failed due to error: {error}.", + + // Errors when initiating a socket connection + CannotBind{error: String} = "Cannot bind socket due to a UDP error: {error}", + CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}", +} + diff --git a/src/gateway/backends/mod.rs b/src/gateway/backends/mod.rs index edb5dc9..6df070d 100644 --- a/src/gateway/backends/mod.rs +++ b/src/gateway/backends/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub mod tungstenite; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] diff --git a/src/gateway/backends/tungstenite.rs b/src/gateway/backends/tungstenite.rs index 5184329..a9f9f64 100644 --- a/src/gateway/backends/tungstenite.rs +++ b/src/gateway/backends/tungstenite.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::{ stream::{SplitSink, SplitStream}, StreamExt, @@ -23,8 +27,16 @@ impl TungsteniteBackend { websocket_url: &str, ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { let mut roots = rustls::RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") - { + let certs = rustls_native_certs::load_native_certs(); + + if let Err(e) = certs { + log::error!("Failed to load platform native certs! {:?}", e); + return Err(GatewayError::CannotConnect { + error: format!("{:?}", e), + }); + } + + for cert in certs.unwrap() { roots.add(&rustls::Certificate(cert.0)).unwrap(); } let (websocket_stream, _) = match connect_async_tls_with_config( diff --git a/src/gateway/backends/wasm.rs b/src/gateway/backends/wasm.rs index e9927ac..83f4b37 100644 --- a/src/gateway/backends/wasm.rs +++ b/src/gateway/backends/wasm.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::{ stream::{SplitSink, SplitStream}, StreamExt, diff --git a/src/gateway/events.rs b/src/gateway/events.rs index fdb7b25..8d38cca 100644 --- a/src/gateway/events.rs +++ b/src/gateway/events.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use super::*; use crate::types; @@ -42,6 +46,8 @@ pub struct Session { pub ready: GatewayEvent, pub ready_supplemental: GatewayEvent, pub replace: GatewayEvent, + pub reconnect: GatewayEvent, + pub invalid: GatewayEvent, } #[derive(Default, Debug)] diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 684b9d2..dabfeb6 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::time::Duration; use futures_util::{SinkExt, StreamExt}; @@ -5,13 +9,14 @@ use log::*; #[cfg(not(target_arch = "wasm32"))] use tokio::task; -use self::event::Events; +use super::events::Events; use super::*; use super::{Sink, Stream}; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, - ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, - ThreadUpdate, UpdateMessage, WebSocketEvent, + ChannelUpdate, GatewayInvalidSession, GatewayReconnect, Guild, GuildRoleCreate, + GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, ThreadUpdate, UpdateMessage, + WebSocketEvent, }; #[derive(Debug)] @@ -21,6 +26,7 @@ pub struct Gateway { websocket_send: Arc>, websocket_receive: Stream, kill_send: tokio::sync::broadcast::Sender<()>, + kill_receive: tokio::sync::broadcast::Receiver<()>, store: Arc>>>>, url: String, } @@ -70,6 +76,7 @@ impl Gateway { websocket_send: shared_websocket_send.clone(), websocket_receive, kill_send: kill_send.clone(), + kill_receive: kill_send.subscribe(), store: store.clone(), url: websocket_url.clone(), }; @@ -94,14 +101,21 @@ impl Gateway { } /// The main gateway listener task; - /// - /// Can only be stopped by closing the websocket, cannot be made to listen for kill pub async fn gateway_listen_task(&mut self) { loop { - let msg = self.websocket_receive.next().await; + let msg; + + tokio::select! { + Ok(_) = self.kill_receive.recv() => { + log::trace!("GW: Closing listener task"); + break; + } + message = self.websocket_receive.next() => { + msg = message; + } + } // PRETTYFYME: Remove inline conditional compiling - // This if chain can be much better but if let is unstable on stable rust #[cfg(not(target_arch = "wasm32"))] if let Some(Ok(message)) = msg { self.handle_message(message.into()).await; @@ -335,10 +349,42 @@ impl Gateway { .unwrap(); } GATEWAY_RECONNECT => { - todo!() + trace!("GW: Received Reconnect"); + + let reconnect = GatewayReconnect {}; + + self.events + .lock() + .await + .session + .reconnect + .notify(reconnect) + .await; } GATEWAY_INVALID_SESSION => { - todo!() + trace!("GW: Received Invalid Session"); + + let mut resumable: bool = false; + + if let Some(raw_value) = gateway_payload.event_data { + if let Ok(deserialized) = serde_json::from_str(raw_value.get()) { + resumable = deserialized; + } else { + warn!("Failed to parse part of INVALID_SESSION ('{}' as bool), assuming non-resumable", raw_value.get()); + } + } else { + warn!("Failed to parse part of INVALID_SESSION ('d' missing), assuming non-resumable"); + } + + let invalid_session = GatewayInvalidSession { resumable }; + + self.events + .lock() + .await + .session + .invalid + .notify(invalid_session) + .await; } // Starts our heartbeat // We should have already handled this in gateway init @@ -394,165 +440,3 @@ impl Gateway { } } } - -pub mod event { - use super::*; - - #[derive(Default, Debug)] - pub struct Events { - pub application: Application, - pub auto_moderation: AutoModeration, - pub session: Session, - pub message: Message, - pub user: User, - pub relationship: Relationship, - pub channel: Channel, - pub thread: Thread, - pub guild: Guild, - pub invite: Invite, - pub integration: Integration, - pub interaction: Interaction, - pub stage_instance: StageInstance, - pub call: Call, - pub voice: Voice, - pub webhooks: Webhooks, - pub gateway_identify_payload: GatewayEvent, - pub gateway_resume: GatewayEvent, - pub error: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Application { - pub command_permissions_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct AutoModeration { - pub rule_create: GatewayEvent, - pub rule_update: GatewayEvent, - pub rule_delete: GatewayEvent, - pub action_execution: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Session { - pub ready: GatewayEvent, - pub ready_supplemental: GatewayEvent, - pub replace: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct StageInstance { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Message { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub delete_bulk: GatewayEvent, - pub reaction_add: GatewayEvent, - pub reaction_remove: GatewayEvent, - pub reaction_remove_all: GatewayEvent, - pub reaction_remove_emoji: GatewayEvent, - pub ack: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct User { - pub update: GatewayEvent, - pub guild_settings_update: GatewayEvent, - pub presence_update: GatewayEvent, - pub typing_start: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Relationship { - pub add: GatewayEvent, - pub remove: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Channel { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub unread_update: GatewayEvent, - pub delete: GatewayEvent, - pub pins_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Thread { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub list_sync: GatewayEvent, - pub member_update: GatewayEvent, - pub members_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Guild { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub audit_log_entry_create: GatewayEvent, - pub ban_add: GatewayEvent, - pub ban_remove: GatewayEvent, - pub emojis_update: GatewayEvent, - pub stickers_update: GatewayEvent, - pub integrations_update: GatewayEvent, - pub member_add: GatewayEvent, - pub member_remove: GatewayEvent, - pub member_update: GatewayEvent, - pub members_chunk: GatewayEvent, - pub role_create: GatewayEvent, - pub role_update: GatewayEvent, - pub role_delete: GatewayEvent, - pub role_scheduled_event_create: GatewayEvent, - pub role_scheduled_event_update: GatewayEvent, - pub role_scheduled_event_delete: GatewayEvent, - pub role_scheduled_event_user_add: GatewayEvent, - pub role_scheduled_event_user_remove: GatewayEvent, - pub passive_update_v1: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Invite { - pub create: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Integration { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Interaction { - pub create: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Call { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Voice { - pub state_update: GatewayEvent, - pub server_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Webhooks { - pub update: GatewayEvent, - } -} diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 620faba..6af5f0d 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -1,9 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::SinkExt; use log::*; use std::fmt::Debug; -use super::{event::Events, *}; +use super::{events::Events, *}; use crate::types::{self, Composite}; /// Represents a handle to a Gateway connection. A Gateway connection will create observable @@ -40,10 +44,19 @@ impl GatewayHandle { .unwrap(); } + /// Recursively observes a [`Shared`] object, by making sure all [`Composite `] fields within + /// that object and its children are being watched. + /// + /// Observing means, that if new information arrives about the observed object or its children, + /// the object automatically gets updated, without you needing to request new information about + /// the object in question from the API, which is expensive and can lead to rate limiting. + /// + /// The [`Shared`] object returned by this method points to a different object than the one + /// being supplied as a &self function argument. pub async fn observe>( &self, - object: Arc>, - ) -> Arc> { + object: Shared, + ) -> Shared { let mut store = self.store.lock().await; let id = object.read().unwrap().id(); if let Some(channel) = store.get(&id) { @@ -84,7 +97,7 @@ impl GatewayHandle { /// with all of its observable fields being observed. pub async fn observe_and_into_inner>( &self, - object: Arc>, + object: Shared, ) -> T { let channel = self.observe(object.clone()).await; let object = channel.read().unwrap().clone(); @@ -160,7 +173,7 @@ impl GatewayHandle { /// Closes the websocket connection and stops all gateway tasks; /// - /// Esentially pulls the plug on the gateway, leaving it possible to resume; + /// Essentially pulls the plug on the gateway, leaving it possible to resume; pub async fn close(&self) { self.kill_send.send(()).unwrap(); self.websocket_send.lock().await.close().await.unwrap(); diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index 1176517..5dcc98d 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -1,9 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::SinkExt; use log::*; -use std::time::{self, Duration, Instant}; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::Instant; +#[cfg(target_arch = "wasm32")] +use wasmtimer::std::Instant; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep_until; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep_until; + +use std::time::Duration; + use tokio::sync::mpsc::{Receiver, Sender}; -use safina_timer::sleep_until; #[cfg(not(target_arch = "wasm32"))] use tokio::task; @@ -11,7 +26,7 @@ use super::*; use crate::types; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; +pub const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used @@ -57,18 +72,11 @@ impl HeartbeatHandler { mut receive: Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); + let mut last_heartbeat_timestamp: Instant = Instant::now(); let mut last_heartbeat_acknowledged = true; let mut last_seq_number: Option = None; - safina_timer::start_timer_thread(); - loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - let timeout = if last_heartbeat_acknowledged { heartbeat_interval } else { @@ -102,6 +110,10 @@ impl HeartbeatHandler { } } } + Ok(_) = kill_receive.recv() => { + log::trace!("GW: Closing heartbeat task"); + break; + } } if should_send { @@ -119,11 +131,11 @@ impl HeartbeatHandler { let send_result = websocket_tx.lock().await.send(msg.into()).await; if send_result.is_err() { // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); + warn!("GW: Couldn't send heartbeat, websocket seems broken"); break; } - last_heartbeat_timestamp = time::Instant::now(); + last_heartbeat_timestamp = Instant::now(); last_heartbeat_acknowledged = false; } } diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 2c12e48..44d912e 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -1,15 +1,19 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types; use super::*; -/// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. +/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. #[derive(Clone, Debug)] pub struct GatewayMessage(pub String); impl GatewayMessage { /// Parses the message as an error; - /// Returns the error if succesfully parsed, None if the message isn't an error + /// Returns the error if successfully parsed, None if the message isn't an error pub fn error(&self) -> Option { // Some error strings have dots on the end, which we don't care about let processed_content = self.0.to_lowercase().replace('.', ""); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 076ed54..5a5881a 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use async_trait::async_trait; pub mod backends; @@ -94,6 +98,12 @@ pub struct GatewayEvent { } impl GatewayEvent { + pub fn new() -> Self { + Self { + observers: Vec::new(), + } + } + /// Returns true if the GatewayEvent is observed by at least one Observer. pub fn is_observed(&self) -> bool { !self.observers.is_empty() @@ -116,9 +126,17 @@ impl GatewayEvent { } /// Notifies the observers of the GatewayEvent. - async fn notify(&self, new_event_data: T) { + pub(crate) async fn notify(&self, new_event_data: T) { for observer in &self.observers { observer.update(&new_event_data).await; } } } + +/// A type alias for [`Arc>`], used to make the public facing API concerned with +/// Composite structs more ergonomic. +/// ## Note +/// +/// While `T` does not have to implement `Composite` to be used with `Shared`, +/// the primary use of `Shared` is with types that implement `Composite`. +pub type Shared = Arc>; diff --git a/src/instance.rs b/src/instance.rs index f88b5d5..1c2091c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Instance and ChorusUser objects. use std::collections::HashMap; @@ -9,7 +13,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayHandle}; +use crate::gateway::{Gateway, GatewayHandle, Shared}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{ @@ -19,6 +23,7 @@ use crate::UrlBundle; #[derive(Debug, Clone, Default, Serialize, Deserialize)] /// 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, @@ -36,8 +41,6 @@ impl PartialEq for Instance { } } -impl Eq for Instance {} - impl std::hash::Hash for Instance { fn hash(&self, state: &mut H) { self.urls.hash(state); @@ -92,8 +95,17 @@ impl PartialEq for LimitsInformation { } impl Instance { - /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`]. - pub async fn new(urls: UrlBundle) -> ChorusResult { + 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 + } + + /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). + /// + /// To create an Instance from one singular url, use [`Instance::new()`]. + pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult { let is_limited: Option = Instance::is_limited(&urls.api).await?; let limit_information; @@ -123,21 +135,12 @@ impl Instance { 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 - } - /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url. - /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. - // RAGC: Can we really call this a conversion? - // Would with_root_url be better? Not really I think, because with is for more details - // Where are this is with.. less? (or rather with other ones) - pub async fn from_root_url(root_url: &str) -> ChorusResult { + /// + /// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`. + pub async fn new(root_url: &str) -> ChorusResult { let urls = UrlBundle::from_root_url(root_url).await?; - Instance::new(urls).await + Instance::from_url_bundle(urls).await } pub async fn is_limited(api_url: &str) -> ChorusResult> { @@ -174,11 +177,11 @@ impl fmt::Display for Token { /// It is used for most authenticated actions on a Spacebar server. /// It also has its own [Gateway] connection. pub struct ChorusUser { - pub belongs_to: Arc>, + pub belongs_to: Shared, pub token: String, pub limits: Option>, - pub settings: Arc>, - pub object: Arc>, + pub settings: Shared, + pub object: Shared, pub gateway: GatewayHandle, } @@ -233,14 +236,14 @@ impl ChorusUser { /// Creates a new [ChorusUser] from existing data. /// /// # Notes - /// This isn't the prefered way to create a ChorusUser. + /// This isn't the preferred way to create a ChorusUser. /// See [Instance::login_account] and [Instance::register_account] instead. pub fn new( - belongs_to: Arc>, + belongs_to: Shared, token: String, limits: Option>, - settings: Arc>, - object: Arc>, + settings: Shared, + object: Shared, gateway: GatewayHandle, ) -> ChorusUser { ChorusUser { @@ -258,7 +261,7 @@ impl ChorusUser { /// 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: Arc>, token: String) -> ChorusUser { + pub(crate) async fn shell(instance: Shared, token: String) -> ChorusUser { let settings = Arc::new(RwLock::new(UserSettings::default())); let object = Arc::new(RwLock::new(User::default())); let wss_url = instance.read().unwrap().urls.wss.clone(); diff --git a/src/lib.rs b/src/lib.rs index 0bddc3a..6ffd8b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + /*! Chorus combines all the required functionalities of a user-centric Spacebar library into one package. The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining @@ -44,7 +48,7 @@ let login_schema = LoginSchema { password: "Correct-Horse-Battery-Staple".to_string(), ..Default::default() }; -// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on +// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // the runtime feature you choose, this can potentially take advantage of all of your computers' threads. let user = instance .login_account(login_schema) @@ -128,7 +132,10 @@ pub mod instance; #[cfg(feature = "client")] pub mod ratelimiter; pub mod types; -#[cfg(feature = "client")] +#[cfg(all( + feature = "client", + any(feature = "voice_udp", feature = "voice_gateway") +))] pub mod voice; #[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -137,6 +144,12 @@ pub mod voice; /// # Notes /// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server pub struct UrlBundle { + /// The root url of an Instance. Usually, this would be the url where `.well-known/spacebar` can + /// be located under. If the instance you are connecting to for some reason does not have a + /// `.well-known` set up (for example, if it is a local/testing instance), you can use the api + /// url as a substitute. + /// Ex: `https://spacebar.chat` + pub root: String, /// The api's url. /// Ex: `https://old.server.spacebar.chat/api` pub api: String, @@ -151,8 +164,9 @@ pub struct UrlBundle { impl UrlBundle { /// Creates a new UrlBundle from the relevant urls. - pub fn new(api: String, wss: String, cdn: String) -> Self { + pub fn new(root: String, api: String, wss: String, cdn: String) -> Self { Self { + root: UrlBundle::parse_url(root), api: UrlBundle::parse_url(api), wss: UrlBundle::parse_url(wss), cdn: UrlBundle::parse_url(cdn), @@ -237,7 +251,12 @@ impl UrlBundle { .json::() .await { - Ok(UrlBundle::new(body.api_endpoint, body.gateway, body.cdn)) + Ok(UrlBundle::new( + url.to_string(), + body.api_endpoint, + body.gateway, + body.cdn, + )) } else { Err(ChorusError::RequestFailed { url: url.to_string(), diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs index 4e7ce6e..10cb711 100644 --- a/src/ratelimiter.rs +++ b/src/ratelimiter.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Ratelimiter and request handling functionality. use std::collections::HashMap; diff --git a/src/types/config/mod.rs b/src/types/config/mod.rs index 4a11c71..4419208 100644 --- a/src/types/config/mod.rs +++ b/src/types/config/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; diff --git a/src/types/config/types/api_configuration.rs b/src/types/config/types/api_configuration.rs index 2d617fe..2543d9c 100644 --- a/src/types/config/types/api_configuration.rs +++ b/src/types/config/types/api_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/types/config/types/cdn_configuration.rs b/src/types/config/types/cdn_configuration.rs index 5c76273..c0045b2 100644 --- a/src/types/config/types/cdn_configuration.rs +++ b/src/types/config/types/cdn_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/defaults_configuration.rs b/src/types/config/types/defaults_configuration.rs index c2b67c2..e6e0867 100644 --- a/src/types/config/types/defaults_configuration.rs +++ b/src/types/config/types/defaults_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults}; diff --git a/src/types/config/types/domains_configuration.rs b/src/types/config/types/domains_configuration.rs index 297b827..6e9218e 100644 --- a/src/types/config/types/domains_configuration.rs +++ b/src/types/config/types/domains_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Clone, Debug)] diff --git a/src/types/config/types/email_configuration.rs b/src/types/config/types/email_configuration.rs index 954f4de..ffeb726 100644 --- a/src/types/config/types/email_configuration.rs +++ b/src/types/config/types/email_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::email::{ diff --git a/src/types/config/types/endpoint_configuration.rs b/src/types/config/types/endpoint_configuration.rs index b484791..0119cb2 100644 --- a/src/types/config/types/endpoint_configuration.rs +++ b/src/types/config/types/endpoint_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/external_tokens_configuration.rs b/src/types/config/types/external_tokens_configuration.rs index f417b2f..44e1313 100644 --- a/src/types/config/types/external_tokens_configuration.rs +++ b/src/types/config/types/external_tokens_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/general_configuration.rs b/src/types/config/types/general_configuration.rs index 450fd52..d49a6f8 100644 --- a/src/types/config/types/general_configuration.rs +++ b/src/types/config/types/general_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/config/types/gif_configuration.rs b/src/types/config/types/gif_configuration.rs index 8644fb4..f0e1358 100644 --- a/src/types/config/types/gif_configuration.rs +++ b/src/types/config/types/gif_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs index 65897ea..af40b30 100644 --- a/src/types/config/types/guild_configuration.rs +++ b/src/types/config/types/guild_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::{Display, Formatter}; #[cfg(feature = "sqlx")] use std::io::Write; diff --git a/src/types/config/types/kafka_configuration.rs b/src/types/config/types/kafka_configuration.rs index 46d10b6..909dc03 100644 --- a/src/types/config/types/kafka_configuration.rs +++ b/src/types/config/types/kafka_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::kafka::KafkaBroker; diff --git a/src/types/config/types/limit_configuration.rs b/src/types/config/types/limit_configuration.rs index 44f888a..f322a30 100644 --- a/src/types/config/types/limit_configuration.rs +++ b/src/types/config/types/limit_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::{ diff --git a/src/types/config/types/login_configuration.rs b/src/types/config/types/login_configuration.rs index a2b1039..83125e0 100644 --- a/src/types/config/types/login_configuration.rs +++ b/src/types/config/types/login_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/metrics_configuration.rs b/src/types/config/types/metrics_configuration.rs index 336ac84..98d3536 100644 --- a/src/types/config/types/metrics_configuration.rs +++ b/src/types/config/types/metrics_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/mod.rs b/src/types/config/types/mod.rs index 6ea2c03..a35b305 100644 --- a/src/types/config/types/mod.rs +++ b/src/types/config/types/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod api_configuration; pub mod cdn_configuration; pub mod defaults_configuration; diff --git a/src/types/config/types/password_reset_configuration.rs b/src/types/config/types/password_reset_configuration.rs index 4dddae9..d1c730e 100644 --- a/src/types/config/types/password_reset_configuration.rs +++ b/src/types/config/types/password_reset_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/rabbit_mq_configuration.rs b/src/types/config/types/rabbit_mq_configuration.rs index 2437055..c073fa0 100644 --- a/src/types/config/types/rabbit_mq_configuration.rs +++ b/src/types/config/types/rabbit_mq_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/region_configuration.rs b/src/types/config/types/region_configuration.rs index 078fa03..9e933a2 100644 --- a/src/types/config/types/region_configuration.rs +++ b/src/types/config/types/region_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::region::Region; diff --git a/src/types/config/types/register_configuration.rs b/src/types/config/types/register_configuration.rs index 4a20824..a4573bf 100644 --- a/src/types/config/types/register_configuration.rs +++ b/src/types/config/types/register_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::register::{ diff --git a/src/types/config/types/security_configuration.rs b/src/types/config/types/security_configuration.rs index caeb72c..0bd190f 100644 --- a/src/types/config/types/security_configuration.rs +++ b/src/types/config/types/security_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use base64::Engine; use rand::Fill; use serde::{Deserialize, Serialize}; diff --git a/src/types/config/types/sentry_configuration.rs b/src/types/config/types/sentry_configuration.rs index 99de4ff..e6e15df 100644 --- a/src/types/config/types/sentry_configuration.rs +++ b/src/types/config/types/sentry_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::ffi::OsString; use serde::{Deserialize, Serialize}; diff --git a/src/types/config/types/subconfigs/client/mod.rs b/src/types/config/types/subconfigs/client/mod.rs index 5d3d304..95aefdc 100644 --- a/src/types/config/types/subconfigs/client/mod.rs +++ b/src/types/config/types/subconfigs/client/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/defaults/guild.rs b/src/types/config/types/subconfigs/defaults/guild.rs index a709f82..8509fe5 100644 --- a/src/types/config/types/subconfigs/defaults/guild.rs +++ b/src/types/config/types/subconfigs/defaults/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel}; diff --git a/src/types/config/types/subconfigs/defaults/mod.rs b/src/types/config/types/subconfigs/defaults/mod.rs index 56d877f..250738f 100644 --- a/src/types/config/types/subconfigs/defaults/mod.rs +++ b/src/types/config/types/subconfigs/defaults/mod.rs @@ -1,2 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod guild; pub mod user; diff --git a/src/types/config/types/subconfigs/defaults/user.rs b/src/types/config/types/subconfigs/defaults/user.rs index 635d6d4..d7dc7b3 100644 --- a/src/types/config/types/subconfigs/defaults/user.rs +++ b/src/types/config/types/subconfigs/defaults/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/mailgun.rs b/src/types/config/types/subconfigs/email/mailgun.rs index 636e462..9f803ad 100644 --- a/src/types/config/types/subconfigs/email/mailgun.rs +++ b/src/types/config/types/subconfigs/email/mailgun.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/mailjet.rs b/src/types/config/types/subconfigs/email/mailjet.rs index 4e505c1..b1c9b42 100644 --- a/src/types/config/types/subconfigs/email/mailjet.rs +++ b/src/types/config/types/subconfigs/email/mailjet.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/mod.rs b/src/types/config/types/subconfigs/email/mod.rs index 21253fd..f3cbbec 100644 --- a/src/types/config/types/subconfigs/email/mod.rs +++ b/src/types/config/types/subconfigs/email/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod mailgun; pub mod mailjet; pub mod sendgrid; diff --git a/src/types/config/types/subconfigs/email/sendgrid.rs b/src/types/config/types/subconfigs/email/sendgrid.rs index 879c719..993a917 100644 --- a/src/types/config/types/subconfigs/email/sendgrid.rs +++ b/src/types/config/types/subconfigs/email/sendgrid.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/smtp.rs b/src/types/config/types/subconfigs/email/smtp.rs index a02c66f..38ade70 100644 --- a/src/types/config/types/subconfigs/email/smtp.rs +++ b/src/types/config/types/subconfigs/email/smtp.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/guild/autojoin.rs b/src/types/config/types/subconfigs/guild/autojoin.rs index fe72c6c..2cb5cff 100644 --- a/src/types/config/types/subconfigs/guild/autojoin.rs +++ b/src/types/config/types/subconfigs/guild/autojoin.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/config/types/subconfigs/guild/discovery.rs b/src/types/config/types/subconfigs/guild/discovery.rs index 1e283b0..50738f1 100644 --- a/src/types/config/types/subconfigs/guild/discovery.rs +++ b/src/types/config/types/subconfigs/guild/discovery.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/guild/mod.rs b/src/types/config/types/subconfigs/guild/mod.rs index e4d7dcf..f180356 100644 --- a/src/types/config/types/subconfigs/guild/mod.rs +++ b/src/types/config/types/subconfigs/guild/mod.rs @@ -1,2 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod autojoin; pub mod discovery; diff --git a/src/types/config/types/subconfigs/kafka/mod.rs b/src/types/config/types/subconfigs/kafka/mod.rs index 1ee4015..459cfd8 100644 --- a/src/types/config/types/subconfigs/kafka/mod.rs +++ b/src/types/config/types/subconfigs/kafka/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/channel.rs b/src/types/config/types/subconfigs/limits/channel.rs index 03e46e5..2415726 100644 --- a/src/types/config/types/subconfigs/limits/channel.rs +++ b/src/types/config/types/subconfigs/limits/channel.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/global.rs b/src/types/config/types/subconfigs/limits/global.rs index 87f9e1c..0140447 100644 --- a/src/types/config/types/subconfigs/limits/global.rs +++ b/src/types/config/types/subconfigs/limits/global.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/guild.rs b/src/types/config/types/subconfigs/limits/guild.rs index 6def5a0..9ef8f90 100644 --- a/src/types/config/types/subconfigs/limits/guild.rs +++ b/src/types/config/types/subconfigs/limits/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/message.rs b/src/types/config/types/subconfigs/limits/message.rs index 9d368b9..3beb76e 100644 --- a/src/types/config/types/subconfigs/limits/message.rs +++ b/src/types/config/types/subconfigs/limits/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/mod.rs b/src/types/config/types/subconfigs/limits/mod.rs index 4dbc2fa..549e909 100644 --- a/src/types/config/types/subconfigs/limits/mod.rs +++ b/src/types/config/types/subconfigs/limits/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod channel; pub mod global; pub mod guild; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs index 9815a5d..78f1908 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs index 934b922..501afe9 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; pub mod auth; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/route.rs b/src/types/config/types/subconfigs/limits/ratelimits/route.rs index 1aa0be2..7c70fdc 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/route.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/route.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::ratelimits::{ diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs index 642dcc8..4c9b9a1 100644 --- a/src/types/config/types/subconfigs/limits/rates.rs +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::collections::HashMap; use serde::{Deserialize, Serialize}; diff --git a/src/types/config/types/subconfigs/limits/user.rs b/src/types/config/types/subconfigs/limits/user.rs index e43b746..473535a 100644 --- a/src/types/config/types/subconfigs/limits/user.rs +++ b/src/types/config/types/subconfigs/limits/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/mod.rs b/src/types/config/types/subconfigs/mod.rs index 4c85096..4366dc4 100644 --- a/src/types/config/types/subconfigs/mod.rs +++ b/src/types/config/types/subconfigs/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod client; pub mod defaults; pub mod email; diff --git a/src/types/config/types/subconfigs/region/mod.rs b/src/types/config/types/subconfigs/region/mod.rs index 08c3b73..1661c09 100644 --- a/src/types/config/types/subconfigs/region/mod.rs +++ b/src/types/config/types/subconfigs/region/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/register/date_of_birth.rs b/src/types/config/types/subconfigs/register/date_of_birth.rs index 9c1bec1..6689297 100644 --- a/src/types/config/types/subconfigs/register/date_of_birth.rs +++ b/src/types/config/types/subconfigs/register/date_of_birth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/register/email.rs b/src/types/config/types/subconfigs/register/email.rs index 8688cd2..f2aea63 100644 --- a/src/types/config/types/subconfigs/register/email.rs +++ b/src/types/config/types/subconfigs/register/email.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/register/mod.rs b/src/types/config/types/subconfigs/register/mod.rs index a2d714e..9a873ae 100644 --- a/src/types/config/types/subconfigs/register/mod.rs +++ b/src/types/config/types/subconfigs/register/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use date_of_birth::DateOfBirthConfiguration; pub use email::RegistrationEmailConfiguration; pub use password::PasswordConfiguration; diff --git a/src/types/config/types/subconfigs/register/password.rs b/src/types/config/types/subconfigs/register/password.rs index 9247f7d..1d380ba 100644 --- a/src/types/config/types/subconfigs/register/password.rs +++ b/src/types/config/types/subconfigs/register/password.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/security/captcha.rs b/src/types/config/types/subconfigs/security/captcha.rs index 82bb517..c97b6e2 100644 --- a/src/types/config/types/subconfigs/security/captcha.rs +++ b/src/types/config/types/subconfigs/security/captcha.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/security/mod.rs b/src/types/config/types/subconfigs/security/mod.rs index 8455000..26146cd 100644 --- a/src/types/config/types/subconfigs/security/mod.rs +++ b/src/types/config/types/subconfigs/security/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use captcha::{CaptchaConfiguration, CaptchaService}; pub use twofactor::TwoFactorConfiguration; diff --git a/src/types/config/types/subconfigs/security/twofactor.rs b/src/types/config/types/subconfigs/security/twofactor.rs index 39a0373..1182a45 100644 --- a/src/types/config/types/subconfigs/security/twofactor.rs +++ b/src/types/config/types/subconfigs/security/twofactor.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/template_configuration.rs b/src/types/config/types/template_configuration.rs index 932670e..9f370b4 100644 --- a/src/types/config/types/template_configuration.rs +++ b/src/types/config/types/template_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs index 0b55626..4014e55 100644 --- a/src/types/entities/application.rs +++ b/src/types/entities/application.rs @@ -1,10 +1,13 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use bitflags::bitflags; use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::utils::Snowflake; use crate::types::{Team, User}; @@ -27,7 +30,7 @@ pub struct Application { pub bot_require_code_grant: bool, pub verify_key: String, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub owner: Arc>, + pub owner: Shared, pub flags: u64, #[cfg(feature = "sqlx")] pub redirect_uris: Option>>, @@ -49,7 +52,7 @@ pub struct Application { #[cfg(feature = "sqlx")] pub install_params: Option>, #[cfg(not(feature = "sqlx"))] - pub install_params: Option>>, + pub install_params: Option>, pub terms_of_service_url: Option, pub privacy_policy_url: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] @@ -142,7 +145,7 @@ pub struct ApplicationCommand { pub application_id: Snowflake, pub name: String, pub description: String, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -154,7 +157,7 @@ pub struct ApplicationCommandOption { pub description: String, pub required: bool, pub choices: Vec, - pub options: Arc>>, + pub options: Shared>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -190,14 +193,14 @@ pub enum ApplicationCommandOptionType { pub struct ApplicationCommandInteractionData { pub id: Snowflake, pub name: String, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationCommandInteractionDataOption { pub name: String, pub value: Value, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -206,7 +209,7 @@ pub struct GuildApplicationCommandPermissions { pub id: Snowflake, pub application_id: Snowflake, pub guild_id: Snowflake, - pub permissions: Vec>>, + pub permissions: Vec>, } #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/types/entities/attachment.rs b/src/types/entities/attachment.rs index 0fb3e46..e6e28b5 100644 --- a/src/types/entities/attachment.rs +++ b/src/types/entities/attachment.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; @@ -126,3 +130,4 @@ impl PartialDiscordFileAttachment { self.id = Some(id); } } + diff --git a/src/types/entities/audit_log.rs b/src/types/entities/audit_log.rs index be14f0f..477fb20 100644 --- a/src/types/entities/audit_log.rs +++ b/src/types/entities/audit_log.rs @@ -1,14 +1,17 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::utils::Snowflake; #[derive(Serialize, Deserialize, Debug, Default, Clone)] /// See pub struct AuditLogEntry { pub target_id: Option, - pub changes: Option>>>, + pub changes: Option>>, pub user_id: Option, pub id: Snowflake, // to:do implement an enum for these types diff --git a/src/types/entities/auto_moderation.rs b/src/types/entities/auto_moderation.rs index a8910b1..cd69bf2 100644 --- a/src/types/entities/auto_moderation.rs +++ b/src/types/entities/auto_moderation.rs @@ -1,5 +1,8 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -21,8 +24,8 @@ pub struct AutoModerationRule { pub creator_id: Snowflake, pub event_type: AutoModerationRuleEventType, pub trigger_type: AutoModerationRuleTriggerType, - pub trigger_metadata: Arc>, - pub actions: Vec>>, + pub trigger_metadata: Shared, + pub actions: Vec>, pub enabled: bool, pub exempt_roles: Vec, pub exempt_channels: Vec, @@ -99,7 +102,7 @@ pub enum AutoModerationRuleKeywordPresetType { pub struct AutoModerationAction { #[serde(rename = "type")] pub action_type: AutoModerationActionType, - pub metadata: Option>>, + pub metadata: Option>, } #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 5acf2ae..7d000dc 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -1,4 +1,6 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -6,6 +8,7 @@ use serde_aux::prelude::deserialize_string_from_number; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::fmt::Debug; +use crate::gateway::Shared; use crate::types::{ entities::{GuildMember, User}, utils::Snowflake, @@ -71,13 +74,13 @@ pub struct Channel { pub permission_overwrites: Option>>, #[cfg(not(feature = "sqlx"))] #[cfg_attr(feature = "client", observe_option_vec)] - pub permission_overwrites: Option>>>, + pub permission_overwrites: Option>>, pub permissions: Option, pub position: Option, pub rate_limit_per_user: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub recipients: Option>>>, + pub recipients: Option>>, pub rtc_region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub thread_metadata: Option, @@ -171,7 +174,7 @@ pub struct ThreadMember { pub user_id: Option, pub join_timestamp: Option, pub flags: Option, - pub member: Option>>, + pub member: Option>, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] diff --git a/src/types/entities/config.rs b/src/types/entities/config.rs index e09d5c4..b64321e 100644 --- a/src/types/entities/config.rs +++ b/src/types/entities/config.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_json::Value; #[cfg(feature = "sqlx")] @@ -11,13 +15,12 @@ pub struct ConfigEntity { } impl ConfigEntity { - // RAGC: not sure about this, but it performs an "expensive" to_string opeartion, resulting in + // RAGC: not sure about this, but it performs an "expensive" to_string operation, resulting in // "borrowed -> owned" ownership - pub fn to_string(&self) -> Option { - let Some(v) = self.value.as_ref() else { - return None; - }; - Some(v.as_str().expect("value is not a string").to_string()) + pub fn as_string(&self) -> Option { + let v = self.value.as_ref()?; + let v = v.as_str()?; + Some(v.to_string()) } // RAGC: Is this proper naming? @@ -27,16 +30,14 @@ impl ConfigEntity { // This has "borrowed -> owned" ownership, yet isn't a to_*, because it isn't expensive. // It seems the inner serde type has the same issue, so I am happy to just leave this be pub fn as_bool(&self) -> Option { - let Some(v) = self.value.as_ref() else { - return None; - }; - Some(v.as_bool().expect("value is not a boolean")) + let v = self.value.as_ref()?; + let v = v.as_bool()?; + Some(v) } pub fn as_int(&self) -> Option { - let Some(v) = self.value.as_ref() else { - return None; - }; - Some(v.as_i64().expect("value is not a number")) + let v = self.value.as_ref()?; + let v = v.as_i64()?; + Some(v) } } diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index b3916e2..e84b025 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -1,8 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; @@ -31,7 +35,7 @@ pub struct Emoji { #[cfg(not(feature = "sqlx"))] pub roles: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, pub require_colons: Option, pub managed: Option, pub animated: Option, @@ -62,37 +66,3 @@ impl PartialEq for Emoji { || self.available != other.available) } } - -impl PartialOrd for Emoji { - fn partial_cmp(&self, other: &Self) -> Option { - match self.id.partial_cmp(&other.id) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.name.partial_cmp(&other.name) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.roles.partial_cmp(&other.roles) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.roles.partial_cmp(&other.roles) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.require_colons.partial_cmp(&other.require_colons) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.managed.partial_cmp(&other.managed) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.animated.partial_cmp(&other.animated) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - self.available.partial_cmp(&other.available) - } -} diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 1fe235b..52ec5e5 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -1,11 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::types::guild_configuration::GuildFeaturesList; use crate::types::{ entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook}, @@ -45,14 +49,14 @@ pub struct Guild { pub bans: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub channels: Option>>>, + pub channels: Option>>, pub default_message_notifications: Option, pub description: Option, pub discovery_splash: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_vec)] #[serde(default)] - pub emojis: Vec>>, + pub emojis: Vec>, pub explicit_content_filter: Option, //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] pub features: Option, @@ -88,7 +92,7 @@ pub struct Guild { pub region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub roles: Option>>>, + pub roles: Option>>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub rules_channel: Option, pub rules_channel_id: Option, @@ -102,10 +106,10 @@ pub struct Guild { pub verification_level: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub voice_states: Option>>>, + pub voice_states: Option>>, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub webhooks: Option>>>, + pub webhooks: Option>>, #[cfg(feature = "sqlx")] pub welcome_screen: Option>, #[cfg(not(feature = "sqlx"))] @@ -217,8 +221,6 @@ impl std::cmp::PartialEq for Guild { } } -impl std::cmp::Eq for Guild {} - /// See #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] @@ -239,11 +241,11 @@ pub struct GuildInvite { pub created_at: DateTime, pub expires_at: Option>, pub guild_id: Snowflake, - pub guild: Option>>, + pub guild: Option>, pub channel_id: Snowflake, - pub channel: Option>>, + pub channel: Option>, pub inviter_id: Option, - pub inviter: Option>>, + pub inviter: Option>, pub target_user_id: Option, pub target_user: Option, pub target_user_type: Option, @@ -296,7 +298,7 @@ pub struct GuildScheduledEvent { pub entity_type: GuildScheduledEventEntityType, pub entity_id: Option, pub entity_metadata: Option, - pub creator: Option>>, + pub creator: Option>, pub user_count: Option, pub image: Option, } diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index bf2f93b..14414c5 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -1,7 +1,10 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{entities::PublicUser, Snowflake}; #[derive(Debug, Deserialize, Default, Serialize, Clone)] @@ -10,7 +13,7 @@ use crate::types::{entities::PublicUser, Snowflake}; /// # Reference /// See pub struct GuildMember { - pub user: Option>>, + pub user: Option>, pub nick: Option, pub avatar: Option, pub roles: Vec, diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs index 0913213..97d21c3 100644 --- a/src/types/entities/integration.rs +++ b/src/types/entities/integration.rs @@ -1,8 +1,11 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{Application, User}, utils::Snowflake, @@ -23,14 +26,14 @@ pub struct Integration { pub expire_behaviour: Option, pub expire_grace_period: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub account: IntegrationAccount, pub synced_at: Option>, pub subscriber_count: Option, pub revoked: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub application: Option>>, + pub application: Option>, pub scopes: Option>, } diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs index d08ad1d..720203a 100644 --- a/src/types/entities/invite.rs +++ b/src/types/entities/invite.rs @@ -1,8 +1,11 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{Snowflake, WelcomeScreenObject}; use super::guild::GuildScheduledEvent; @@ -21,7 +24,7 @@ pub struct Invite { pub flags: Option, pub guild: Option, pub guild_id: Option, - pub guild_scheduled_event: Option>>, + pub guild_scheduled_event: Option>, #[serde(rename = "type")] pub invite_type: Option, pub inviter: Option, @@ -59,7 +62,7 @@ pub struct InviteGuild { /// See #[derive(Debug, Serialize, Deserialize)] pub struct InviteStageInstance { - pub members: Vec>>, + pub members: Vec>, pub participant_count: i32, pub speaker_count: i32, pub topic: String, diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index 41c4d51..d764243 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -1,7 +1,10 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{ Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData, @@ -121,7 +124,7 @@ pub struct MessageInteraction { pub interaction_type: u8, pub name: String, pub user: User, - pub member: Option>>, + pub member: Option>, } #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)] @@ -219,7 +222,7 @@ pub struct EmbedField { inline: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialOrd, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Reaction { pub count: u32, pub burst_count: u32, diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 8343628..1400d0e 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use application::*; pub use attachment::*; pub use audit_log::*; @@ -23,6 +27,7 @@ pub use user_settings::*; pub use voice_state::*; pub use webhook::*; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -34,7 +39,6 @@ use async_trait::async_trait; #[cfg(feature = "client")] use std::fmt::Debug; - #[cfg(feature = "client")] use std::sync::{Arc, RwLock}; @@ -69,9 +73,9 @@ pub trait Composite { async fn watch_whole(self, gateway: &GatewayHandle) -> Self; async fn option_observe_fn( - value: Option>>, + value: Option>, gateway: &GatewayHandle, - ) -> Option>> + ) -> Option> where T: Composite + Debug, { @@ -84,9 +88,9 @@ pub trait Composite { } async fn option_vec_observe_fn( - value: Option>>>, + value: Option>>, gateway: &GatewayHandle, - ) -> Option>>> + ) -> Option>> where T: Composite, { @@ -101,17 +105,14 @@ pub trait Composite { } } - async fn value_observe_fn(value: Arc>, gateway: &GatewayHandle) -> Arc> + async fn value_observe_fn(value: Shared, gateway: &GatewayHandle) -> Shared where T: Composite, { gateway.observe(value).await } - async fn vec_observe_fn( - value: Vec>>, - gateway: &GatewayHandle, - ) -> Vec>> + async fn vec_observe_fn(value: Vec>, gateway: &GatewayHandle) -> Vec> where T: Composite, { @@ -122,3 +123,19 @@ pub trait Composite { vec } } + +pub trait IntoShared { + /// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. + /// + /// [`Shared`] can then be observed using the [`Gateway`], turning the underlying + /// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus + /// entity struct, updating its' held information when new information concerning itself arrives + /// over the [`Gateway`] connection, reducing the need for expensive network-API calls. + fn into_shared(self) -> Shared; +} + +impl IntoShared for T { + fn into_shared(self) -> Shared { + Arc::new(RwLock::new(self)) + } +} diff --git a/src/types/entities/ratelimits.rs b/src/types/entities/ratelimits.rs index f766a12..1823e76 100644 --- a/src/types/entities/ratelimits.rs +++ b/src/types/entities/ratelimits.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::hash::Hash; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/relationship.rs b/src/types/entities/relationship.rs index 576d99a..a568256 100644 --- a/src/types/entities/relationship.rs +++ b/src/types/entities/relationship.rs @@ -1,9 +1,12 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::Snowflake; use super::PublicUser; @@ -15,7 +18,7 @@ pub struct Relationship { #[serde(rename = "type")] pub relationship_type: RelationshipType, pub nickname: Option, - pub user: Arc>, + pub user: Shared, pub since: Option>, } diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 6a8327e..1b5e91e 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use serde::{Deserialize, Serialize}; use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number}; diff --git a/src/types/entities/security_key.rs b/src/types/entities/security_key.rs index 2cf8f66..7e0bb6b 100644 --- a/src/types/entities/security_key.rs +++ b/src/types/entities/security_key.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/entities/stage_instance.rs b/src/types/entities/stage_instance.rs index 8810f52..d48231b 100644 --- a/src/types/entities/stage_instance.rs +++ b/src/types/entities/stage_instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs index 593206d..8b95bc4 100644 --- a/src/types/entities/sticker.rs +++ b/src/types/entities/sticker.rs @@ -1,7 +1,10 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{entities::User, utils::Snowflake}; #[derive(Debug, Serialize, Deserialize, Clone, Default)] @@ -24,7 +27,7 @@ pub struct Sticker { pub available: Option, pub guild_id: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, pub sort_value: Option, } diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs index 8e32f55..98bd23e 100644 --- a/src/types/entities/team.rs +++ b/src/types/entities/team.rs @@ -1,7 +1,10 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; @@ -21,5 +24,5 @@ pub struct TeamMember { pub membership_state: u8, pub permissions: Vec, pub team_id: Snowflake, - pub user: Arc>, + pub user: Shared, } diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs index 1305a98..f34fbd7 100644 --- a/src/types/entities/template.rs +++ b/src/types/entities/template.rs @@ -1,8 +1,11 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{Guild, User}, utils::Snowflake, @@ -18,13 +21,13 @@ pub struct GuildTemplate { pub usage_count: Option, pub creator_id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub creator: Arc>, + pub creator: Shared, pub created_at: DateTime, pub updated_at: DateTime, pub source_guild_id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub source_guild: Vec>>, + pub source_guild: Vec>, // Unsure how a {recursive: Guild} looks like, might be a Vec? #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub serialized_source_guild: Vec>>, + pub serialized_source_guild: Vec>, } diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index d9f70ce..66fbab8 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::utils::Snowflake; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -133,7 +137,7 @@ bitflags::bitflags! { } } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct UserProfileMetadata { pub guild_id: Option, pub pronouns: String, diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs index e6db7e7..6c072a8 100644 --- a/src/types/entities/user_settings.rs +++ b/src/types/entities/user_settings.rs @@ -1,8 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use chrono::{serde::ts_milliseconds_option, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "lowercase")] @@ -77,7 +83,7 @@ pub struct UserSettings { #[cfg(not(feature = "sqlx"))] pub restricted_guilds: Vec, pub show_current_game: bool, - pub status: Arc>, + pub status: Shared, pub stream_notifications_enabled: bool, pub theme: UserTheme, pub timezone_offset: i16, @@ -153,5 +159,5 @@ pub struct GuildFolder { #[derive(Debug, Serialize, Deserialize)] pub struct LoginResult { pub token: String, - pub settings: Arc>, + pub settings: Shared, } diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 1a0c3b3..304b724 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -1,8 +1,11 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. #[cfg(feature = "client")] use chorus_macros::Composite; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::types::Composite; @@ -33,7 +36,8 @@ pub struct VoiceState { pub guild: Option, pub channel_id: Option, pub user_id: Snowflake, - pub member: Option>>, + pub member: Option>, + /// Includes alphanumeric characters, not a snowflake pub session_id: String, pub token: Option, pub deaf: bool, @@ -48,6 +52,7 @@ pub struct VoiceState { } impl Updateable for VoiceState { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Snowflake { if let Some(id) = self.id { id // ID exists: Only the case for Spacebar Server impls diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index cf5716c..f973956 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -1,8 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -36,10 +40,10 @@ pub struct Webhook { pub application_id: Snowflake, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub source_guild: Option>>, + pub source_guild: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, } diff --git a/src/types/errors.rs b/src/types/errors.rs index db7cbf7..f0a488c 100644 --- a/src/types/errors.rs +++ b/src/types/errors.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, thiserror::Error)] diff --git a/src/types/events/application.rs b/src/types/events/application.rs index 7fee577..c39b896 100644 --- a/src/types/events/application.rs +++ b/src/types/events/application.rs @@ -1,12 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{GuildApplicationCommandPermissions, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct ApplicationCommandPermissionsUpdate { #[serde(flatten)] pub permissions: GuildApplicationCommandPermissions, } - -impl WebSocketEvent for ApplicationCommandPermissionsUpdate {} diff --git a/src/types/events/auto_moderation.rs b/src/types/events/auto_moderation.rs index 2a2eb6b..a7e3a34 100644 --- a/src/types/events/auto_moderation.rs +++ b/src/types/events/auto_moderation.rs @@ -1,25 +1,28 @@ -use crate::types::{JsonField, SourceUrlField}; -use chorus_macros::{JsonField, SourceUrlField}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{JsonField, SourceUrlField, WebSocketEvent}; +use chorus_macros::{JsonField, SourceUrlField, WebSocketEvent}; use serde::{Deserialize, Serialize}; use crate::types::{ AutoModerationAction, AutoModerationRule, AutoModerationRuleTriggerType, Snowflake, - WebSocketEvent, }; #[cfg(feature = "client")] use super::UpdateMessage; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct AutoModerationRuleCreate { #[serde(flatten)] pub rule: AutoModerationRule, } -impl WebSocketEvent for AutoModerationRuleCreate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField)] +#[derive( + Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField, WebSocketEvent, +)] /// See pub struct AutoModerationRuleUpdate { #[serde(flatten)] @@ -31,24 +34,22 @@ pub struct AutoModerationRuleUpdate { } #[cfg(feature = "client")] +#[cfg(not(tarpaulin_include))] impl UpdateMessage for AutoModerationRuleUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.rule.id) } } -impl WebSocketEvent for AutoModerationRuleUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct AutoModerationRuleDelete { #[serde(flatten)] pub rule: AutoModerationRule, } -impl WebSocketEvent for AutoModerationRuleDelete {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct AutoModerationActionExecution { pub guild_id: Snowflake, @@ -63,5 +64,3 @@ pub struct AutoModerationActionExecution { pub matched_keyword: Option, pub matched_content: Option, } - -impl WebSocketEvent for AutoModerationActionExecution {} diff --git a/src/types/events/call.rs b/src/types/events/call.rs index 508aae2..5dc5911 100644 --- a/src/types/events/call.rs +++ b/src/types/events/call.rs @@ -1,8 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Snowflake, VoiceState, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented; /// Is sent to a client by the server to signify a new call being created; /// @@ -19,9 +24,7 @@ pub struct CallCreate { pub channel_id: Snowflake, } -impl WebSocketEvent for CallCreate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// Officially Undocumented; /// Updates the client on which calls are ringing, along with a specific call?; /// @@ -36,9 +39,7 @@ pub struct CallUpdate { pub channel_id: Snowflake, } -impl WebSocketEvent for CallUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// Officially Undocumented; /// Deletes a ringing call; /// Ex: {"t":"CALL_DELETE","s":8,"op":0,"d":{"channel_id":"837609115475771392"}} @@ -46,9 +47,7 @@ pub struct CallDelete { pub channel_id: Snowflake, } -impl WebSocketEvent for CallDelete {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// Officially Undocumented; /// See ; /// @@ -57,4 +56,3 @@ pub struct CallSync { pub channel_id: Snowflake, } -impl WebSocketEvent for CallSync {} diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index eb557d7..28a96e5 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -1,4 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::WebSocketEvent; +use crate::types::IntoShared; use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField}; use chrono::{DateTime, Utc}; @@ -8,12 +13,12 @@ use serde::{Deserialize, Serialize}; use super::UpdateMessage; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::types::Guild; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, WebSocketEvent)] /// See pub struct ChannelPinsUpdate { pub guild_id: Option, @@ -21,9 +26,7 @@ pub struct ChannelPinsUpdate { pub last_pin_timestamp: Option>, } -impl WebSocketEvent for ChannelPinsUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ChannelCreate { #[serde(flatten)] @@ -34,17 +37,16 @@ pub struct ChannelCreate { pub source_url: String, } -impl WebSocketEvent for ChannelCreate {} - #[cfg(feature = "client")] impl UpdateMessage for ChannelCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { self.channel.guild_id } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); - let update = Arc::new(RwLock::new(self.channel.clone())); + let update = self.channel.clone().into_shared(); if write.channels.is_some() { write.channels.as_mut().unwrap().push(update); } else { @@ -53,7 +55,7 @@ impl UpdateMessage for ChannelCreate { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ChannelUpdate { #[serde(flatten)] @@ -64,20 +66,20 @@ pub struct ChannelUpdate { pub source_url: String, } -impl WebSocketEvent for ChannelUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for ChannelUpdate { - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); *write = self.channel.clone(); } + + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.channel.id) } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// Officially undocumented. /// Sends updates to client about a new message with its id /// {"channel_unread_updates": [{"id": "816412869766938648", "last_message_id": "1085892012085104680"}} @@ -95,9 +97,7 @@ pub struct ChannelUnreadUpdateObject { pub last_pin_timestamp: Option, } -impl WebSocketEvent for ChannelUnreadUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ChannelDelete { #[serde(flatten)] @@ -110,11 +110,12 @@ pub struct ChannelDelete { #[cfg(feature = "client")] impl UpdateMessage for ChannelDelete { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { self.channel.guild_id } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { if self.id().is_none() { return; } @@ -131,4 +132,3 @@ impl UpdateMessage for ChannelDelete { } } -impl WebSocketEvent for ChannelDelete {} diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs index 89e4a75..7271d6a 100644 --- a/src/types/events/guild.rs +++ b/src/types/events/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus_macros::{JsonField, SourceUrlField}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -5,8 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, PublicUser, UnavailableGuild}; use crate::types::events::WebSocketEvent; use crate::types::{ - AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake, - SourceUrlField, Sticker, + AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, IntoShared, JsonField, RoleObject, + Snowflake, SourceUrlField, Sticker, }; use super::PresenceUpdate; @@ -14,9 +18,9 @@ use super::PresenceUpdate; #[cfg(feature = "client")] use super::UpdateMessage; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; -#[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField, WebSocketEvent)] /// See ; /// Received to give data about a guild; // This one is particularly painful, it can be a Guild object with an extra field or an unavailable guild object @@ -30,7 +34,9 @@ pub struct GuildCreate { } #[cfg(feature = "client")] +#[cfg(not(tarpaulin_include))] impl UpdateMessage for GuildCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { match &self.d { GuildCreateDataOption::UnavailableGuild(unavailable) => Some(unavailable.id), @@ -38,7 +44,7 @@ impl UpdateMessage for GuildCreate { } } - fn update(&mut self, _: Arc>) {} + fn update(&mut self, _: Shared) {} } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -54,9 +60,7 @@ impl Default for GuildCreateDataOption { } } -impl WebSocketEvent for GuildCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to give info about a user being banned from a guild; pub struct GuildBanAdd { @@ -64,9 +68,7 @@ pub struct GuildBanAdd { pub user: PublicUser, } -impl WebSocketEvent for GuildBanAdd {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to give info about a user being unbanned from a guild; pub struct GuildBanRemove { @@ -74,9 +76,7 @@ pub struct GuildBanRemove { pub user: PublicUser, } -impl WebSocketEvent for GuildBanRemove {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField, WebSocketEvent)] /// See ; /// Received to give info about a guild being updated; pub struct GuildUpdate { @@ -88,16 +88,15 @@ pub struct GuildUpdate { pub json: String, } -impl WebSocketEvent for GuildUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for GuildUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild.id) } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField, WebSocketEvent)] /// See ; /// Received to tell the client about a guild being deleted; pub struct GuildDelete { @@ -111,15 +110,14 @@ pub struct GuildDelete { #[cfg(feature = "client")] impl UpdateMessage for GuildDelete { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild.id) } - fn update(&mut self, _: Arc>) {} + fn update(&mut self, _: Shared) {} } -impl WebSocketEvent for GuildDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to the client about an audit log entry being added; pub struct GuildAuditLogEntryCreate { @@ -127,9 +125,7 @@ pub struct GuildAuditLogEntryCreate { pub entry: AuditLogEntry, } -impl WebSocketEvent for GuildAuditLogEntryCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to tell the client about a change to a guild's emoji list; pub struct GuildEmojisUpdate { @@ -137,9 +133,7 @@ pub struct GuildEmojisUpdate { pub emojis: Vec, } -impl WebSocketEvent for GuildEmojisUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to tell the client about a change to a guild's sticker list; pub struct GuildStickersUpdate { @@ -147,17 +141,13 @@ pub struct GuildStickersUpdate { pub stickers: Vec, } -impl WebSocketEvent for GuildStickersUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildIntegrationsUpdate { pub guild_id: Snowflake, } -impl WebSocketEvent for GuildIntegrationsUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to tell the client about a user joining a guild; pub struct GuildMemberAdd { @@ -166,9 +156,7 @@ pub struct GuildMemberAdd { pub guild_id: Snowflake, } -impl WebSocketEvent for GuildMemberAdd {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See ; /// Received to tell the client about a user leaving a guild; pub struct GuildMemberRemove { @@ -176,9 +164,7 @@ pub struct GuildMemberRemove { pub user: PublicUser, } -impl WebSocketEvent for GuildMemberRemove {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildMemberUpdate { pub guild_id: Snowflake, @@ -194,9 +180,7 @@ pub struct GuildMemberUpdate { pub communication_disabled_until: Option>, } -impl WebSocketEvent for GuildMemberUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildMembersChunk { pub guild_id: Snowflake, @@ -208,9 +192,7 @@ pub struct GuildMembersChunk { pub nonce: Option, } -impl WebSocketEvent for GuildMembersChunk {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct GuildRoleCreate { pub guild_id: Snowflake, @@ -221,29 +203,28 @@ pub struct GuildRoleCreate { pub source_url: String, } -impl WebSocketEvent for GuildRoleCreate {} - #[cfg(feature = "client")] impl UpdateMessage for GuildRoleCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild_id) } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut object_to_update = object_to_update.write().unwrap(); if object_to_update.roles.is_some() { object_to_update .roles .as_mut() .unwrap() - .push(Arc::new(RwLock::new(self.role.clone()))); + .push(self.role.clone().into_shared()); } else { - object_to_update.roles = Some(Vec::from([Arc::new(RwLock::new(self.role.clone()))])); + object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()])); } } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct GuildRoleUpdate { pub guild_id: Snowflake, @@ -254,57 +235,48 @@ pub struct GuildRoleUpdate { pub source_url: String, } -impl WebSocketEvent for GuildRoleUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for GuildRoleUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.role.id) } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); *write = self.role.clone(); } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildRoleDelete { pub guild_id: Snowflake, pub role_id: Snowflake, } -impl WebSocketEvent for GuildRoleDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildScheduledEventCreate { #[serde(flatten)] pub event: GuildScheduledEvent, } -impl WebSocketEvent for GuildScheduledEventCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildScheduledEventUpdate { #[serde(flatten)] pub event: GuildScheduledEvent, } -impl WebSocketEvent for GuildScheduledEventUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildScheduledEventDelete { #[serde(flatten)] pub event: GuildScheduledEvent, } -impl WebSocketEvent for GuildScheduledEventDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildScheduledEventUserAdd { pub guild_scheduled_event_id: Snowflake, @@ -312,14 +284,10 @@ pub struct GuildScheduledEventUserAdd { pub guild_id: Snowflake, } -impl WebSocketEvent for GuildScheduledEventUserAdd {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct GuildScheduledEventUserRemove { pub guild_scheduled_event_id: Snowflake, pub user_id: Snowflake, pub guild_id: Snowflake, } - -impl WebSocketEvent for GuildScheduledEventUserRemove {} diff --git a/src/types/events/heartbeat.rs b/src/types/events/heartbeat.rs index b402ff9..b7a0024 100644 --- a/src/types/events/heartbeat.rs +++ b/src/types/events/heartbeat.rs @@ -1,17 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, WebSocketEvent)] pub struct GatewayHeartbeat { pub op: u8, pub d: Option, } -impl WebSocketEvent for GatewayHeartbeat {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] pub struct GatewayHeartbeatAck { pub op: i32, } -impl WebSocketEvent for GatewayHeartbeatAck {} diff --git a/src/types/events/hello.rs b/src/types/events/hello.rs index fef3e22..f72720b 100644 --- a/src/types/events/hello.rs +++ b/src/types/events/hello.rs @@ -1,20 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; /// Received on gateway init, tells the client how often to send heartbeats; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] pub struct GatewayHello { pub op: i32, pub d: HelloData, } -impl WebSocketEvent for GatewayHello {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, WebSocketEvent)] /// 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 pub heartbeat_interval: u64, } -impl WebSocketEvent for HelloData {} diff --git a/src/types/events/identify.rs b/src/types/events/identify.rs index 12bc369..84c46a7 100644 --- a/src/types/events/identify.rs +++ b/src/types/events/identify.rs @@ -1,8 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::{PresenceUpdate, WebSocketEvent}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, WebSocketEvent)] pub struct GatewayIdentifyPayload { pub token: String, pub properties: GatewayIdentifyConnectionProps, @@ -66,9 +70,7 @@ impl GatewayIdentifyPayload { } } -impl WebSocketEvent for GatewayIdentifyPayload {} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] #[serde_as] pub struct GatewayIdentifyConnectionProps { /// Almost always sent diff --git a/src/types/events/integration.rs b/src/types/events/integration.rs index 2423e78..cf167c9 100644 --- a/src/types/events/integration.rs +++ b/src/types/events/integration.rs @@ -1,8 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Integration, Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct IntegrationCreate { #[serde(flatten)] @@ -10,9 +15,7 @@ pub struct IntegrationCreate { pub guild_id: Snowflake, } -impl WebSocketEvent for IntegrationCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct IntegrationUpdate { #[serde(flatten)] @@ -20,9 +23,7 @@ pub struct IntegrationUpdate { pub guild_id: Snowflake, } -impl WebSocketEvent for IntegrationUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct IntegrationDelete { pub id: Snowflake, @@ -30,4 +31,3 @@ pub struct IntegrationDelete { pub application_id: Option, } -impl WebSocketEvent for IntegrationDelete {} diff --git a/src/types/events/interaction.rs b/src/types/events/interaction.rs index 304e7d4..c90c519 100644 --- a/src/types/events/interaction.rs +++ b/src/types/events/interaction.rs @@ -1,12 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Interaction, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct InteractionCreate { #[serde(flatten)] pub interaction: Interaction, } -impl WebSocketEvent for InteractionCreate {} diff --git a/src/types/events/invalid_session.rs b/src/types/events/invalid_session.rs new file mode 100644 index 0000000..ee94eeb --- /dev/null +++ b/src/types/events/invalid_session.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +use super::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] +/// Your session is now invalid. +/// +/// Either reauthenticate and reidentify or resume if possible. +/// +/// # Reference +/// See +pub struct GatewayInvalidSession { + #[serde(rename = "d")] + pub resumable: bool, +} + diff --git a/src/types/events/invite.rs b/src/types/events/invite.rs index 674cc62..13b91b4 100644 --- a/src/types/events/invite.rs +++ b/src/types/events/invite.rs @@ -1,17 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{GuildInvite, Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct InviteCreate { #[serde(flatten)] pub invite: GuildInvite, } -impl WebSocketEvent for InviteCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct InviteDelete { pub channel_id: Snowflake, @@ -19,4 +22,3 @@ pub struct InviteDelete { pub code: String, } -impl WebSocketEvent for InviteDelete {} diff --git a/src/types/events/lazy_request.rs b/src/types/events/lazy_request.rs index fd53183..6e17b8e 100644 --- a/src/types/events/lazy_request.rs +++ b/src/types/events/lazy_request.rs @@ -1,19 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::collections::HashMap; use serde::{Deserialize, Serialize}; use crate::types::Snowflake; - use super::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented /// /// Sent to the server to signify lazy loading of a guild; /// Sent by the official client when switching to a guild or channel; -/// After this, you should recieve message updates +/// After this, you should receive message updates /// -/// See +/// See /// /// {"op":14,"d":{"guild_id":"848582562217590824","typing":true,"activities":true,"threads":true}} pub struct LazyRequest { @@ -27,4 +30,3 @@ pub struct LazyRequest { pub channels: Option>>>, } -impl WebSocketEvent for LazyRequest {} diff --git a/src/types/events/message.rs b/src/types/events/message.rs index fac083b..8f982e5 100644 --- a/src/types/events/message.rs +++ b/src/types/events/message.rs @@ -1,13 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{ entities::{Emoji, GuildMember, Message, PublicUser}, - Snowflake, + Snowflake, WebSocketEvent }; -use super::WebSocketEvent; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct TypingStartEvent { @@ -18,9 +22,7 @@ pub struct TypingStartEvent { pub member: Option, } -impl WebSocketEvent for TypingStartEvent {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// See pub struct MessageCreate { #[serde(flatten)] @@ -30,7 +32,7 @@ pub struct MessageCreate { pub mentions: Option>, } -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// See pub struct MessageCreateUser { #[serde(flatten)] @@ -38,9 +40,7 @@ pub struct MessageCreateUser { pub member: Option, } -impl WebSocketEvent for MessageCreate {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageUpdate { @@ -51,9 +51,7 @@ pub struct MessageUpdate { pub mentions: Option>, } -impl WebSocketEvent for MessageUpdate {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageDelete { @@ -62,9 +60,7 @@ pub struct MessageDelete { pub guild_id: Option, } -impl WebSocketEvent for MessageDelete {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageDeleteBulk { @@ -73,9 +69,7 @@ pub struct MessageDeleteBulk { pub guild_id: Option, } -impl WebSocketEvent for MessageDeleteBulk {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionAdd { @@ -87,9 +81,7 @@ pub struct MessageReactionAdd { pub emoji: Emoji, } -impl WebSocketEvent for MessageReactionAdd {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionRemove { @@ -100,9 +92,7 @@ pub struct MessageReactionRemove { pub emoji: Emoji, } -impl WebSocketEvent for MessageReactionRemove {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionRemoveAll { @@ -111,9 +101,7 @@ pub struct MessageReactionRemoveAll { pub guild_id: Option, } -impl WebSocketEvent for MessageReactionRemoveAll {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionRemoveEmoji { @@ -123,15 +111,13 @@ pub struct MessageReactionRemoveEmoji { pub emoji: Emoji, } -impl WebSocketEvent for MessageReactionRemoveEmoji {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented /// /// Not documented anywhere unofficially /// /// Apparently "Message ACK refers to marking a message as read for Discord's API." () -/// I suspect this is sent and recieved from the gateway to let clients on other devices know the user has read a message +/// I suspect this is sent and received from the gateway to let clients on other devices know the user has read a message /// /// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}} pub struct MessageACK { @@ -146,4 +132,3 @@ pub struct MessageACK { pub channel_id: Snowflake, } -impl WebSocketEvent for MessageACK {} diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index a4e497a..f76e4c2 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; pub use application::*; @@ -10,12 +14,14 @@ pub use hello::*; pub use identify::*; pub use integration::*; pub use interaction::*; +pub use invalid_session::*; pub use invite::*; pub use lazy_request::*; pub use message::*; pub use passive_update::*; pub use presence::*; pub use ready::*; +pub use reconnect::*; pub use relationship::*; pub use request_members::*; pub use resume::*; @@ -24,8 +30,10 @@ pub use stage_instance::*; pub use thread::*; pub use user::*; pub use voice::*; +pub use voice_gateway::*; pub use webhooks::*; -pub use webrtc::*; + +use chorus_macros::WebSocketEvent; #[cfg(feature = "client")] use super::Snowflake; @@ -39,9 +47,9 @@ use serde_json::{from_str, from_value, to_value, Value}; #[cfg(feature = "client")] use std::collections::HashMap; -use std::fmt::Debug; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; +use std::fmt::Debug; #[cfg(feature = "client")] use serde::de::DeserializeOwned; @@ -56,12 +64,14 @@ mod hello; mod identify; mod integration; mod interaction; +mod invalid_session; mod invite; mod lazy_request; mod message; mod passive_update; mod presence; mod ready; +mod reconnect; mod relationship; mod request_members; mod resume; @@ -72,11 +82,11 @@ mod user; mod voice; mod webhooks; -mod webrtc; +mod voice_gateway; pub trait WebSocketEvent: Send + Sync + Debug {} -#[derive(Debug, Default, Serialize, Clone)] +#[derive(Debug, Default, Serialize, Clone, WebSocketEvent)] /// The payload used for sending events to the gateway /// /// Similar to [GatewayReceivePayload], except we send a [serde_json::value::Value] for d whilst we receive a [serde_json::value::RawValue] @@ -94,8 +104,6 @@ pub struct GatewaySendPayload { pub sequence_number: Option, } -impl WebSocketEvent for GatewaySendPayload {} - #[derive(Debug, Default, Deserialize, Clone)] /// The payload used for receiving events from the gateway pub struct GatewayReceivePayload<'a> { @@ -132,9 +140,10 @@ pub(crate) trait UpdateMessage: Clone + JsonField + SourceUrlField where T: Updateable + Serialize + DeserializeOwned + Clone, { - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { update_object(self.get_json(), object_to_update) } + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option; } @@ -154,7 +163,7 @@ pub trait SourceUrlField: Clone { /// Only applicable for events where the Update struct is the same as the Entity struct pub(crate) fn update_object( value: String, - object: Arc>, + object: Shared<(impl Updateable + Serialize + DeserializeOwned + Clone)>, ) { let data_from_event: HashMap = from_str(&value).unwrap(); let mut original_data: HashMap = diff --git a/src/types/events/passive_update.rs b/src/types/events/passive_update.rs index 234af3e..a0f9909 100644 --- a/src/types/events/passive_update.rs +++ b/src/types/events/passive_update.rs @@ -1,9 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use super::{ChannelUnreadUpdateObject, WebSocketEvent}; use crate::types::{GuildMember, Snowflake, VoiceState}; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)] /// Officially Undocumented /// /// Seems to be passively set to update the client on guild details (though, why not just send the update events?) @@ -14,4 +18,3 @@ pub struct PassiveUpdateV1 { pub channels: Vec, } -impl WebSocketEvent for PassiveUpdateV1 {} diff --git a/src/types/events/presence.rs b/src/types/events/presence.rs index e9a7dee..9fe7c1e 100644 --- a/src/types/events/presence.rs +++ b/src/types/events/presence.rs @@ -1,8 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, UserStatus}; use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Sent by the client to update its status and presence; /// See pub struct UpdatePresence { @@ -14,7 +18,7 @@ pub struct UpdatePresence { pub afk: bool, } -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, WebSocketEvent)] /// Received to tell the client that a user updated their presence / status /// See pub struct PresenceUpdate { @@ -26,4 +30,3 @@ pub struct PresenceUpdate { pub client_status: ClientStatusObject, } -impl WebSocketEvent for PresenceUpdate {} diff --git a/src/types/events/ready.rs b/src/types/events/ready.rs index ea46b69..4faa95d 100644 --- a/src/types/events/ready.rs +++ b/src/types/events/ready.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, User}; @@ -5,7 +9,7 @@ use crate::types::events::{Session, WebSocketEvent}; use crate::types::interfaces::ClientStatusObject; use crate::types::{Activity, GuildMember, PresenceUpdate, VoiceState}; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// 1/2 half documented; /// Received after identifying, provides initial user info; /// See @@ -26,9 +30,7 @@ pub struct GatewayReady { pub shard: Option<(u64, u64)>, } -impl WebSocketEvent for GatewayReady {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented; /// Sent after the READY event when a client is a user, seems to somehow add onto the ready event; pub struct GatewayReadySupplemental { @@ -41,8 +43,6 @@ pub struct GatewayReadySupplemental { pub disclose: Vec, } -impl WebSocketEvent for GatewayReadySupplemental {} - #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct MergedPresences { pub guilds: Vec>, diff --git a/src/types/events/reconnect.rs b/src/types/events/reconnect.rs new file mode 100644 index 0000000..558d953 --- /dev/null +++ b/src/types/events/reconnect.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +use super::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] +/// "The reconnect event is dispatched when a client should reconnect to the Gateway (and resume their existing session, if they have one). This event usually occurs during deploys to migrate sessions gracefully off old hosts" +/// +/// # Reference +/// See +pub struct GatewayReconnect {} + diff --git a/src/types/events/relationship.rs b/src/types/events/relationship.rs index a1f75a5..b12d73d 100644 --- a/src/types/events/relationship.rs +++ b/src/types/events/relationship.rs @@ -1,7 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, Relationship, RelationshipType, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)] /// See pub struct RelationshipAdd { #[serde(flatten)] @@ -9,9 +13,7 @@ pub struct RelationshipAdd { pub should_notify: bool, } -impl WebSocketEvent for RelationshipAdd {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct RelationshipRemove { pub id: Snowflake, @@ -19,4 +21,3 @@ pub struct RelationshipRemove { pub relationship_type: RelationshipType, } -impl WebSocketEvent for RelationshipRemove {} diff --git a/src/types/events/request_members.rs b/src/types/events/request_members.rs index 526313b..a6cffdf 100644 --- a/src/types/events/request_members.rs +++ b/src/types/events/request_members.rs @@ -1,7 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)] /// See pub struct GatewayRequestGuildMembers { pub guild_id: Snowflake, @@ -13,4 +17,3 @@ pub struct GatewayRequestGuildMembers { pub nonce: Option, } -impl WebSocketEvent for GatewayRequestGuildMembers {} diff --git a/src/types/events/resume.rs b/src/types/events/resume.rs index 45d2235..2485dc3 100644 --- a/src/types/events/resume.rs +++ b/src/types/events/resume.rs @@ -1,11 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[derive(Debug, Clone, Deserialize, Serialize, Default, WebSocketEvent)] pub struct GatewayResume { pub token: String, pub session_id: String, pub seq: String, } -impl WebSocketEvent for GatewayResume {} diff --git a/src/types/events/session.rs b/src/types/events/session.rs index 868c8e8..a76ebc3 100644 --- a/src/types/events/session.rs +++ b/src/types/events/session.rs @@ -1,8 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; use crate::types::{Activity, WebSocketEvent}; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented /// Seems like it sends active session info to users on connect /// [{"activities":[],"client_info":{"client":"web","os":"other","version":0},"session_id":"ab5941b50d818b1f8d93b4b1b581b192","status":"online"}] @@ -29,4 +34,3 @@ pub struct ClientInfo { pub version: u8, } -impl WebSocketEvent for SessionsReplace {} diff --git a/src/types/events/stage_instance.rs b/src/types/events/stage_instance.rs index c2bbc46..32c5a17 100644 --- a/src/types/events/stage_instance.rs +++ b/src/types/events/stage_instance.rs @@ -1,30 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{StageInstance, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct StageInstanceCreate { #[serde(flatten)] pub stage_instance: StageInstance, } -impl WebSocketEvent for StageInstanceCreate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct StageInstanceUpdate { #[serde(flatten)] pub stage_instance: StageInstance, } -impl WebSocketEvent for StageInstanceUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct StageInstanceDelete { #[serde(flatten)] pub stage_instance: StageInstance, } -impl WebSocketEvent for StageInstanceDelete {} diff --git a/src/types/events/thread.rs b/src/types/events/thread.rs index cff5f6f..bf67e83 100644 --- a/src/types/events/thread.rs +++ b/src/types/events/thread.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus_macros::{JsonField, SourceUrlField}; use serde::{Deserialize, Serialize}; @@ -8,16 +12,14 @@ use crate::types::{JsonField, Snowflake, SourceUrlField}; #[cfg(feature = "client")] use super::UpdateMessage; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadCreate { #[serde(flatten)] pub thread: Channel, } -impl WebSocketEvent for ThreadCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ThreadUpdate { #[serde(flatten)] @@ -28,25 +30,22 @@ pub struct ThreadUpdate { pub source_url: String, } -impl WebSocketEvent for ThreadUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for ThreadUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.thread.id) } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadDelete { #[serde(flatten)] pub thread: Channel, } -impl WebSocketEvent for ThreadDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadListSync { pub guild_id: Snowflake, @@ -55,9 +54,7 @@ pub struct ThreadListSync { pub members: Option>, } -impl WebSocketEvent for ThreadListSync {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See /// The inner payload is a thread member object with an extra field. pub struct ThreadMemberUpdate { @@ -66,9 +63,7 @@ pub struct ThreadMemberUpdate { pub guild_id: Snowflake, } -impl WebSocketEvent for ThreadMemberUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadMembersUpdate { pub id: Snowflake, @@ -79,4 +74,3 @@ pub struct ThreadMembersUpdate { pub removed_members: Option>, } -impl WebSocketEvent for ThreadMembersUpdate {} diff --git a/src/types/events/user.rs b/src/types/events/user.rs index 7165812..877c96c 100644 --- a/src/types/events/user.rs +++ b/src/types/events/user.rs @@ -1,10 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::entities::PublicUser; use crate::types::events::WebSocketEvent; use crate::types::utils::Snowflake; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] /// See ; /// Sent to indicate updates to a user object; (name changes, discriminator changes, etc); pub struct UserUpdate { @@ -12,9 +16,7 @@ pub struct UserUpdate { pub user: PublicUser, } -impl WebSocketEvent for UserUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] /// Undocumented; /// /// Possibly an update for muted guild / channel settings for the current user; @@ -37,8 +39,6 @@ pub struct UserGuildSettingsUpdate { pub channel_overrides: Vec, } -impl WebSocketEvent for UserGuildSettingsUpdate {} - #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] /// Undocumented; /// diff --git a/src/types/events/voice.rs b/src/types/events/voice.rs index 2618ee1..a2b80b9 100644 --- a/src/types/events/voice.rs +++ b/src/types/events/voice.rs @@ -1,7 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, Snowflake, VoiceState}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, PartialEq, Eq, WebSocketEvent)] /// /// Sent to the server to indicate an update of the voice state (leave voice channel, join voice channel, mute, deafen); /// @@ -13,9 +17,7 @@ pub struct UpdateVoiceState { pub self_deaf: bool, } -impl WebSocketEvent for UpdateVoiceState {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See ; /// /// Received from the server to indicate an update in a user's voice state (leave voice channel, join voice channel, mute, deafen, etc); @@ -26,16 +28,16 @@ pub struct VoiceStateUpdate { pub state: VoiceState, } -impl WebSocketEvent for VoiceStateUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// See ; /// /// Received to indicate which voice endpoint, token and guild_id to use; pub struct VoiceServerUpdate { pub token: String, - pub guild_id: Snowflake, + /// The guild this voice server update is for + pub guild_id: Option, + /// The private channel this voice server update is for + pub channel_id: Option, pub endpoint: Option, } -impl WebSocketEvent for VoiceServerUpdate {} diff --git a/src/types/events/voice_gateway/client_connect.rs b/src/types/events/voice_gateway/client_connect.rs new file mode 100644 index 0000000..b5cbc78 --- /dev/null +++ b/src/types/events/voice_gateway/client_connect.rs @@ -0,0 +1,38 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] +/// Sent when another user connects to the voice server. +/// +/// Contains the user id and "flags". +/// +/// Not documented anywhere, if you know what this is, please reach out +/// +/// {"op":18,"d":{"user_id":"1234567890","flags":2}} +pub struct VoiceClientConnectFlags { + pub user_id: Snowflake, + // Likely some sort of bitflags + // + // Not always sent, sometimes null? + pub flags: Option, +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] +/// Sent when another user connects to the voice server. +/// +/// Contains the user id and "platform". +/// +/// Not documented anywhere, if you know what this is, please reach out +/// +/// {"op":20,"d":{"user_id":"1234567890","platform":0}} +pub struct VoiceClientConnectPlatform { + pub user_id: Snowflake, + // Likely an enum + pub platform: u8, +} + diff --git a/src/types/events/voice_gateway/client_disconnect.rs b/src/types/events/voice_gateway/client_disconnect.rs new file mode 100644 index 0000000..3b6b201 --- /dev/null +++ b/src/types/events/voice_gateway/client_disconnect.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] +/// Sent when another user disconnects from the voice server. +/// +/// When received, the SSRC of the user should be discarded. +/// +/// See +pub struct VoiceClientDisconnection { + pub user_id: Snowflake, +} + diff --git a/src/types/events/voice_gateway/hello.rs b/src/types/events/voice_gateway/hello.rs new file mode 100644 index 0000000..2bd8c72 --- /dev/null +++ b/src/types/events/voice_gateway/hello.rs @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] +/// Contains info on how often the client should send heartbeats to the server; +/// +/// Differs from the normal hello data in that discord sends heartbeat interval as a float. +/// +/// See +pub struct VoiceHelloData { + /// The voice gateway version. + /// + /// Note: no idea why this is sent, we already specify the version when establishing a connection. + #[serde(rename = "v")] + pub version: u8, + /// How often a client should send heartbeats, in milliseconds + pub heartbeat_interval: f64, +} + diff --git a/src/types/events/voice_gateway/identify.rs b/src/types/events/voice_gateway/identify.rs new file mode 100644 index 0000000..383aabb --- /dev/null +++ b/src/types/events/voice_gateway/identify.rs @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] +/// The identify payload for the voice gateway connection; +/// +/// Contains authentication info and context to authenticate to the voice gateway. +/// +/// See +pub struct VoiceIdentify { + /// The ID of the guild or the private channel being connected to + pub server_id: Snowflake, + pub user_id: Snowflake, + pub session_id: String, + pub token: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub video: Option, + // TODO: Add video streams +} + diff --git a/src/types/events/voice_gateway/media_sink_wants.rs b/src/types/events/voice_gateway/media_sink_wants.rs new file mode 100644 index 0000000..be6a497 --- /dev/null +++ b/src/types/events/voice_gateway/media_sink_wants.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] +/// What does this do? +/// +/// {"op":15,"d":{"any":100}} +/// +/// Opcode from +pub struct VoiceMediaSinkWants { + pub any: u16, +} + diff --git a/src/types/events/voice_gateway/mod.rs b/src/types/events/voice_gateway/mod.rs new file mode 100644 index 0000000..bb632e2 --- /dev/null +++ b/src/types/events/voice_gateway/mod.rs @@ -0,0 +1,168 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use super::WebSocketEvent; +use serde::{Deserialize, Serialize}; +use serde_json::{value::RawValue, Value}; + +pub use client_connect::*; +pub use client_disconnect::*; +pub use hello::*; +pub use identify::*; +pub use media_sink_wants::*; +pub use ready::*; +pub use select_protocol::*; +pub use session_description::*; +pub use speaking::*; +pub use ssrc_definition::*; +pub use voice_backend_version::*; + +mod client_connect; +mod client_disconnect; +mod hello; +mod identify; +mod media_sink_wants; +mod ready; +mod select_protocol; +mod session_description; +mod speaking; +mod ssrc_definition; +mod voice_backend_version; + +#[derive(Debug, Default, Serialize, Clone, WebSocketEvent)] +/// The payload used for sending events to the voice gateway. +/// +/// Similar to [VoiceGatewayReceivePayload], except we send a [Value] for d whilst we receive a [serde_json::value::RawValue] +pub struct VoiceGatewaySendPayload { + #[serde(rename = "op")] + pub op_code: u8, + + #[serde(rename = "d")] + pub data: Value, +} + +#[derive(Debug, Deserialize, Clone)] +/// The payload used for receiving events from the voice gateway. +/// +/// Note that this is similar to the regular gateway, except we no longer have s or t +/// +/// Similar to [VoiceGatewaySendPayload], except we send a [Value] for d whilst we receive a [serde_json::value::RawValue] +pub struct VoiceGatewayReceivePayload<'a> { + #[serde(rename = "op")] + pub op_code: u8, + + #[serde(borrow)] + #[serde(rename = "d")] + pub data: &'a RawValue, +} + +impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {} + +/// The modes of encryption available in voice UDP connections; +/// +/// Not all encryption modes are implemented; it is generally recommended +/// to use either [[VoiceEncryptionMode::Xsalsa20Poly1305]] or +/// [[VoiceEncryptionMode::Xsalsa20Poly1305Suffix]] +/// +/// See and +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum VoiceEncryptionMode { + #[default] + // Officially Documented + /// Use XSalsa20Poly1305 encryption, using the rtp header as a nonce. + /// + /// Fully implemented + Xsalsa20Poly1305, + /// Use XSalsa20Poly1305 encryption, using a random 24 byte suffix as a nonce. + /// + /// Fully implemented + Xsalsa20Poly1305Suffix, + /// Use XSalsa20Poly1305 encryption, using a 4 byte incremental value as a nonce. + /// + /// Fully implemented + Xsalsa20Poly1305Lite, + // Officially Undocumented + /// Not implemented yet, we have no idea what the rtpsize nonces are. + Xsalsa20Poly1305LiteRtpsize, + /// Not implemented yet, we have no idea what the nonce is. + AeadAes256Gcm, + /// Not implemented yet, we have no idea what the rtpsize nonces are. + AeadAes256GcmRtpsize, + /// Not implemented yet, we have no idea what the rtpsize nonces are. + AeadXchacha20Poly1305Rtpsize, +} + +impl VoiceEncryptionMode { + /// Returns whether this encryption mode uses Xsalsa20Poly1305 encryption. + pub fn is_xsalsa20_poly1305(&self) -> bool { + matches!( + *self, + VoiceEncryptionMode::Xsalsa20Poly1305 + | VoiceEncryptionMode::Xsalsa20Poly1305Lite + | VoiceEncryptionMode::Xsalsa20Poly1305Suffix + | VoiceEncryptionMode::Xsalsa20Poly1305LiteRtpsize + ) + } + + /// Returns whether this encryption mode uses AeadAes256Gcm encryption. + pub fn is_aead_aes256_gcm(&self) -> bool { + matches!( + *self, + VoiceEncryptionMode::AeadAes256Gcm | VoiceEncryptionMode::AeadAes256GcmRtpsize + ) + } + + /// Returns whether this encryption mode uses AeadXchacha20Poly1305 encryption. + pub fn is_aead_xchacha20_poly1305(&self) -> bool { + *self == VoiceEncryptionMode::AeadXchacha20Poly1305Rtpsize + } +} + +/// The possible audio codecs to use +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum AudioCodec { + #[default] + Opus, +} + +/// The possible video codecs to use +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "UPPERCASE")] +pub enum VideoCodec { + #[default] + VP8, + VP9, + H264, +} + +// The various voice opcodes +pub const VOICE_IDENTIFY: u8 = 0; +pub const VOICE_SELECT_PROTOCOL: u8 = 1; +pub const VOICE_READY: u8 = 2; +pub const VOICE_HEARTBEAT: u8 = 3; +pub const VOICE_SESSION_DESCRIPTION: u8 = 4; +pub const VOICE_SPEAKING: u8 = 5; +pub const VOICE_HEARTBEAT_ACK: u8 = 6; +pub const VOICE_RESUME: u8 = 7; +pub const VOICE_HELLO: u8 = 8; +pub const VOICE_RESUMED: u8 = 9; +pub const VOICE_SSRC_DEFINITION: u8 = 12; +pub const VOICE_CLIENT_DISCONNECT: u8 = 13; +pub const VOICE_SESSION_UPDATE: u8 = 14; + +/// What is this? +/// +/// {"op":15,"d":{"any":100}} +/// +/// Opcode from +pub const VOICE_MEDIA_SINK_WANTS: u8 = 15; +/// See +/// Sent with empty data from the client, the server responds with the voice backend version; +pub const VOICE_BACKEND_VERSION: u8 = 16; + +// These two get simultaenously fired when a user joins, one has flags and one has a platform +pub const VOICE_CLIENT_CONNECT_FLAGS: u8 = 18; +pub const VOICE_CLIENT_CONNECT_PLATFORM: u8 = 20; diff --git a/src/types/events/voice_gateway/ready.rs b/src/types/events/voice_gateway/ready.rs new file mode 100644 index 0000000..833f8d5 --- /dev/null +++ b/src/types/events/voice_gateway/ready.rs @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::net::Ipv4Addr; + +use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +use super::VoiceEncryptionMode; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] +/// The voice gateway's ready event; +/// +/// Gives the user info about the UDP connection IP and port, srrc to use, +/// available encryption modes and other data. +/// +/// Sent in response to an Identify event. +/// +/// See +pub struct VoiceReady { + /// See + pub ssrc: u32, + pub ip: Ipv4Addr, + pub port: u16, + /// The available encryption modes for the UDP connection + pub modes: Vec, + #[serde(default)] + pub experiments: Vec, + // TODO: Add video streams + // Heartbeat interval is also sent, but is "an erroneous field and should be ignored. The correct heartbeat_interval value comes from the Hello payload." +} + +impl Default for VoiceReady { + fn default() -> Self { + VoiceReady { + ssrc: 1, + ip: Ipv4Addr::UNSPECIFIED, + port: 0, + modes: Vec::new(), + experiments: Vec::new(), + } + } +} + diff --git a/src/types/events/voice_gateway/select_protocol.rs b/src/types/events/voice_gateway/select_protocol.rs new file mode 100644 index 0000000..375a12c --- /dev/null +++ b/src/types/events/voice_gateway/select_protocol.rs @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use serde::{Deserialize, Serialize}; + +use super::VoiceEncryptionMode; + +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +/// An event sent by the client to the voice gateway server, +/// detailing what protocol, address and encryption to use; +/// +/// See +pub struct SelectProtocol { + /// The protocol to use. The only option chorus supports is [VoiceProtocol::Udp]. + pub protocol: VoiceProtocol, + pub data: SelectProtocolData, + /// The UUID4 RTC connection ID, used for analytics. + /// + /// Note: Not recommended to set this + pub rtc_connection_id: Option, + // TODO: Add codecs, what is a codec object + /// The possible experiments we want to enable + #[serde(rename = "experiments")] + pub enabled_experiments: Vec, +} + +/// The possible protocol for sending a receiving voice data. +/// +/// See +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum VoiceProtocol { + #[default] + /// Sending data via UDP, documented and the only protocol chorus supports. + Udp, + // Possible value, yet NOT RECOMMENDED, AS CHORUS DOES NOT SUPPORT WEBRTC + //Webrtc, +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone)] +/// The data field of the SelectProtocol Event +/// +/// See +pub struct SelectProtocolData { + /// Our external IP we got from IP discovery + pub address: String, + /// Our external UDP port we got from IP discovery + pub port: u16, + /// The mode of encryption to use + pub mode: VoiceEncryptionMode, +} diff --git a/src/types/events/voice_gateway/session_description.rs b/src/types/events/voice_gateway/session_description.rs new file mode 100644 index 0000000..16b6390 --- /dev/null +++ b/src/types/events/voice_gateway/session_description.rs @@ -0,0 +1,37 @@ +use super::{AudioCodec, VideoCodec, VoiceEncryptionMode}; +use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone, Default, WebSocketEvent)] +/// Event that describes our encryption mode and secret key for encryption +/// +/// See +pub struct SessionDescription { + pub audio_codec: AudioCodec, + pub video_codec: VideoCodec, + pub media_session_id: String, + /// The encryption mode to use + #[serde(rename = "mode")] + pub encryption_mode: VoiceEncryptionMode, + /// The secret key we'll use for encryption + pub secret_key: [u8; 32], + /// The keyframe interval in milliseconds + pub keyframe_interval: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, Default, WebSocketEvent)] +/// Event that might be sent to update session parameters +/// +/// See +pub struct SessionUpdate { + #[serde(rename = "audio_codec")] + pub new_audio_codec: Option, + + #[serde(rename = "video_codec")] + pub new_video_codec: Option, + + #[serde(rename = "media_session_id")] + pub new_media_session_id: Option, +} + diff --git a/src/types/events/voice_gateway/speaking.rs b/src/types/events/voice_gateway/speaking.rs new file mode 100644 index 0000000..64cf2e7 --- /dev/null +++ b/src/types/events/voice_gateway/speaking.rs @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use bitflags::bitflags; +use serde::{Deserialize, Serialize}; + +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; + +/// Event that tells the server we are speaking; +/// +/// Essentially, what allows us to send UDP data and lights up the green circle around your avatar. +/// +/// See +#[derive(Debug, Deserialize, Serialize, Clone, Default, WebSocketEvent)] +pub struct Speaking { + /// Data about the audio we're transmitting. + /// + /// See [SpeakingBitflags] + pub speaking: u8, + pub ssrc: u32, + /// The user id of the speaking user, only sent by the server + #[serde(skip_serializing)] + pub user_id: Option, + /// Delay in milliseconds, not sent by the server + #[serde(default)] + pub delay: u64, +} + +bitflags! { + /// Bitflags of speaking types; + /// + /// See + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] + pub struct SpeakingBitflags: u8 { + /// Whether we'll be transmitting normal voice audio + const MICROPHONE = 1 << 0; + /// Whether we'll be transmitting context audio for video, no speaking indicator + const SOUNDSHARE = 1 << 1; + /// Whether we are a priority speaker, lowering audio of other speakers + const PRIORITY = 1 << 2; + } +} + +impl Default for SpeakingBitflags { + /// Returns the default value for these flags, assuming normal microphone audio and not being a priority speaker + fn default() -> Self { + Self::MICROPHONE + } +} diff --git a/src/types/events/voice_gateway/ssrc_definition.rs b/src/types/events/voice_gateway/ssrc_definition.rs new file mode 100644 index 0000000..69c1336 --- /dev/null +++ b/src/types/events/voice_gateway/ssrc_definition.rs @@ -0,0 +1,53 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +/// Defines an event which provides ssrcs for voice and video for a user id. +/// +/// This event is sent when we begin to speak. +/// +/// It must be sent before sending audio, or else clients will not be able to play the stream. +/// +/// This event is sent via opcode 12. +/// +/// Examples of the event: +/// +/// When receiving: +/// ```json +/// {"op":12,"d":{"video_ssrc":0,"user_id":"463640391196082177","streams":[{"ssrc":26595,"rtx_ssrc":26596,"rid":"100","quality":100,"max_resolution":{"width":1280,"type":"fixed","height":720},"max_framerate":30,"active":false}],"audio_ssrc":26597}}{"op":12,"d":{"video_ssrc":0,"user_id":"463640391196082177","streams":[{"ssrc":26595,"rtx_ssrc":26596,"rid":"100","quality":100,"max_resolution":{"width":1280,"type":"fixed","height":720},"max_framerate":30,"active":false}],"audio_ssrc":26597}} +/// ``` +/// +/// When sending: +/// ```json +/// {"op":12,"d":{"audio_ssrc":2307250864,"video_ssrc":0,"rtx_ssrc":0,"streams":[{"type":"video","rid":"100","ssrc":26595,"active":false,"quality":100,"rtx_ssrc":26596,"max_bitrate":2500000,"max_framerate":30,"max_resolution":{"type":"fixed","width":1280,"height":720}}]}} +/// ``` +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] +pub struct SsrcDefinition { + /// The ssrc used for video communications. + /// + /// Is always sent and received, though is 0 if describing only the audio ssrc. + #[serde(default)] + pub video_ssrc: usize, + /// The ssrc used for audio communications. + /// + /// Is always sent and received, though is 0 if describing only the video ssrc. + #[serde(default)] + pub audio_ssrc: usize, + // Not sure what this is + // It is usually 0 + #[serde(default)] + pub rtx_ssrc: usize, + /// The user id these ssrcs apply to. + /// + /// Is never sent by the user and is filled in by the server + #[serde(skip_serializing)] + pub user_id: Option, + // TODO: Add video streams + #[serde(default)] + pub streams: Vec, +} + diff --git a/src/types/events/voice_gateway/voice_backend_version.rs b/src/types/events/voice_gateway/voice_backend_version.rs new file mode 100644 index 0000000..0e8ce88 --- /dev/null +++ b/src/types/events/voice_gateway/voice_backend_version.rs @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, WebSocketEvent)] +/// Received from the voice gateway server to describe the backend version. +/// +/// See +pub struct VoiceBackendVersion { + /// The voice backend's version + #[serde(rename = "voice")] + pub voice_version: String, + /// The WebRTC worker's version + #[serde(rename = "rtc_worker")] + pub rtc_worker_version: String, +} + diff --git a/src/types/events/webhooks.rs b/src/types/events/webhooks.rs index 518b332..3f09492 100644 --- a/src/types/events/webhooks.rs +++ b/src/types/events/webhooks.rs @@ -1,14 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; -use crate::types::Snowflake; +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -use super::WebSocketEvent; - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct WebhooksUpdate { pub guild_id: Snowflake, pub channel_id: Snowflake, } -impl WebSocketEvent for WebhooksUpdate {} diff --git a/src/types/events/webrtc/identify.rs b/src/types/events/webrtc/identify.rs deleted file mode 100644 index 45f1037..0000000 --- a/src/types/events/webrtc/identify.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::types::{Snowflake, WebSocketEvent}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] -/// The identify payload for the webrtc stream; -/// Contains info to begin a webrtc connection; -/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload; -pub struct VoiceIdentify { - server_id: Snowflake, - user_id: Snowflake, - session_id: String, - token: String, - #[serde(skip_serializing_if = "Option::is_none")] - /// Undocumented field, but is also in discord client comms - video: Option, -} - -impl WebSocketEvent for VoiceIdentify {} diff --git a/src/types/events/webrtc/mod.rs b/src/types/events/webrtc/mod.rs deleted file mode 100644 index ebaf7b2..0000000 --- a/src/types/events/webrtc/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub use identify::*; -pub use ready::*; - -mod identify; -mod ready; diff --git a/src/types/events/webrtc/ready.rs b/src/types/events/webrtc/ready.rs deleted file mode 100644 index 008e41e..0000000 --- a/src/types/events/webrtc/ready.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::net::Ipv4Addr; - -use crate::types::WebSocketEvent; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -/// The ready event for the webrtc stream; -/// Used to give info after the identify event; -/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-ready-payload; -pub struct VoiceReady { - ssrc: i32, - ip: Ipv4Addr, - port: u32, - modes: Vec, - // Heartbeat interval is also sent, but is "an erroneous field and should be ignored. The correct heartbeat_interval value comes from the Hello payload." -} - -impl Default for VoiceReady { - fn default() -> Self { - VoiceReady { - ssrc: 1, - ip: Ipv4Addr::UNSPECIFIED, - port: 0, - modes: Vec::new(), - } - } -} - -impl WebSocketEvent for VoiceReady {} diff --git a/src/types/interfaces/activity.rs b/src/types/interfaces/activity.rs index 0da4747..b042d91 100644 --- a/src/types/interfaces/activity.rs +++ b/src/types/interfaces/activity.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{entities::Emoji, Snowflake}; diff --git a/src/types/interfaces/connected_account.rs b/src/types/interfaces/connected_account.rs index 8b13789..66e0819 100644 --- a/src/types/interfaces/connected_account.rs +++ b/src/types/interfaces/connected_account.rs @@ -1 +1,3 @@ - +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/types/interfaces/guild_welcome_screen.rs b/src/types/interfaces/guild_welcome_screen.rs index dbeef0f..1646ca1 100644 --- a/src/types/interfaces/guild_welcome_screen.rs +++ b/src/types/interfaces/guild_welcome_screen.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/interfaces/interaction.rs b/src/types/interfaces/interaction.rs index 2aa29fd..f88eaf9 100644 --- a/src/types/interfaces/interaction.rs +++ b/src/types/interfaces/interaction.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/src/types/interfaces/mod.rs b/src/types/interfaces/mod.rs index 946eb39..b5742d6 100644 --- a/src/types/interfaces/mod.rs +++ b/src/types/interfaces/mod.rs @@ -1,3 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused_imports)] pub use activity::*; pub use connected_account::*; pub use guild_welcome_screen::*; diff --git a/src/types/interfaces/status.rs b/src/types/interfaces/status.rs index d5c07b6..fa5f821 100644 --- a/src/types/interfaces/status.rs +++ b/src/types/interfaces/status.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 6f06ef0..8eee13a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! All the types, entities, events and interfaces of the Spacebar API. pub use config::*; diff --git a/src/types/schema/apierror.rs b/src/types/schema/apierror.rs index 95e72a5..0dd1f6f 100644 --- a/src/types/schema/apierror.rs +++ b/src/types/schema/apierror.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + #[cfg(feature = "poem")] use poem::{http::StatusCode, IntoResponse, Response}; use serde_json::{json, Value}; diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs index 2159de9..2796805 100644 --- a/src/types/schema/auth.rs +++ b/src/types/schema/auth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index 354459c..1502f97 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use serde::{Deserialize, Serialize}; diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index 56c0f9e..2e29ce0 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -34,6 +38,8 @@ pub struct GuildBanCreateSchema { #[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)] #[serde(rename_all = "snake_case")] +/// Represents the schema used to modify a guild. +/// See: pub struct GuildModifySchema { pub name: Option, pub icon: Option>, @@ -43,6 +49,7 @@ pub struct GuildModifySchema { pub discovery_splash: Option>, pub owner_id: Option, pub description: Option, + /// Deprecated pub region: Option, pub afk_channel_id: Option, pub afk_timeout: Option, @@ -52,6 +59,9 @@ pub struct GuildModifySchema { pub features: Option>, pub system_channel_id: Option, pub system_channel_flags: Option, + /// If set to Some(1), will create a new #rules channel + /// + /// Reference: pub rules_channel_id: Option, pub public_updates_channel_id: Option, pub safety_alerts_channel_id: Option, @@ -78,7 +88,7 @@ impl std::default::Default for GetUserGuildSchema { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] pub struct GuildPreview { pub id: Snowflake, pub name: String, diff --git a/src/types/schema/message.rs b/src/types/schema/message.rs index 4e34910..7551b6b 100644 --- a/src/types/schema/message.rs +++ b/src/types/schema/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::entities::{ diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs index 08dae05..d353e09 100644 --- a/src/types/schema/mod.rs +++ b/src/types/schema/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use apierror::*; pub use auth::*; pub use channel::*; diff --git a/src/types/schema/relationship.rs b/src/types/schema/relationship.rs index b0a60d6..54145ae 100644 --- a/src/types/schema/relationship.rs +++ b/src/types/schema/relationship.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::RelationshipType; diff --git a/src/types/schema/role.rs b/src/types/schema/role.rs index 284f506..168d999 100644 --- a/src/types/schema/role.rs +++ b/src/types/schema/role.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index 5584cf4..7d21754 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::collections::HashMap; use serde::{Deserialize, Serialize}; diff --git a/src/types/utils/jwt.rs b/src/types/utils/jwt.rs index ca0aebb..6addb4c 100644 --- a/src/types/utils/jwt.rs +++ b/src/types/utils/jwt.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::utils::Snowflake; use jsonwebtoken::{encode, EncodingKey, Header}; use serde::{Deserialize, Serialize}; diff --git a/src/types/utils/mod.rs b/src/types/utils/mod.rs index 1b1b3b6..8879688 100644 --- a/src/types/utils/mod.rs +++ b/src/types/utils/mod.rs @@ -1,3 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused_imports)] pub use regexes::*; pub use rights::Rights; pub use snowflake::Snowflake; diff --git a/src/types/utils/regexes.rs b/src/types/utils/regexes.rs index 0f160eb..f854b4f 100644 --- a/src/types/utils/regexes.rs +++ b/src/types/utils/regexes.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use lazy_static::lazy_static; use regex::Regex; diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs index 73ae5f7..5f44de3 100644 --- a/src/types/utils/rights.rs +++ b/src/types/utils/rights.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; bitflags! { diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs index b4e1d9e..1582085 100644 --- a/src/types/utils/snowflake.rs +++ b/src/types/utils/snowflake.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::{ fmt::Display, sync::atomic::{AtomicUsize, Ordering}, diff --git a/src/voice.rs b/src/voice.rs deleted file mode 100644 index c2fcaf2..0000000 --- a/src/voice.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Where the voice chat implementation will be, once it's finished. -//! For development on voice, see the feature/voice branch. diff --git a/src/voice/crypto.rs b/src/voice/crypto.rs new file mode 100644 index 0000000..b59ce98 --- /dev/null +++ b/src/voice/crypto.rs @@ -0,0 +1,90 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Defines cryptography functions used within the voice implementation. +//! +//! All functions in this module return a 24 byte long `Vec`. + +/// Gets an `xsalsa20_poly1305` nonce from an rtppacket. +/// +/// See +pub(crate) fn get_xsalsa20_poly1305_nonce(packet: &[u8]) -> Vec { + let mut rtp_header = Vec::with_capacity(24); + rtp_header.append(&mut packet[0..12].to_vec()); + + // The header is only 12 bytes, but the nonce has to be 24 + while rtp_header.len() < 24 { + rtp_header.push(0); + } + + rtp_header +} + +/// Gets an `xsalsa20_poly1305_suffix` nonce from an rtppacket. +/// +/// See +pub(crate) fn get_xsalsa20_poly1305_suffix_nonce(packet: &[u8]) -> Vec { + let mut nonce = Vec::with_capacity(24); + + nonce.append(&mut packet[(packet.len() - 24)..packet.len()].to_vec()); + + nonce +} + +/// Gets an `xsalsa20_poly1305_lite` nonce from an rtppacket. +/// +/// See +pub(crate) fn get_xsalsa20_poly1305_lite_nonce(packet: &[u8]) -> Vec { + let mut nonce = Vec::with_capacity(24); + + nonce.append(&mut packet[(packet.len() - 4)..packet.len()].to_vec()); + + // The suffix is only 4 bytes, but the nonce has to be 24 + while nonce.len() < 24 { + nonce.push(0); + } + + nonce +} + +#[test] +// Asserts all functions that retrieve a nonce from packet bytes +fn test_packet_nonce_derives() { + let test_packet_bytes = vec![ + 144, 120, 98, 5, 71, 174, 52, 64, 0, 4, 85, 36, 178, 8, 37, 146, 35, 154, 141, 36, 125, 15, + 65, 179, 227, 108, 165, 56, 68, 68, 3, 62, 87, 233, 7, 81, 147, 93, 22, 95, 115, 202, 48, + 66, 190, 229, 69, 146, 66, 108, 60, 114, 2, 228, 111, 40, 108, 5, 68, 226, 76, 240, 20, + 231, 210, 214, 123, 175, 188, 161, 10, 125, 13, 196, 114, 248, 50, 84, 103, 139, 86, 223, + 82, 173, 8, 209, 78, 188, 169, 151, 157, 42, 189, 153, 228, 105, 199, 19, 185, 16, 33, 133, + 113, 253, 145, 36, 106, 14, 222, 128, 226, 239, 10, 39, 72, 113, 33, 113, + ]; + + let nonce_1 = get_xsalsa20_poly1305_nonce(&test_packet_bytes); + let nonce_1_expected = vec![ + 144, 120, 98, 5, 71, 174, 52, 64, 0, 4, 85, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let nonce_2 = get_xsalsa20_poly1305_suffix_nonce(&test_packet_bytes); + let nonce_2_expected = vec![ + 228, 105, 199, 19, 185, 16, 33, 133, 113, 253, 145, 36, 106, 14, 222, 128, 226, 239, 10, + 39, 72, 113, 33, 113, + ]; + + let nonce_3 = get_xsalsa20_poly1305_lite_nonce(&test_packet_bytes); + let nonce_3_expected = vec![ + 72, 113, 33, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + println!("nonce 1: {:?}", nonce_1); + println!("nonce 2: {:?}", nonce_2); + println!("nonce 3: {:?}", nonce_3); + + assert_eq!(nonce_1.len(), 24); + assert_eq!(nonce_2.len(), 24); + assert_eq!(nonce_3.len(), 24); + + assert_eq!(nonce_1, nonce_1_expected); + assert_eq!(nonce_2, nonce_2_expected); + assert_eq!(nonce_3, nonce_3_expected); +} diff --git a/src/voice/gateway/backends/mod.rs b/src/voice/gateway/backends/mod.rs new file mode 100644 index 0000000..7f3f3dd --- /dev/null +++ b/src/voice/gateway/backends/mod.rs @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub mod tungstenite; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub use tungstenite::*; + +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub mod wasm; +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub use wasm::*; + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub type Sink = tungstenite::TungsteniteSink; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub type Stream = tungstenite::TungsteniteStream; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub type WebSocketBackend = tungstenite::TungsteniteBackend; + +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub type Sink = wasm::WasmSink; +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub type Stream = wasm::WasmStream; +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub type WebSocketBackend = wasm::WasmBackend; diff --git a/src/voice/gateway/backends/tungstenite.rs b/src/voice/gateway/backends/tungstenite.rs new file mode 100644 index 0000000..26cc0fe --- /dev/null +++ b/src/voice/gateway/backends/tungstenite.rs @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, +}; + +use crate::{errors::VoiceGatewayError, voice::gateway::VoiceGatewayMessage}; + +#[derive(Debug, Clone)] +pub struct TungsteniteBackend; + +// These could be made into inherent associated types when that's stabilized +pub type TungsteniteSink = + SplitSink>, tungstenite::Message>; +pub type TungsteniteStream = SplitStream>>; + +impl TungsteniteBackend { + pub async fn connect( + websocket_url: &str, + ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::VoiceGatewayError> { + let mut roots = rustls::RootCertStore::empty(); + let certs = rustls_native_certs::load_native_certs(); + + if let Err(e) = certs { + log::error!("Failed to load platform native certs! {:?}", e); + return Err(VoiceGatewayError::CannotConnect { + error: format!("{:?}", e), + }); + } + + for cert in certs.unwrap() { + roots.add(&rustls::Certificate(cert.0)).unwrap(); + } + let (websocket_stream, _) = match connect_async_tls_with_config( + websocket_url, + None, + false, + Some(Connector::Rustls( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth() + .into(), + )), + ) + .await + { + Ok(websocket_stream) => websocket_stream, + Err(e) => { + return Err(VoiceGatewayError::CannotConnect { + error: e.to_string(), + }) + } + }; + + Ok(websocket_stream.split()) + } +} + +impl From for tungstenite::Message { + fn from(message: VoiceGatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for VoiceGatewayMessage { + fn from(value: tungstenite::Message) -> Self { + Self(value.to_string()) + } +} diff --git a/src/voice/gateway/backends/wasm.rs b/src/voice/gateway/backends/wasm.rs new file mode 100644 index 0000000..a39723e --- /dev/null +++ b/src/voice/gateway/backends/wasm.rs @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; + +use ws_stream_wasm::*; + +use crate::errors::VoiceGatewayError; +use crate::voice::gateway::VoiceGatewayMessage; + +#[derive(Debug, Clone)] +pub struct WasmBackend; + +// These could be made into inherent associated types when that's stabilized +pub type WasmSink = SplitSink; +pub type WasmStream = SplitStream; + +impl WasmBackend { + pub async fn connect(websocket_url: &str) -> Result<(WasmSink, WasmStream), VoiceGatewayError> { + let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { + Ok(stream) => Ok(stream), + Err(e) => Err(VoiceGatewayError::CannotConnect { + error: e.to_string(), + }), + }?; + + Ok(websocket_stream.split()) + } +} + +impl From for WsMessage { + fn from(message: VoiceGatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for VoiceGatewayMessage { + fn from(value: WsMessage) -> Self { + match value { + WsMessage::Text(text) => Self(text), + WsMessage::Binary(bin) => { + let mut text = String::new(); + let _ = bin.iter().map(|v| text.push_str(&v.to_string())); + Self(text) + } + } + } +} diff --git a/src/voice/gateway/events.rs b/src/voice/gateway/events.rs new file mode 100644 index 0000000..af043b3 --- /dev/null +++ b/src/voice/gateway/events.rs @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::{ + errors::VoiceGatewayError, + gateway::GatewayEvent, + types::{ + SessionDescription, SessionUpdate, Speaking, SsrcDefinition, VoiceBackendVersion, + VoiceClientConnectFlags, VoiceClientConnectPlatform, VoiceClientDisconnection, + VoiceMediaSinkWants, VoiceReady, + }, +}; + +#[derive(Default, Debug)] +pub struct VoiceEvents { + pub voice_ready: GatewayEvent, + pub backend_version: GatewayEvent, + pub session_description: GatewayEvent, + pub session_update: GatewayEvent, + pub speaking: GatewayEvent, + pub ssrc_definition: GatewayEvent, + pub client_disconnect: GatewayEvent, + pub client_connect_flags: GatewayEvent, + pub client_connect_platform: GatewayEvent, + pub media_sink_wants: GatewayEvent, + pub error: GatewayEvent, +} diff --git a/src/voice/gateway/gateway.rs b/src/voice/gateway/gateway.rs new file mode 100644 index 0000000..4727ae4 --- /dev/null +++ b/src/voice/gateway/gateway.rs @@ -0,0 +1,349 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{sync::Arc, time::Duration}; + +use log::*; + +use tokio::sync::Mutex; + +use futures_util::SinkExt; +use futures_util::StreamExt; + +use crate::{ + errors::VoiceGatewayError, + gateway::GatewayEvent, + types::{ + VoiceGatewayReceivePayload, VoiceHelloData, WebSocketEvent, VOICE_BACKEND_VERSION, + VOICE_CLIENT_CONNECT_FLAGS, VOICE_CLIENT_CONNECT_PLATFORM, VOICE_CLIENT_DISCONNECT, + VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK, VOICE_HELLO, VOICE_IDENTIFY, VOICE_MEDIA_SINK_WANTS, + VOICE_READY, VOICE_RESUME, VOICE_SELECT_PROTOCOL, VOICE_SESSION_DESCRIPTION, + VOICE_SESSION_UPDATE, VOICE_SPEAKING, VOICE_SSRC_DEFINITION, + }, + voice::gateway::{ + heartbeat::VoiceHeartbeatThreadCommunication, VoiceGatewayMessage, WebSocketBackend, + }, +}; + +use super::{ + events::VoiceEvents, heartbeat::VoiceHeartbeatHandler, Sink, Stream, VoiceGatewayHandle, +}; + +#[derive(Debug)] +pub struct VoiceGateway { + events: Arc>, + heartbeat_handler: VoiceHeartbeatHandler, + websocket_send: Arc>, + websocket_receive: Stream, + kill_send: tokio::sync::broadcast::Sender<()>, + kill_receive: tokio::sync::broadcast::Receiver<()>, +} + +impl VoiceGateway { + #[allow(clippy::new_ret_no_self)] + pub async fn spawn(websocket_url: String) -> Result { + // Append the needed things to the websocket url + let processed_url = format!("wss://{}/?v=7", websocket_url); + trace!("Created voice socket url: {}", processed_url.clone()); + + let (websocket_send, mut websocket_receive) = + WebSocketBackend::connect(&processed_url).await?; + + let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); + + // Create a shared broadcast channel for killing all gateway tasks + let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); + + // Wait for the first hello and then spawn both tasks so we avoid nested tasks + // This automatically spawns the heartbeat task, but from the main thread + #[cfg(not(target_arch = "wasm32"))] + let msg: VoiceGatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + #[cfg(target_arch = "wasm32")] + let msg: VoiceGatewayMessage = websocket_receive.next().await.unwrap().into(); + let gateway_payload: VoiceGatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); + + if gateway_payload.op_code != VOICE_HELLO { + return Err(VoiceGatewayError::NonHelloOnInitiate { + opcode: gateway_payload.op_code, + }); + } + + info!("VGW: Received Hello"); + + // The hello data for voice gateways is in float milliseconds, so we convert it to f64 seconds + let gateway_hello: VoiceHelloData = + serde_json::from_str(gateway_payload.data.get()).unwrap(); + let heartbeat_interval_seconds: f64 = gateway_hello.heartbeat_interval / 1000.0; + + let voice_events = VoiceEvents::default(); + let shared_events = Arc::new(Mutex::new(voice_events)); + + let mut gateway = VoiceGateway { + events: shared_events.clone(), + heartbeat_handler: VoiceHeartbeatHandler::new( + Duration::from_secs_f64(heartbeat_interval_seconds), + 1, // to:do actually compute nonce + shared_websocket_send.clone(), + kill_send.subscribe(), + ), + websocket_send: shared_websocket_send.clone(), + websocket_receive, + kill_send: kill_send.clone(), + kill_receive: kill_send.subscribe(), + }; + + // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + #[cfg(not(target_arch = "wasm32"))] + tokio::task::spawn(async move { + gateway.gateway_listen_task().await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + gateway.gateway_listen_task().await; + }); + + Ok(VoiceGatewayHandle { + url: websocket_url.clone(), + events: shared_events, + websocket_send: shared_websocket_send.clone(), + kill_send: kill_send.clone(), + }) + } + + /// The main gateway listener task; + pub async fn gateway_listen_task(&mut self) { + loop { + let msg; + + tokio::select! { + Ok(_) = self.kill_receive.recv() => { + log::trace!("VGW: Closing listener task"); + break; + } + message = self.websocket_receive.next() => { + msg = message; + } + } + + // PRETTYFYME: Remove inline conditional compiling + #[cfg(not(target_arch = "wasm32"))] + if let Some(Ok(message)) = msg { + self.handle_message(message.into()).await; + continue; + } + #[cfg(target_arch = "wasm32")] + if let Some(message) = msg { + self.handle_message(message.into()).await; + continue; + } + + // We couldn't receive the next message or it was an error, something is wrong with the websocket, close + warn!("VGW: Websocket is broken, stopping gateway"); + break; + } + } + + /// Closes the websocket connection and stops all tasks + async fn close(&mut self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } + + /// Deserializes and updates a dispatched event, when we already know its type; + /// (Called for every event in handle_message) + async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( + data: &'a str, + event: &mut GatewayEvent, + ) -> Result<(), serde_json::Error> { + let data_deserialize_result: Result = serde_json::from_str(data); + + if data_deserialize_result.is_err() { + return Err(data_deserialize_result.err().unwrap()); + } + + event.notify(data_deserialize_result.unwrap()).await; + Ok(()) + } + + /// This handles a message as a websocket event and updates its events along with the events' observers + pub async fn handle_message(&mut self, msg: VoiceGatewayMessage) { + if msg.0.is_empty() { + return; + } + + let Ok(gateway_payload) = msg.payload() else { + if let Some(error) = msg.error() { + warn!("GW: Received error {:?}, connection will close..", error); + self.close().await; + self.events.lock().await.error.notify(error).await; + } else { + warn!( + "Message unrecognised: {:?}, please open an issue on the chorus github", + msg.0 + ); + } + return; + }; + + // See + match gateway_payload.op_code { + VOICE_READY => { + trace!("VGW: Received READY!"); + + let event = &mut self.events.lock().await.voice_ready; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!("Failed to parse VOICE_READY ({})", result.err().unwrap()); + } + } + VOICE_BACKEND_VERSION => { + trace!("VGW: Received Backend Version"); + + let event = &mut self.events.lock().await.backend_version; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_BACKEND_VERSION ({})", + result.err().unwrap() + ); + } + } + VOICE_SESSION_DESCRIPTION => { + trace!("VGW: Received Session Description"); + + let event = &mut self.events.lock().await.session_description; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SESSION_DESCRIPTION ({})", + result.err().unwrap() + ); + } + } + VOICE_SESSION_UPDATE => { + trace!("VGW: Received Session Update"); + + let event = &mut self.events.lock().await.session_update; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SESSION_UPDATE ({})", + result.err().unwrap() + ); + } + } + VOICE_SPEAKING => { + trace!("VGW: Received Speaking"); + + let event = &mut self.events.lock().await.speaking; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!("Failed to parse VOICE_SPEAKING ({})", result.err().unwrap()); + } + } + VOICE_SSRC_DEFINITION => { + trace!("VGW: Received Ssrc Definition"); + + let event = &mut self.events.lock().await.ssrc_definition; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SSRC_DEFINITION ({})", + result.err().unwrap() + ); + } + } + VOICE_CLIENT_DISCONNECT => { + trace!("VGW: Received Client Disconnect"); + + let event = &mut self.events.lock().await.client_disconnect; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_DISCONNECT ({})", + result.err().unwrap() + ); + } + } + VOICE_CLIENT_CONNECT_FLAGS => { + trace!("VGW: Received Client Connect Flags"); + + let event = &mut self.events.lock().await.client_connect_flags; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_CONNECT_FLAGS ({})", + result.err().unwrap() + ); + } + } + VOICE_CLIENT_CONNECT_PLATFORM => { + trace!("VGW: Received Client Connect Platform"); + + let event = &mut self.events.lock().await.client_connect_platform; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_CONNECT_PLATFORM ({})", + result.err().unwrap() + ); + } + } + VOICE_MEDIA_SINK_WANTS => { + trace!("VGW: Received Media Sink Wants"); + + let event = &mut self.events.lock().await.media_sink_wants; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_MEDIA_SINK_WANTS ({})", + result.err().unwrap() + ); + } + } + // 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." + VOICE_HEARTBEAT => { + trace!("VGW: Received Heartbeat // Heartbeat Request"); + + // Tell the heartbeat handler it should send a heartbeat right away + let heartbeat_communication = VoiceHeartbeatThreadCommunication { + updated_nonce: None, + op_code: Some(VOICE_HEARTBEAT), + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + VOICE_HEARTBEAT_ACK => { + trace!("VGW: Received Heartbeat ACK"); + + // Tell the heartbeat handler we received an ack + + let heartbeat_communication = VoiceHeartbeatThreadCommunication { + updated_nonce: None, + op_code: Some(VOICE_HEARTBEAT_ACK), + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + VOICE_IDENTIFY | VOICE_SELECT_PROTOCOL | VOICE_RESUME => { + info!( + "VGW: Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", + gateway_payload.op_code + ); + } + _ => { + warn!("VGW: Received unrecognized voice gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); + } + } + } +} diff --git a/src/voice/gateway/handle.rs b/src/voice/gateway/handle.rs new file mode 100644 index 0000000..b48080a --- /dev/null +++ b/src/voice/gateway/handle.rs @@ -0,0 +1,105 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::sync::Arc; + +use log::*; + +use futures_util::SinkExt; + +use serde_json::json; +use tokio::sync::Mutex; + +use crate::types::{ + SelectProtocol, Speaking, SsrcDefinition, VoiceGatewaySendPayload, VoiceIdentify, + VOICE_BACKEND_VERSION, VOICE_IDENTIFY, VOICE_SELECT_PROTOCOL, VOICE_SPEAKING, + VOICE_SSRC_DEFINITION, +}; + +use super::{events::VoiceEvents, Sink, VoiceGatewayMessage}; + +/// Represents a handle to a Voice Gateway connection. +/// Using this handle you can send Gateway Events directly. +#[derive(Debug, Clone)] +pub struct VoiceGatewayHandle { + pub url: String, + pub events: Arc>, + pub websocket_send: Arc>, + /// Tells gateway tasks to close + pub(super) kill_send: tokio::sync::broadcast::Sender<()>, +} + +impl VoiceGatewayHandle { + /// Sends json to the gateway with an opcode + async fn send_json(&self, op_code: u8, to_send: serde_json::Value) { + let gateway_payload = VoiceGatewaySendPayload { + op_code, + data: to_send, + }; + + let payload_json = serde_json::to_string(&gateway_payload).unwrap(); + let message = VoiceGatewayMessage(payload_json); + + self.websocket_send + .lock() + .await + .send(message.into()) + .await + .unwrap(); + } + + /// Sends a voice identify event to the gateway + pub async fn send_identify(&self, to_send: VoiceIdentify) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending Identify.."); + + self.send_json(VOICE_IDENTIFY, to_send_value).await; + } + + /// Sends a select protocol event to the gateway + pub async fn send_select_protocol(&self, to_send: SelectProtocol) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending Select Protocol"); + + self.send_json(VOICE_SELECT_PROTOCOL, to_send_value).await; + } + + /// Sends a speaking event to the gateway + pub async fn send_speaking(&self, to_send: Speaking) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending Speaking"); + + self.send_json(VOICE_SPEAKING, to_send_value).await; + } + + /// Sends an ssrc definition event + pub async fn send_ssrc_definition(&self, to_send: SsrcDefinition) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending SsrcDefinition"); + + self.send_json(VOICE_SSRC_DEFINITION, to_send_value).await; + } + + /// Sends a voice backend version request to the gateway + pub async fn send_voice_backend_version_request(&self) { + let data_empty_object = json!("{}"); + + trace!("VGW: Requesting voice backend version"); + + self.send_json(VOICE_BACKEND_VERSION, data_empty_object) + .await; + } + + /// Closes the websocket connection and stops all gateway tasks; + /// + /// Essentially pulls the plug on the voice gateway, leaving it possible to resume; + pub async fn close(&self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } +} diff --git a/src/voice/gateway/heartbeat.rs b/src/voice/gateway/heartbeat.rs new file mode 100644 index 0000000..2b9fde5 --- /dev/null +++ b/src/voice/gateway/heartbeat.rs @@ -0,0 +1,174 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures_util::SinkExt; +use log::*; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::Instant; +#[cfg(target_arch = "wasm32")] +use wasmtimer::std::Instant; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep_until; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep_until; + +use std::{sync::Arc, time::Duration}; + +use tokio::sync::{ + mpsc::{Receiver, Sender}, + Mutex, +}; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::task; + +use crate::{ + gateway::heartbeat::HEARTBEAT_ACK_TIMEOUT, + types::{VoiceGatewaySendPayload, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK}, + voice::gateway::VoiceGatewayMessage, +}; + +use super::Sink; + +/// Handles sending heartbeats to the voice gateway in another thread +#[allow(dead_code)] // FIXME: Remove this, once all fields of VoiceHeartbeatHandler are used +#[derive(Debug)] +pub(super) struct VoiceHeartbeatHandler { + /// The heartbeat interval in milliseconds + pub heartbeat_interval: Duration, + /// The send channel for the heartbeat thread + pub send: Sender, +} + +impl VoiceHeartbeatHandler { + pub fn new( + heartbeat_interval: Duration, + starting_nonce: u64, + websocket_tx: Arc>, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> Self { + let (send, receive) = tokio::sync::mpsc::channel(32); + let kill_receive = kill_rc.resubscribe(); + + #[cfg(not(target_arch = "wasm32"))] + task::spawn(async move { + Self::heartbeat_task( + websocket_tx, + heartbeat_interval, + starting_nonce, + receive, + kill_receive, + ) + .await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + Self::heartbeat_task( + websocket_tx, + heartbeat_interval, + starting_nonce, + receive, + kill_receive, + ) + .await; + }); + + Self { + heartbeat_interval, + send, + } + } + + /// The main heartbeat task; + /// + /// Can be killed by the kill broadcast; + /// If the websocket is closed, will die out next time it tries to send a heartbeat; + pub async fn heartbeat_task( + websocket_tx: Arc>, + heartbeat_interval: Duration, + starting_nonce: u64, + mut receive: Receiver, + mut kill_receive: tokio::sync::broadcast::Receiver<()>, + ) { + let mut last_heartbeat_timestamp: Instant = Instant::now(); + let mut last_heartbeat_acknowledged = true; + let mut nonce: u64 = starting_nonce; + + loop { + 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 mut should_send = false; + + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; + } + Some(communication) = receive.recv() => { + // If we received a nonce update, use that nonce now + if communication.updated_nonce.is_some() { + nonce = communication.updated_nonce.unwrap(); + } + + if let Some(op_code) = communication.op_code { + match op_code { + VOICE_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; + } + VOICE_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} + } + } + } + Ok(_) = kill_receive.recv() => { + log::trace!("VGW: Closing heartbeat task"); + break; + } + } + + if should_send { + trace!("VGW: Sending Heartbeat.."); + + let heartbeat = VoiceGatewaySendPayload { + op_code: VOICE_HEARTBEAT, + data: nonce.into(), + }; + + let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); + + let msg = VoiceGatewayMessage(heartbeat_json); + + let send_result = websocket_tx.lock().await.send(msg.into()).await; + if send_result.is_err() { + // We couldn't send, the websocket is broken + warn!("VGW: Couldnt send heartbeat, websocket seems broken"); + break; + } + + last_heartbeat_timestamp = Instant::now(); + last_heartbeat_acknowledged = false; + } + } + } +} + +/// Used for communications between the voice heartbeat and voice gateway thread. +/// Either signifies a nonce update, a heartbeat ACK or a Heartbeat request by the server +#[derive(Clone, Copy, Debug)] +pub(super) struct VoiceHeartbeatThreadCommunication { + /// The opcode for the communication we received, if relevant + pub(super) op_code: Option, + /// The new nonce to use, if any + pub(super) updated_nonce: Option, +} diff --git a/src/voice/gateway/message.rs b/src/voice/gateway/message.rs new file mode 100644 index 0000000..4b40f35 --- /dev/null +++ b/src/voice/gateway/message.rs @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::{errors::VoiceGatewayError, types::VoiceGatewayReceivePayload}; + +/// Represents a message received from the voice websocket connection. +/// +/// This will be either a [VoiceGatewayReceivePayload], containing voice gateway events, or a [VoiceGatewayError]. +/// +/// This struct is used internally when handling messages. +#[derive(Clone, Debug)] +pub struct VoiceGatewayMessage(pub String); + +impl VoiceGatewayMessage { + /// Parses the message as an error; + /// Returns the error if successfully parsed, None if the message isn't an error + pub fn error(&self) -> Option { + // Some error strings have dots on the end, which we don't care about + let processed_content = self.0.to_lowercase().replace('.', ""); + + match processed_content.as_str() { + "unknown opcode" | "4001" => Some(VoiceGatewayError::UnknownOpcode), + "decode error" | "failed to decode payload" | "4002" => { + Some(VoiceGatewayError::FailedToDecodePayload) + } + "not authenticated" | "4003" => Some(VoiceGatewayError::NotAuthenticated), + "authentication failed" | "4004" => Some(VoiceGatewayError::AuthenticationFailed), + "already authenticated" | "4005" => Some(VoiceGatewayError::AlreadyAuthenticated), + "session is no longer valid" | "4006" => Some(VoiceGatewayError::SessionNoLongerValid), + "session timeout" | "4009" => Some(VoiceGatewayError::SessionTimeout), + "server not found" | "4011" => Some(VoiceGatewayError::ServerNotFound), + "unknown protocol" | "4012" => Some(VoiceGatewayError::UnknownProtocol), + "disconnected" | "4014" => Some(VoiceGatewayError::Disconnected), + "voice server crashed" | "4015" => Some(VoiceGatewayError::VoiceServerCrashed), + "unknown encryption mode" | "4016" => Some(VoiceGatewayError::UnknownEncryptionMode), + _ => None, + } + } + + /// Parses the message as a payload; + /// Returns a result of deserializing + pub fn payload(&self) -> Result { + serde_json::from_str(&self.0) + } +} diff --git a/src/voice/gateway/mod.rs b/src/voice/gateway/mod.rs new file mode 100644 index 0000000..e8b36e5 --- /dev/null +++ b/src/voice/gateway/mod.rs @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +pub mod backends; +pub mod events; +pub mod gateway; +pub mod handle; +pub mod heartbeat; +pub mod message; + +pub use backends::*; +pub use gateway::*; +pub use handle::*; +pub use message::*; diff --git a/src/voice/mod.rs b/src/voice/mod.rs new file mode 100644 index 0000000..0d4f6e1 --- /dev/null +++ b/src/voice/mod.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Module for all voice functionality within chorus. + +mod crypto; +#[cfg(feature = "voice_gateway")] +pub mod gateway; +#[cfg(feature = "voice_udp")] +pub mod udp; +#[cfg(feature = "voice_udp")] +pub mod voice_data; + +// Pub use this so users can interact with packet types if they want +#[cfg(feature = "voice_udp")] +pub use discortp; diff --git a/src/voice/udp/backends/mod.rs b/src/voice/udp/backends/mod.rs new file mode 100644 index 0000000..e8c98f2 --- /dev/null +++ b/src/voice/udp/backends/mod.rs @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub mod tokio; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub use tokio::*; + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub type UdpSocket = tokio::TokioSocket; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub type UdpBackend = tokio::TokioBackend; + +#[cfg(all(target_arch = "wasm32", feature = "voice_udp"))] +compile_error!("UDP Voice support is not (and will likely never be) supported for WASM. This is because UDP cannot be used in the browser. We are however looking into Webrtc for WASM voice support."); diff --git a/src/voice/udp/backends/tokio.rs b/src/voice/udp/backends/tokio.rs new file mode 100644 index 0000000..e529a0c --- /dev/null +++ b/src/voice/udp/backends/tokio.rs @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::net::SocketAddr; + +use crate::errors::VoiceUdpError; + +#[derive(Debug, Clone)] +pub struct TokioBackend; + +pub type TokioSocket = tokio::net::UdpSocket; + +impl TokioBackend { + pub async fn connect(url: SocketAddr) -> Result { + // Bind with a port number of 0, so the os assigns this listener a port + let udp_socket_result = TokioSocket::bind("0.0.0.0:0").await; + + if let Err(e) = udp_socket_result { + return Err(VoiceUdpError::CannotBind { + error: format!("{:?}", e), + }); + } + + let udp_socket = udp_socket_result.unwrap(); + + let connection_result = udp_socket.connect(url).await; + + if let Err(e) = connection_result { + return Err(VoiceUdpError::CannotConnect { + error: format!("{:?}", e), + }); + } + + Ok(udp_socket) + } +} diff --git a/src/voice/udp/events.rs b/src/voice/udp/events.rs new file mode 100644 index 0000000..d4917fe --- /dev/null +++ b/src/voice/udp/events.rs @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use discortp::{rtcp::Rtcp, rtp::Rtp}; + +use crate::{gateway::GatewayEvent, types::WebSocketEvent}; + +impl WebSocketEvent for Rtp {} +impl WebSocketEvent for Rtcp {} + +#[derive(Debug)] +pub struct VoiceUDPEvents { + pub rtp: GatewayEvent, + pub rtcp: GatewayEvent, +} + +impl Default for VoiceUDPEvents { + fn default() -> Self { + Self { + rtp: GatewayEvent::new(), + rtcp: GatewayEvent::new(), + } + } +} diff --git a/src/voice/udp/handle.rs b/src/voice/udp/handle.rs new file mode 100644 index 0000000..822384d --- /dev/null +++ b/src/voice/udp/handle.rs @@ -0,0 +1,256 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::sync::Arc; + +use crypto_secretbox::{ + aead::Aead, cipher::generic_array::GenericArray, KeyInit, XSalsa20Poly1305, +}; +use discortp::Packet; + +use getrandom::getrandom; +use log::*; + +use tokio::{sync::Mutex, sync::RwLock}; + +use super::UdpSocket; + +use crate::{ + errors::VoiceUdpError, + types::VoiceEncryptionMode, + voice::{crypto::get_xsalsa20_poly1305_nonce, voice_data::VoiceData}, +}; + +use super::{events::VoiceUDPEvents, RTP_HEADER_SIZE}; + +/// Handle to a voice UDP connection +/// +/// Can be safely cloned and will still correspond to the same connection. +#[derive(Debug, Clone)] +pub struct UdpHandle { + pub events: Arc>, + pub(super) socket: Arc, + pub data: Arc>, +} + +impl UdpHandle { + /// Constructs and sends encoded opus rtp data. + /// + /// Automatically makes an [RtpPacket](discortp::rtp::RtpPacket), encrypts it and sends it. + /// + /// # Errors + /// If we do not have VoiceReady data, which contains our ssrc, this returns a + /// [VoiceUdpError::NoData] error. + /// + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// If the UDP socket is broken, this returns a [VoiceUdpError::BrokenSocket] error. + pub async fn send_opus_data( + &self, + timestamp: u32, + payload: Vec, + ) -> Result<(), VoiceUdpError> { + let voice_ready_data_result = self.data.read().await.ready_data.clone(); + if voice_ready_data_result.is_none() { + return Err(VoiceUdpError::NoData); + } + + let ssrc = voice_ready_data_result.unwrap().ssrc; + let sequence_number = self.data.read().await.last_sequence_number.wrapping_add(1); + self.data.write().await.last_sequence_number = sequence_number; + + let payload_len = payload.len(); + + let rtp_data = discortp::rtp::Rtp { + // Always the same + version: 2, + padding: 0, + extension: 0, + csrc_count: 0, + csrc_list: Vec::new(), + marker: 0, + payload_type: discortp::rtp::RtpType::Dynamic(120), + // Actually variable + sequence: sequence_number.into(), + timestamp: timestamp.into(), + ssrc, + payload, + }; + + let buffer_size = payload_len + RTP_HEADER_SIZE as usize; + + let mut buffer = vec![0; buffer_size]; + + let mut rtp_packet = discortp::rtp::MutableRtpPacket::new(&mut buffer).expect("Mangled rtp packet creation buffer, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new"); + rtp_packet.populate(&rtp_data); + + self.send_rtp_packet(rtp_packet).await + } + + /// Encrypts and sends and rtp packet. + /// + /// # Errors + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// If the Udp socket is broken, this returns a [VoiceUdpError::BrokenSocket] error. + pub async fn send_rtp_packet( + &self, + packet: discortp::rtp::MutableRtpPacket<'_>, + ) -> Result<(), VoiceUdpError> { + let mut buffer = self.encrypt_rtp_packet_payload(&packet).await?; + let new_packet = discortp::rtp::MutableRtpPacket::new(&mut buffer).unwrap(); + self.send_encrypted_rtp_packet(new_packet.consume_to_immutable()) + .await?; + Ok(()) + } + + /// Encrypts an unencrypted rtp packet, returning a copy of the packet's bytes with an + /// encrypted payload + /// + /// # Errors + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// When using voice encryption modes which require special nonce generation, and said generation fails, this returns a [VoiceUdpError::FailedNonceGeneration] error. + pub async fn encrypt_rtp_packet_payload( + &self, + packet: &discortp::rtp::MutableRtpPacket<'_>, + ) -> Result, VoiceUdpError> { + let payload = packet.payload(); + + let session_description_result = self.data.read().await.session_description.clone(); + + // We are trying to encrypt, but have not received SessionDescription yet, + // which contains the secret key. + if session_description_result.is_none() { + return Err(VoiceUdpError::NoKey); + } + + let session_description = session_description_result.unwrap(); + + let mut nonce_bytes = match session_description.encryption_mode { + VoiceEncryptionMode::Xsalsa20Poly1305 => get_xsalsa20_poly1305_nonce(packet.packet()), + VoiceEncryptionMode::Xsalsa20Poly1305Suffix => { + // Generate 24 random bytes + let mut random_destinaton: Vec = vec![0; 24]; + let random_result = getrandom(&mut random_destinaton); + if let Err(e) = random_result { + return Err(VoiceUdpError::FailedNonceGeneration { + error: format!("{:?}", e), + }); + } + random_destinaton + } + VoiceEncryptionMode::Xsalsa20Poly1305Lite => { + // "Incremental 4 bytes (32bit) int value" + let mut data_lock = self.data.write().await; + let nonce = data_lock + .last_udp_encryption_nonce + .unwrap_or_default() + .wrapping_add(1); + + data_lock.last_udp_encryption_nonce = Some(nonce); + drop(data_lock); + // TODO: Is big endian correct? This is not documented anywhere + let mut bytes = nonce.to_be_bytes().to_vec(); + + // This is 4 bytes, it has to be a different size, appends 0s + while bytes.len() < 24 { + bytes.push(0); + } + bytes + } + _ => { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + }; + + let key = GenericArray::from_slice(&session_description.secret_key); + + let encryption_result; + + if session_description.encryption_mode.is_xsalsa20_poly1305() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let encryptor = XSalsa20Poly1305::new(key); + + encryption_result = encryptor.encrypt(nonce, payload); + } + // Note: currently unused because I have no idea what the AeadAes256Gcm nonce is + /*else if session_description.encryption_mode.is_aead_aes256_gcm() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let encryptor = Aes256Gcm::new(key); + + encryption_result = encryptor.encrypt(nonce, payload); + + }*/ + else { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + + if encryption_result.is_err() { + // Safety: If encryption fails here, it's chorus' fault, and it makes no sense to + // return the error to the user. + // + // This is not an error the user should account for, which is why we throw it here. + panic!("{}", VoiceUdpError::FailedEncryption); + } + + let mut encrypted_payload = encryption_result.unwrap(); + + // Append the nonce bytes, if needed + // All other encryption modes have an explicit nonce, whereas Xsalsa20Poly1305 + // has the nonce as the rtp header. + if session_description.encryption_mode != VoiceEncryptionMode::Xsalsa20Poly1305 { + encrypted_payload.append(&mut nonce_bytes); + } + + // We need to allocate a new buffer, since the old one is too small for our new encrypted + // data + let buffer_size = encrypted_payload.len() + RTP_HEADER_SIZE as usize; + + let mut new_buffer: Vec = Vec::with_capacity(buffer_size); + + let mut rtp_header = packet.packet().to_vec()[0..RTP_HEADER_SIZE as usize].to_vec(); + + new_buffer.append(&mut rtp_header); + new_buffer.append(&mut encrypted_payload); + + Ok(new_buffer) + } + + /// Sends an (already encrypted) rtp packet to the connection. + /// + /// # Errors + /// If the Udp socket is broken, this returns a [VoiceUdpError::BrokenSocket] error. + pub async fn send_encrypted_rtp_packet( + &self, + packet: discortp::rtp::RtpPacket<'_>, + ) -> Result<(), VoiceUdpError> { + let raw_bytes = packet.packet(); + + let send_res = self.socket.send(raw_bytes).await; + if let Err(e) = send_res { + return Err(VoiceUdpError::BrokenSocket { + error: format!("{:?}", e), + }); + } + + trace!("VUDP: Sent rtp packet!"); + + Ok(()) + } +} diff --git a/src/voice/udp/handler.rs b/src/voice/udp/handler.rs new file mode 100644 index 0000000..c21709b --- /dev/null +++ b/src/voice/udp/handler.rs @@ -0,0 +1,356 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{net::SocketAddr, sync::Arc}; + +use crypto_secretbox::aead::Aead; +use crypto_secretbox::cipher::generic_array::GenericArray; +use crypto_secretbox::KeyInit; +use crypto_secretbox::XSalsa20Poly1305; + +use discortp::demux::Demuxed; +use discortp::discord::{ + IpDiscovery, IpDiscoveryPacket, IpDiscoveryType, MutableIpDiscoveryPacket, +}; +use discortp::rtcp::report::ReceiverReport; +use discortp::rtcp::report::SenderReport; +use discortp::{demux::demux, Packet}; +use tokio::sync::{Mutex, RwLock}; + +use super::UdpBackend; +use super::UdpSocket; + +use super::RTP_HEADER_SIZE; +use crate::errors::VoiceUdpError; +use crate::types::VoiceEncryptionMode; +use crate::voice::crypto::get_xsalsa20_poly1305_lite_nonce; +use crate::voice::crypto::get_xsalsa20_poly1305_nonce; +use crate::voice::crypto::get_xsalsa20_poly1305_suffix_nonce; +use crate::voice::voice_data::VoiceData; + +use super::{events::VoiceUDPEvents, UdpHandle}; + +use log::*; + +#[derive(Debug)] +/// The main UDP struct, which handles receiving, parsing and decrypting the rtp packets +pub struct UdpHandler { + events: Arc>, + pub data: Arc>, + socket: Arc, +} + +impl UdpHandler { + /// Spawns a new UDP handler and performs IP discovery. + /// + /// Mutates the given data_reference with the IP discovery data. + pub async fn spawn( + data_reference: Arc>, + url: SocketAddr, + ssrc: u32, + ) -> Result { + let udp_socket = UdpBackend::connect(url).await?; + + // First perform ip discovery + let ip_discovery = IpDiscovery { + pkt_type: IpDiscoveryType::Request, + ssrc, + length: 70, + address: Vec::new(), + port: 0, + payload: Vec::new(), + }; + + // Minimum size with an empty Address value, + 64 bytes for the actual address size + let size = IpDiscoveryPacket::minimum_packet_size() + 64; + + let mut buf: Vec = vec![0; size]; + + // Safety: expect is justified here, since this is an error which should never happen. + // If this errors, the code at fault is the buffer size calculation. + let mut ip_discovery_packet = + MutableIpDiscoveryPacket::new(&mut buf).expect("Mangled ip discovery packet creation buffer, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new"); + + ip_discovery_packet.populate(&ip_discovery); + + let data = ip_discovery_packet.packet(); + + debug!("VUDP: Sending Ip Discovery {:?}", &data); + + let send_res = udp_socket.send(data).await; + if let Err(e) = send_res { + return Err(VoiceUdpError::BrokenSocket { + error: format!("{:?}", e), + }); + } + + debug!("VUDP: Sent packet discovery request"); + + // Handle the ip discovery response + let received_size_or_err = udp_socket.recv(&mut buf).await; + + if let Err(e) = received_size_or_err { + return Err(VoiceUdpError::BrokenSocket { + error: format!("{:?}", e), + }); + } + + let receieved_ip_discovery = IpDiscoveryPacket::new(&buf).expect("Could not make ipdiscovery packet from received data, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new"); + + debug!( + "VUDP: Received ip discovery!!! {:?}", + receieved_ip_discovery + ); + + let ip_discovery = IpDiscovery { + pkt_type: receieved_ip_discovery.get_pkt_type(), + length: receieved_ip_discovery.get_length(), + ssrc: receieved_ip_discovery.get_ssrc(), + address: receieved_ip_discovery.get_address(), + port: receieved_ip_discovery.get_port(), + payload: Vec::new(), + }; + + let mut data_reference_lock = data_reference.write().await; + data_reference_lock.ip_discovery = Some(ip_discovery); + drop(data_reference_lock); + + let socket = Arc::new(udp_socket); + + let events = VoiceUDPEvents::default(); + let shared_events = Arc::new(Mutex::new(events)); + + let mut handler = UdpHandler { + events: shared_events.clone(), + data: data_reference.clone(), + socket: socket.clone(), + }; + + // Now we can continuously check for messages in a different task + tokio::spawn(async move { + handler.listen_task().await; + }); + + Ok(UdpHandle { + events: shared_events, + socket, + data: data_reference, + }) + } + + /// The main listen task; + /// + /// Receives UDP messages and parses them. + async fn listen_task(&mut self) { + loop { + // FIXME: is there a max size for these packets? + // Allocating 512 bytes seems a bit extreme + // + // Update: see + // > "The RTP standard does not set a maximum size.." + // + // The theoretical max for this buffer would be 1458 bytes, but that is imo + // unreasonable to allocate for every message. + let mut buf: Vec = vec![0; 512]; + + let result = self.socket.recv(&mut buf).await; + if let Ok(size) = result { + self.handle_message(&buf[0..size]).await; + continue; + } + + warn!("VUDP: Voice UDP is broken, closing connection"); + break; + } + } + + /// Handles a message buf + async fn handle_message(&self, buf: &[u8]) { + let parsed = demux(buf); + + match parsed { + Demuxed::Rtp(rtp) => { + trace!("VUDP: Parsed packet as rtp! {:?}", buf); + + let decryption_result = self.decrypt_rtp_packet_payload(&rtp).await; + + if let Err(err) = decryption_result { + match err { + VoiceUdpError::NoKey => { + warn!("VUDP: Received encyrpted voice data, but no encryption key, CANNOT DECRYPT!"); + return; + } + VoiceUdpError::FailedDecryption => { + warn!("VUDP: Failed to decrypt voice data!"); + return; + } + _ => { + error!("VUDP: Failed to decrypt voice data: {}", err); + return; + } + } + } + + let decrypted = decryption_result.unwrap(); + + trace!("VUDP: Successfully decrypted voice data!"); + + let rtp_with_decrypted_data = discortp::rtp::Rtp { + ssrc: rtp.get_ssrc(), + marker: rtp.get_marker(), + version: rtp.get_version(), + padding: rtp.get_padding(), + sequence: rtp.get_sequence(), + extension: rtp.get_extension(), + timestamp: rtp.get_timestamp(), + csrc_list: rtp.get_csrc_list(), + csrc_count: rtp.get_csrc_count(), + payload_type: rtp.get_payload_type(), + payload: decrypted, + }; + + self.events + .lock() + .await + .rtp + .notify(rtp_with_decrypted_data) + .await; + } + Demuxed::Rtcp(rtcp) => { + trace!("VUDP: Parsed packet as rtcp!"); + + let rtcp_data = match rtcp { + discortp::rtcp::RtcpPacket::KnownType(knowntype) => { + discortp::rtcp::Rtcp::KnownType(knowntype) + } + discortp::rtcp::RtcpPacket::SenderReport(senderreport) => { + discortp::rtcp::Rtcp::SenderReport(SenderReport { + payload: senderreport.payload().to_vec(), + padding: senderreport.get_padding(), + version: senderreport.get_version(), + ssrc: senderreport.get_ssrc(), + pkt_length: senderreport.get_pkt_length(), + packet_type: senderreport.get_packet_type(), + rx_report_count: senderreport.get_rx_report_count(), + }) + } + discortp::rtcp::RtcpPacket::ReceiverReport(receiverreport) => { + discortp::rtcp::Rtcp::ReceiverReport(ReceiverReport { + payload: receiverreport.payload().to_vec(), + padding: receiverreport.get_padding(), + version: receiverreport.get_version(), + ssrc: receiverreport.get_ssrc(), + pkt_length: receiverreport.get_pkt_length(), + packet_type: receiverreport.get_packet_type(), + rx_report_count: receiverreport.get_rx_report_count(), + }) + } + _ => { + unreachable!(); + } + }; + + self.events.lock().await.rtcp.notify(rtcp_data).await; + } + Demuxed::FailedParse(e) => { + trace!("VUDP: Failed to parse packet: {:?}", e); + } + Demuxed::TooSmall => { + unreachable!() + } + } + } + + /// Decrypts an encrypted rtp packet, returning a decrypted copy of the packet's payload + /// bytes. + /// + /// # Errors + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// If the decryption fails, this returns a [VoiceUdpError::FailedDecryption]. + pub async fn decrypt_rtp_packet_payload( + &self, + rtp: &discortp::rtp::RtpPacket<'_>, + ) -> Result, VoiceUdpError> { + let packet_bytes = rtp.packet(); + + let mut ciphertext: Vec = + packet_bytes[(RTP_HEADER_SIZE as usize)..packet_bytes.len()].to_vec(); + + let session_description_result = self.data.read().await.session_description.clone(); + + // We are trying to decrypt, but have not received SessionDescription yet, + // which contains the secret key + if session_description_result.is_none() { + return Err(VoiceUdpError::NoKey); + } + + let session_description = session_description_result.unwrap(); + + let nonce_bytes = match session_description.encryption_mode { + VoiceEncryptionMode::Xsalsa20Poly1305 => get_xsalsa20_poly1305_nonce(packet_bytes), + VoiceEncryptionMode::Xsalsa20Poly1305Suffix => { + // Remove the suffix from the ciphertext + ciphertext = ciphertext[0..ciphertext.len() - 24].to_vec(); + get_xsalsa20_poly1305_suffix_nonce(packet_bytes) + } + // Note: Rtpsize is documented by userdoccers to be the same, yet decryption + // doesn't work. + // + // I have no idea how Rtpsize works. + VoiceEncryptionMode::Xsalsa20Poly1305Lite => { + // Remove the suffix from the ciphertext + ciphertext = ciphertext[0..ciphertext.len() - 4].to_vec(); + get_xsalsa20_poly1305_lite_nonce(packet_bytes) + } + _ => { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + }; + + let key = GenericArray::from_slice(&session_description.secret_key); + + let decryption_result; + + if session_description.encryption_mode.is_xsalsa20_poly1305() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let decryptor = XSalsa20Poly1305::new(key); + + decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref()); + } + // Note: currently unused because I have no idea what the AeadAes256Gcm nonce is + /*else if session_description.encryption_mode.is_aead_aes256_gcm() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let decryptor = Aes256Gcm::new(key); + + decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref()); + + }*/ + else { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + + // Note: this may seem like we are throwing away valuable error handling data, + // but the decryption error provides no extra info. + if decryption_result.is_err() { + return Err(VoiceUdpError::FailedDecryption); + } + + Ok(decryption_result.unwrap()) + } +} diff --git a/src/voice/udp/mod.rs b/src/voice/udp/mod.rs new file mode 100644 index 0000000..5ae839a --- /dev/null +++ b/src/voice/udp/mod.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Defines the UDP component of voice communications, sending and receiving raw rtp data. + +/// See +/// This always adds up to 12 bytes +const RTP_HEADER_SIZE: u8 = 12; + +pub mod backends; +pub mod events; +pub mod handle; +pub mod handler; + +pub use backends::*; +pub use handle::*; +pub use handler::*; diff --git a/src/voice/voice_data.rs b/src/voice/voice_data.rs new file mode 100644 index 0000000..6bc408c --- /dev/null +++ b/src/voice/voice_data.rs @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use discortp::discord::IpDiscovery; + +use crate::types::{SessionDescription, Snowflake, VoiceReady, VoiceServerUpdate}; + +#[derive(Debug, Default)] +/// Saves data shared between parts of the voice architecture; +/// +/// Struct used to give the UDP connection data received from the gateway. +pub struct VoiceData { + pub server_data: Option, + pub ready_data: Option, + pub session_description: Option, + pub user_id: Snowflake, + pub session_id: String, + /// The last sequence number we used, has to be incremented by one every time we send a message + pub last_sequence_number: u16, + pub ip_discovery: Option, + + /// The last UDP encryption nonce, if we are using an encryption mode with incremental nonces. + pub last_udp_encryption_nonce: Option, +} diff --git a/tests/auth.rs b/tests/auth.rs index 130bfb6..35007f1 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{LoginSchema, RegisterSchema}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/channels.rs b/tests/channels.rs index d9842c6..14359d2 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{ self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, PermissionOverwrite, PrivateChannelCreateSchema, RelationshipType, Snowflake, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d6aaa34..f24c7e6 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,9 @@ -use std::sync::{Arc, RwLock}; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. -use chorus::gateway::Gateway; +use chorus::gateway::{Gateway, Shared}; +use chorus::types::IntoShared; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -16,9 +19,9 @@ pub(crate) struct TestBundle { pub urls: UrlBundle, pub user: ChorusUser, pub instance: Instance, - pub guild: Arc>, - pub role: Arc>, - pub channel: Arc>, + pub guild: Shared, + pub role: Shared, + pub channel: Shared, } #[allow(unused)] @@ -52,13 +55,8 @@ impl TestBundle { // Set up a test by creating an Instance and a User. Reduces Test boilerplate. 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 instance = Instance::new(urls.clone()).await.unwrap(); - // Requires the existance of the below user. + let instance = Instance::new("http://localhost:3001/api").await.unwrap(); + // Requires the existence of the below user. let reg = RegisterSchema { username: "integrationtestuser".into(), consent: true, @@ -114,13 +112,19 @@ pub(crate) async fn setup() -> TestBundle { .await .unwrap(); + let urls = UrlBundle::new( + "http://localhost:3001/api".to_string(), + "http://localhost:3001/api".to_string(), + "ws://localhost:3001".to_string(), + "http://localhost:3001".to_string(), + ); TestBundle { urls, user, instance, - guild: Arc::new(RwLock::new(guild)), - role: Arc::new(RwLock::new(role)), - channel: Arc::new(RwLock::new(channel)), + guild: guild.into_shared(), + role: role.into_shared(), + channel: channel.into_shared(), } } diff --git a/tests/gateway.rs b/tests/gateway.rs index 5bf5865..9f72a64 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -1,15 +1,29 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod common; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; +use std::time::Duration; +use async_trait::async_trait; use chorus::errors::GatewayError; use chorus::gateway::*; -use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +use chorus::types::{ + self, Channel, ChannelCreateSchema, ChannelModifySchema, GatewayReady, IntoShared, + RoleCreateModifySchema, RoleObject, +}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep; + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection (hello and heartbeats) on the local gateway; @@ -20,6 +34,18 @@ async fn test_gateway_establish() { common::teardown(bundle).await } +#[derive(Debug)] +struct GatewayReadyObserver { + channel: tokio::sync::mpsc::Sender<()>, +} + +#[async_trait] +impl Observer for GatewayReadyObserver { + async fn update(&self, _data: &GatewayReady) { + self.channel.send(()).await.unwrap(); + } +} + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection and authenticating @@ -28,17 +54,45 @@ async fn test_gateway_authenticate() { let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); + let (ready_send, mut ready_receive) = tokio::sync::mpsc::channel(1); + + let observer = Arc::new(GatewayReadyObserver { + channel: ready_send, + }); + + gateway + .events + .lock() + .await + .session + .ready + .subscribe(observer); + let mut identify = types::GatewayIdentifyPayload::common(); identify.token = bundle.user.token.clone(); gateway.send_identify(identify).await; + + tokio::select! { + // Fail, we timed out waiting for it + () = sleep(Duration::from_secs(20)) => { + println!("Timed out waiting for event, failing.."); + assert!(false); + } + // Sucess, we have received it + Some(_) = ready_receive.recv() => {} + }; + common::teardown(bundle).await } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { + // PRETTYFYME: This test is a bit of a mess, but it works. Ideally, each self-updating struct + // would have its own test. let mut bundle = common::setup().await; + let received_channel = bundle .user .gateway @@ -66,6 +120,34 @@ async fn test_self_updating_structs() { "selfupdating".to_string() ); + let guild = bundle + .user + .gateway + .observe_and_into_inner(bundle.guild.clone()) + .await; + assert!(guild.channels.is_none()); + + Channel::create( + &mut bundle.user, + guild.id, + None, + ChannelCreateSchema { + name: "selfupdating2".to_string(), + channel_type: Some(types::ChannelType::GuildText), + ..Default::default() + }, + ) + .await + .unwrap(); + + let guild = bundle + .user + .gateway + .observe_and_into_inner(guild.into_shared()) + .await; + assert!(guild.channels.is_some()); + assert!(guild.channels.as_ref().unwrap().len() == 1); + common::teardown(bundle).await } @@ -100,7 +182,7 @@ async fn test_recursive_self_updating_structs() { bundle .user .gateway - .observe(Arc::new(RwLock::new(role.clone()))) + .observe(role.clone().into_shared()) .await; // Update Guild and check for Guild let inner_guild = guild.read().unwrap().clone(); @@ -113,7 +195,7 @@ async fn test_recursive_self_updating_structs() { let role_inner = bundle .user .gateway - .observe_and_into_inner(Arc::new(RwLock::new(role.clone()))) + .observe_and_into_inner(role.clone().into_shared()) .await; assert_eq!(role_inner.name, "yippieee"); // Check if the change propagated diff --git a/tests/guilds.rs b/tests/guilds.rs index ab955de..e3a73f5 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{ CreateChannelInviteSchema, Guild, GuildBanCreateSchema, GuildCreateSchema, GuildModifySchema, }; diff --git a/tests/instance.rs b/tests/instance.rs index f1243a5..eb5fc60 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod common; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/invites.rs b/tests/invites.rs index ae1b9ab..e020199 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod common; use chorus::types::CreateChannelInviteSchema; #[cfg(target_arch = "wasm32")] diff --git a/tests/members.rs b/tests/members.rs index 9b415c3..a66d25a 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::{errors::ChorusResult, types::GuildMember}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/messages.rs b/tests/messages.rs index 7ff7598..3ca6e16 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fs::File; use std::io::{BufReader, Read}; diff --git a/tests/ratelimit.rs b/tests/ratelimit.rs new file mode 100644 index 0000000..bae78c9 --- /dev/null +++ b/tests/ratelimit.rs @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use chorus::ratelimiter::ChorusRequest; + +mod common; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn get_limit_config() { + let conf = ChorusRequest::get_limits_config("http://localhost:3001/api") + .await + .unwrap(); + assert!(conf.channel.max_pins > 0); + assert!(conf.channel.max_topic > 0); + assert!(conf.channel.max_webhooks > 0); + assert!(conf.guild.max_roles > 0); + assert!(conf.guild.max_channels > 0); + assert!(conf.guild.max_emojis > 0); + assert!(conf.guild.max_channels_in_category > 0); + assert!(conf.guild.max_members > 0); + assert!(conf.message.max_attachment_size > 0); + assert!(conf.message.max_bulk_delete > 0); + assert!(conf.message.max_reactions > 0); + assert!(conf.message.max_characters > 0); + assert!(conf.message.max_tts_characters == 0); + assert!(conf.user.max_guilds > 0); + assert!(conf.user.max_friends > 0); +} diff --git a/tests/relationships.rs b/tests/relationships.rs index c8ee9cc..2eea5b3 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{self, Relationship, RelationshipType}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/roles.rs b/tests/roles.rs index 8dda704..3246140 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{self, RoleCreateModifySchema, RoleObject}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 0000000..8895bb4 --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,1058 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +mod config { + mod subconfigs { + mod client { + use chorus::types::types::subconfigs::client::ClientReleaseConfiguration; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn client_release_configuration() { + let _client_release_configuration = ClientReleaseConfiguration::default(); + } + } + + mod limits { + use chorus::types::types::subconfigs::limits::rates::RateLimits; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn rates() { + let rate_limits = RateLimits::default(); + let hash_map = rate_limits.to_hash_map(); + assert!(hash_map.contains_key(&chorus::types::LimitType::ChannelBaseline)); + assert!(hash_map.contains_key(&chorus::types::LimitType::GuildBaseline)); + assert!(hash_map.contains_key(&chorus::types::LimitType::AuthLogin)); + assert!(hash_map.contains_key(&chorus::types::LimitType::AuthRegister)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Error)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Global)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Ip)); + assert!(hash_map.contains_key(&chorus::types::LimitType::WebhookBaseline)); + assert!(hash_map.len() == 8) + } + } + } + + mod guild_configuration { + use std::ops::Deref; + use std::str::FromStr; + + use chorus::types::types::guild_configuration::{GuildFeatures, GuildFeaturesList}; + use chorus::types::{Error, GuildError}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn deref_guild_features_list() { + let guild_features_list = &GuildFeaturesList::default(); + let _guild_features_list_deref = guild_features_list.deref().clone(); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_deref_mut() { + let mut guild_features_list = GuildFeaturesList::default(); + guild_features_list.clear(); + let mut list = GuildFeaturesList::default().to_vec(); + list.push(GuildFeatures::ActivitiesAlpha); + *guild_features_list = list.to_vec(); + assert_eq!(guild_features_list.len(), 1); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_display() { + let mut guild_features_list = GuildFeaturesList::default(); + guild_features_list.push(GuildFeatures::ActivitiesAlpha); + guild_features_list.push(GuildFeatures::AnimatedBanner); + assert_eq!( + format!("{}", guild_features_list), + "ACTIVITIES_ALPHA,ANIMATED_BANNER" + ); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_from_str() { + // GPT moment + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_ALPHA").unwrap(), + GuildFeatures::ActivitiesAlpha + ); + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_EMPLOYEE").unwrap(), + GuildFeatures::ActivitiesEmployee + ); + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_INTERNAL_DEV").unwrap(), + GuildFeatures::ActivitiesInternalDev + ); + assert_eq!( + GuildFeatures::from_str("ANIMATED_BANNER").unwrap(), + GuildFeatures::AnimatedBanner + ); + assert_eq!( + GuildFeatures::from_str("ANIMATED_ICON").unwrap(), + GuildFeatures::AnimatedIcon + ); + assert_eq!( + GuildFeatures::from_str("APPLICATION_COMMAND_PERMISSIONS_V2").unwrap(), + GuildFeatures::ApplicationCommandPermissionsV2 + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MODERATION").unwrap(), + GuildFeatures::AutoModeration + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_KEYWORD_FILTER").unwrap(), + GuildFeatures::AutoModTriggerKeywordFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_ML_SPAM_FILTER").unwrap(), + GuildFeatures::AutoModTriggerMLSpamFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_SPAM_LINK_FILTER").unwrap(), + GuildFeatures::AutoModTriggerSpamLinkFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_USER_PROFILE").unwrap(), + GuildFeatures::AutoModTriggerUserProfile + ); + assert_eq!( + GuildFeatures::from_str("BANNER").unwrap(), + GuildFeatures::Banner + ); + assert_eq!(GuildFeatures::from_str("BFG").unwrap(), GuildFeatures::Bfg); + assert_eq!( + GuildFeatures::from_str("BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD").unwrap(), + GuildFeatures::BoostingTiersExperimentMediumGuild + ); + assert_eq!( + GuildFeatures::from_str("BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD").unwrap(), + GuildFeatures::BoostingTiersExperimentSmallGuild + ); + assert_eq!( + GuildFeatures::from_str("BOT_DEVELOPER_EARLY_ACCESS").unwrap(), + GuildFeatures::BotDeveloperEarlyAccess + ); + assert_eq!( + GuildFeatures::from_str("BURST_REACTIONS").unwrap(), + GuildFeatures::BurstReactions + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_CANARY").unwrap(), + GuildFeatures::CommunityCanary + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_LARGE_GATED").unwrap(), + GuildFeatures::CommunityExpLargeGated + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_LARGE_UNGATED").unwrap(), + GuildFeatures::CommunityExpLargeUngated + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_MEDIUM").unwrap(), + GuildFeatures::CommunityExpMedium + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_EMOJIS_GENERATED").unwrap(), + GuildFeatures::ChannelEmojisGenerated + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_HIGHLIGHTS").unwrap(), + GuildFeatures::ChannelHighlights + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_HIGHLIGHTS_DISABLED").unwrap(), + GuildFeatures::ChannelHighlightsDisabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_ENABLED").unwrap(), + GuildFeatures::ClydeEnabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_EXPERIMENT_ENABLED").unwrap(), + GuildFeatures::ClydeExperimentEnabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_DISABLED").unwrap(), + GuildFeatures::ClydeDisabled + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY").unwrap(), + GuildFeatures::Community + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_ACCEPTED_NEW_TERMS").unwrap(), + GuildFeatures::CreatorAcceptedNewTerms + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE").unwrap(), + GuildFeatures::CreatorMonetizable + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_DISABLED").unwrap(), + GuildFeatures::CreatorMonetizableDisabled + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING") + .unwrap(), + GuildFeatures::CreatorMonetizablePendingNewOwnerOnboarding + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_PROVISIONAL").unwrap(), + GuildFeatures::CreatorMonetizableProvisional + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_RESTRICTED").unwrap(), + GuildFeatures::CreatorMonetizableRestricted + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_WHITEGLOVE").unwrap(), + GuildFeatures::CreatorMonetizableWhiteglove + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_APPLICATION_ALLOWLIST").unwrap(), + GuildFeatures::CreatorMonetizableApplicationAllowlist + ); + assert_eq!( + GuildFeatures::from_str("CREATE_STORE_PAGE").unwrap(), + GuildFeatures::CreateStorePage + ); + assert_eq!( + GuildFeatures::from_str("DEVELOPER_SUPPORT_SERVER").unwrap(), + GuildFeatures::DeveloperSupportServer + ); + assert_eq!( + GuildFeatures::from_str("DISCOVERABLE_DISABLED").unwrap(), + GuildFeatures::DiscoverableDisabled + ); + assert_eq!( + GuildFeatures::from_str("DISCOVERABLE").unwrap(), + GuildFeatures::Discoverable + ); + assert_eq!( + GuildFeatures::from_str("ENABLED_DISCOVERABLE_BEFORE").unwrap(), + GuildFeatures::EnabledDiscoverableBefore + ); + assert_eq!( + GuildFeatures::from_str("EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT").unwrap(), + GuildFeatures::ExposedToActivitiesWTPExperiment + ); + assert_eq!( + GuildFeatures::from_str("GUESTS_ENABLED").unwrap(), + GuildFeatures::GuestsEnabled + ); + assert_eq!( + GuildFeatures::from_str("GUILD_AUTOMOD_DEFAULT_LIST").unwrap(), + GuildFeatures::GuildAutomodDefaultList + ); + assert_eq!( + GuildFeatures::from_str("GUILD_COMMUNICATION_DISABLED_GUILDS").unwrap(), + GuildFeatures::GuildCommunicationDisabledGuilds + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_DEPRECATION_OVERRIDE").unwrap(), + GuildFeatures::GuildHomeDeprecationOverride + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_OVERRIDE").unwrap(), + GuildFeatures::GuildHomeOverride + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_TEST").unwrap(), + GuildFeatures::GuildHomeTest + ); + assert_eq!( + GuildFeatures::from_str("GUILD_MEMBER_VERIFICATION_EXPERIMENT").unwrap(), + GuildFeatures::GuildMemberVerificationExperiment + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING").unwrap(), + GuildFeatures::GuildOnboarding + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_ADMIN_ONLY").unwrap(), + GuildFeatures::GuildOnboardingAdminOnly + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_EVER_ENABLED").unwrap(), + GuildFeatures::GuildOnboardingEverEnabled + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_HAS_PROMPTS").unwrap(), + GuildFeatures::GuildOnboardingHasPrompts + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION").unwrap(), + GuildFeatures::GuildRoleSubscription + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP").unwrap(), + GuildFeatures::GuildRoleSubscriptionPurchaseFeedbackLoop + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION_TRIALS").unwrap(), + GuildFeatures::GuildRoleSubscriptionTrials + ); + assert_eq!( + GuildFeatures::from_str("GUILD_SERVER_GUIDE").unwrap(), + GuildFeatures::GuildServerGuide + ); + assert_eq!( + GuildFeatures::from_str("GUILD_WEB_PAGE_VANITY_URL").unwrap(), + GuildFeatures::GuildWebPageVanityURL + ); + assert_eq!( + GuildFeatures::from_str("HAD_EARLY_ACTIVITIES_ACCESS").unwrap(), + GuildFeatures::HadEarlyActivitiesAccess + ); + assert_eq!( + GuildFeatures::from_str("HAS_DIRECTORY_ENTRY").unwrap(), + GuildFeatures::HasDirectoryEntry + ); + assert_eq!( + GuildFeatures::from_str("HIDE_FROM_EXPERIMENT_UI").unwrap(), + GuildFeatures::HideFromExperimentUi + ); + assert_eq!(GuildFeatures::from_str("HUB").unwrap(), GuildFeatures::Hub); + assert_eq!( + GuildFeatures::from_str("INCREASED_THREAD_LIMIT").unwrap(), + GuildFeatures::IncreasedThreadLimit + ); + assert_eq!( + GuildFeatures::from_str("INTERNAL_EMPLOYEE_ONLY").unwrap(), + GuildFeatures::InternalEmployeeOnly + ); + assert_eq!( + GuildFeatures::from_str("INVITE_SPLASH").unwrap(), + GuildFeatures::InviteSplash + ); + assert_eq!( + GuildFeatures::from_str("INVITES_DISABLED").unwrap(), + GuildFeatures::InvitesDisabled + ); + assert_eq!( + GuildFeatures::from_str("LINKED_TO_HUB").unwrap(), + GuildFeatures::LinkedToHub + ); + assert_eq!( + GuildFeatures::from_str("MARKETPLACES_CONNECTION_ROLES").unwrap(), + GuildFeatures::MarketplacesConnectionRoles + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_PROFILES").unwrap(), + GuildFeatures::MemberProfiles + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_VERIFICATION_GATE_ENABLED").unwrap(), + GuildFeatures::MemberVerificationGateEnabled + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_VERIFICATION_MANUAL_APPROVAL").unwrap(), + GuildFeatures::MemberVerificationManualApproval + ); + assert_eq!( + GuildFeatures::from_str("MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE").unwrap(), + GuildFeatures::MobileWebRoleSubscriptionPurchasePage + ); + assert_eq!( + GuildFeatures::from_str("MONETIZATION_ENABLED").unwrap(), + GuildFeatures::MonetizationEnabled + ); + assert_eq!( + GuildFeatures::from_str("MORE_EMOJI").unwrap(), + GuildFeatures::MoreEmoji + ); + assert_eq!( + GuildFeatures::from_str("MORE_STICKERS").unwrap(), + GuildFeatures::MoreStickers + ); + assert_eq!( + GuildFeatures::from_str("NEWS").unwrap(), + GuildFeatures::News + ); + assert_eq!( + GuildFeatures::from_str("NEW_THREAD_PERMISSIONS").unwrap(), + GuildFeatures::NewThreadPermissions + ); + assert_eq!( + GuildFeatures::from_str("PARTNERED").unwrap(), + GuildFeatures::Partnered + ); + assert_eq!( + GuildFeatures::from_str("PREMIUM_TIER_3_OVERRIDE").unwrap(), + GuildFeatures::PremiumTier3Override + ); + assert_eq!( + GuildFeatures::from_str("PREVIEW_ENABLED").unwrap(), + GuildFeatures::PreviewEnabled + ); + assert_eq!( + GuildFeatures::from_str("RAID_ALERTS_DISABLED").unwrap(), + GuildFeatures::RaidAlertsDisabled + ); + assert_eq!( + GuildFeatures::from_str("RELAY_ENABLED").unwrap(), + GuildFeatures::RelayEnabled + ); + assert_eq!( + GuildFeatures::from_str("RESTRICT_SPAM_RISK_GUILD").unwrap(), + GuildFeatures::RestrictSpamRiskGuild + ); + assert_eq!( + GuildFeatures::from_str("ROLE_ICONS").unwrap(), + GuildFeatures::RoleIcons + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE").unwrap(), + GuildFeatures::RoleSubscriptionsAvailableForPurchase + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_ENABLED").unwrap(), + GuildFeatures::RoleSubscriptionsEnabled + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE").unwrap(), + GuildFeatures::RoleSubscriptionsEnabledForPurchase + ); + assert_eq!( + GuildFeatures::from_str("SHARD").unwrap(), + GuildFeatures::Shard + ); + assert_eq!( + GuildFeatures::from_str("SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST").unwrap(), + GuildFeatures::SharedCanvasFriendsAndFamilyTest + ); + assert_eq!( + GuildFeatures::from_str("SOUNDBOARD").unwrap(), + GuildFeatures::Soundboard + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED").unwrap(), + GuildFeatures::SummariesEnabled + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED_GA").unwrap(), + GuildFeatures::SummariesEnabledGa + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_DISABLED_BY_USER").unwrap(), + GuildFeatures::SummariesDisabledByUser + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED_BY_USER").unwrap(), + GuildFeatures::SummariesEnabledByUser + ); + assert_eq!( + GuildFeatures::from_str("TEXT_IN_STAGE_ENABLED").unwrap(), + GuildFeatures::TextInStageEnabled + ); + assert_eq!( + GuildFeatures::from_str("TEXT_IN_VOICE_ENABLED").unwrap(), + GuildFeatures::TextInVoiceEnabled + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ENABLED_TESTING").unwrap(), + GuildFeatures::ThreadsEnabledTesting + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ENABLED").unwrap(), + GuildFeatures::ThreadsEnabled + ); + assert_eq!( + GuildFeatures::from_str("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION").unwrap(), + GuildFeatures::ThreadDefaultAutoArchiveDuration + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ONLY_CHANNEL").unwrap(), + GuildFeatures::ThreadsOnlyChannel + ); + assert_eq!( + GuildFeatures::from_str("TICKETED_EVENTS_ENABLED").unwrap(), + GuildFeatures::TicketedEventsEnabled + ); + assert_eq!( + GuildFeatures::from_str("TICKETING_ENABLED").unwrap(), + GuildFeatures::TicketingEnabled + ); + assert_eq!( + GuildFeatures::from_str("VANITY_URL").unwrap(), + GuildFeatures::VanityUrl + ); + assert_eq!( + GuildFeatures::from_str("VERIFIED").unwrap(), + GuildFeatures::Verified + ); + assert_eq!( + GuildFeatures::from_str("VIP_REGIONS").unwrap(), + GuildFeatures::VipRegions + ); + assert_eq!( + GuildFeatures::from_str("VOICE_CHANNEL_EFFECTS").unwrap(), + GuildFeatures::VoiceChannelEffects + ); + assert_eq!( + GuildFeatures::from_str("WELCOME_SCREEN_ENABLED").unwrap(), + GuildFeatures::WelcomeScreenEnabled + ); + assert_eq!( + GuildFeatures::from_str("ALIASABLE_NAMES").unwrap(), + GuildFeatures::AliasableNames + ); + assert_eq!( + GuildFeatures::from_str("ALLOW_INVALID_CHANNEL_NAME").unwrap(), + GuildFeatures::AllowInvalidChannelName + ); + assert_eq!( + GuildFeatures::from_str("ALLOW_UNNAMED_CHANNELS").unwrap(), + GuildFeatures::AllowUnnamedChannels + ); + assert_eq!( + GuildFeatures::from_str("CROSS_CHANNEL_REPLIES").unwrap(), + GuildFeatures::CrossChannelReplies + ); + assert_eq!( + GuildFeatures::from_str("IRC_LIKE_CATEGORY_NAMES").unwrap(), + GuildFeatures::IrcLikeCategoryNames + ); + assert_eq!( + GuildFeatures::from_str("INVITES_CLOSED").unwrap(), + GuildFeatures::InvitesClosed + ); + assert_eq!( + GuildFeatures::from_str("INVALID").unwrap_err().to_string(), + Error::Guild(GuildError::InvalidGuildFeature).to_string() + ); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_to_str() { + assert_eq!(GuildFeatures::ActivitiesAlpha.to_str(), "ACTIVITIES_ALPHA"); + assert_eq!( + GuildFeatures::ActivitiesEmployee.to_str(), + "ACTIVITIES_EMPLOYEE" + ); + assert_eq!( + GuildFeatures::ActivitiesInternalDev.to_str(), + "ACTIVITIES_INTERNAL_DEV" + ); + assert_eq!(GuildFeatures::AnimatedBanner.to_str(), "ANIMATED_BANNER"); + assert_eq!(GuildFeatures::AnimatedIcon.to_str(), "ANIMATED_ICON"); + assert_eq!( + GuildFeatures::ApplicationCommandPermissionsV2.to_str(), + "APPLICATION_COMMAND_PERMISSIONS_V2" + ); + assert_eq!(GuildFeatures::AutoModeration.to_str(), "AUTO_MODERATION"); + assert_eq!( + GuildFeatures::AutoModTriggerKeywordFilter.to_str(), + "AUTO_MOD_TRIGGER_KEYWORD_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerMLSpamFilter.to_str(), + "AUTO_MOD_TRIGGER_ML_SPAM_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerSpamLinkFilter.to_str(), + "AUTO_MOD_TRIGGER_SPAM_LINK_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerUserProfile.to_str(), + "AUTO_MOD_TRIGGER_USER_PROFILE" + ); + assert_eq!(GuildFeatures::Banner.to_str(), "BANNER"); + assert_eq!(GuildFeatures::Bfg.to_str(), "BFG"); + assert_eq!( + GuildFeatures::BoostingTiersExperimentMediumGuild.to_str(), + "BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD" + ); + assert_eq!( + GuildFeatures::BoostingTiersExperimentSmallGuild.to_str(), + "BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD" + ); + assert_eq!( + GuildFeatures::BotDeveloperEarlyAccess.to_str(), + "BOT_DEVELOPER_EARLY_ACCESS" + ); + assert_eq!(GuildFeatures::BurstReactions.to_str(), "BURST_REACTIONS"); + assert_eq!(GuildFeatures::CommunityCanary.to_str(), "COMMUNITY_CANARY"); + assert_eq!( + GuildFeatures::CommunityExpLargeGated.to_str(), + "COMMUNITY_EXP_LARGE_GATED" + ); + assert_eq!( + GuildFeatures::CommunityExpLargeUngated.to_str(), + "COMMUNITY_EXP_LARGE_UNGATED" + ); + assert_eq!( + GuildFeatures::CommunityExpMedium.to_str(), + "COMMUNITY_EXP_MEDIUM" + ); + assert_eq!( + GuildFeatures::ChannelEmojisGenerated.to_str(), + "CHANNEL_EMOJIS_GENERATED" + ); + assert_eq!( + GuildFeatures::ChannelHighlights.to_str(), + "CHANNEL_HIGHLIGHTS" + ); + assert_eq!( + GuildFeatures::ChannelHighlightsDisabled.to_str(), + "CHANNEL_HIGHLIGHTS_DISABLED" + ); + assert_eq!(GuildFeatures::ClydeEnabled.to_str(), "CLYDE_ENABLED"); + assert_eq!( + GuildFeatures::ClydeExperimentEnabled.to_str(), + "CLYDE_EXPERIMENT_ENABLED" + ); + assert_eq!(GuildFeatures::ClydeDisabled.to_str(), "CLYDE_DISABLED"); + assert_eq!(GuildFeatures::Community.to_str(), "COMMUNITY"); + assert_eq!( + GuildFeatures::CreatorAcceptedNewTerms.to_str(), + "CREATOR_ACCEPTED_NEW_TERMS" + ); + assert_eq!( + GuildFeatures::CreatorMonetizable.to_str(), + "CREATOR_MONETIZABLE" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableDisabled.to_str(), + "CREATOR_MONETIZABLE_DISABLED" + ); + assert_eq!( + GuildFeatures::CreatorMonetizablePendingNewOwnerOnboarding.to_str(), + "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableProvisional.to_str(), + "CREATOR_MONETIZABLE_PROVISIONAL" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableRestricted.to_str(), + "CREATOR_MONETIZABLE_RESTRICTED" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableWhiteglove.to_str(), + "CREATOR_MONETIZABLE_WHITEGLOVE" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableApplicationAllowlist.to_str(), + "CREATOR_MONETIZABLE_APPLICATION_ALLOWLIST" + ); + assert_eq!(GuildFeatures::CreateStorePage.to_str(), "CREATE_STORE_PAGE"); + assert_eq!( + GuildFeatures::DeveloperSupportServer.to_str(), + "DEVELOPER_SUPPORT_SERVER" + ); + assert_eq!( + GuildFeatures::DiscoverableDisabled.to_str(), + "DISCOVERABLE_DISABLED" + ); + assert_eq!(GuildFeatures::Discoverable.to_str(), "DISCOVERABLE"); + assert_eq!( + GuildFeatures::EnabledDiscoverableBefore.to_str(), + "ENABLED_DISCOVERABLE_BEFORE" + ); + assert_eq!( + GuildFeatures::ExposedToActivitiesWTPExperiment.to_str(), + "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" + ); + assert_eq!(GuildFeatures::GuestsEnabled.to_str(), "GUESTS_ENABLED"); + assert_eq!( + GuildFeatures::GuildAutomodDefaultList.to_str(), + "GUILD_AUTOMOD_DEFAULT_LIST" + ); + assert_eq!( + GuildFeatures::GuildCommunicationDisabledGuilds.to_str(), + "GUILD_COMMUNICATION_DISABLED_GUILDS" + ); + assert_eq!( + GuildFeatures::GuildHomeDeprecationOverride.to_str(), + "GUILD_HOME_DEPRECATION_OVERRIDE" + ); + assert_eq!( + GuildFeatures::GuildHomeOverride.to_str(), + "GUILD_HOME_OVERRIDE" + ); + assert_eq!(GuildFeatures::GuildHomeTest.to_str(), "GUILD_HOME_TEST"); + assert_eq!( + GuildFeatures::GuildMemberVerificationExperiment.to_str(), + "GUILD_MEMBER_VERIFICATION_EXPERIMENT" + ); + assert_eq!(GuildFeatures::GuildOnboarding.to_str(), "GUILD_ONBOARDING"); + assert_eq!( + GuildFeatures::GuildOnboardingAdminOnly.to_str(), + "GUILD_ONBOARDING_ADMIN_ONLY" + ); + assert_eq!( + GuildFeatures::GuildOnboardingEverEnabled.to_str(), + "GUILD_ONBOARDING_EVER_ENABLED" + ); + assert_eq!( + GuildFeatures::GuildOnboardingHasPrompts.to_str(), + "GUILD_ONBOARDING_HAS_PROMPTS" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscription.to_str(), + "GUILD_ROLE_SUBSCRIPTION" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscriptionPurchaseFeedbackLoop.to_str(), + "GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscriptionTrials.to_str(), + "GUILD_ROLE_SUBSCRIPTION_TRIALS" + ); + assert_eq!( + GuildFeatures::GuildServerGuide.to_str(), + "GUILD_SERVER_GUIDE" + ); + assert_eq!( + GuildFeatures::GuildWebPageVanityURL.to_str(), + "GUILD_WEB_PAGE_VANITY_URL" + ); + assert_eq!( + GuildFeatures::HadEarlyActivitiesAccess.to_str(), + "HAD_EARLY_ACTIVITIES_ACCESS" + ); + assert_eq!( + GuildFeatures::HasDirectoryEntry.to_str(), + "HAS_DIRECTORY_ENTRY" + ); + assert_eq!( + GuildFeatures::HideFromExperimentUi.to_str(), + "HIDE_FROM_EXPERIMENT_UI" + ); + assert_eq!(GuildFeatures::Hub.to_str(), "HUB"); + assert_eq!( + GuildFeatures::IncreasedThreadLimit.to_str(), + "INCREASED_THREAD_LIMIT" + ); + assert_eq!( + GuildFeatures::InternalEmployeeOnly.to_str(), + "INTERNAL_EMPLOYEE_ONLY" + ); + assert_eq!(GuildFeatures::InviteSplash.to_str(), "INVITE_SPLASH"); + assert_eq!(GuildFeatures::InvitesDisabled.to_str(), "INVITES_DISABLED"); + assert_eq!(GuildFeatures::LinkedToHub.to_str(), "LINKED_TO_HUB"); + assert_eq!( + GuildFeatures::MarketplacesConnectionRoles.to_str(), + "MARKETPLACES_CONNECTION_ROLES" + ); + assert_eq!(GuildFeatures::MemberProfiles.to_str(), "MEMBER_PROFILES"); + assert_eq!( + GuildFeatures::MemberVerificationGateEnabled.to_str(), + "MEMBER_VERIFICATION_GATE_ENABLED" + ); + assert_eq!( + GuildFeatures::MemberVerificationManualApproval.to_str(), + "MEMBER_VERIFICATION_MANUAL_APPROVAL" + ); + assert_eq!( + GuildFeatures::MobileWebRoleSubscriptionPurchasePage.to_str(), + "MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE" + ); + assert_eq!( + GuildFeatures::MonetizationEnabled.to_str(), + "MONETIZATION_ENABLED" + ); + assert_eq!(GuildFeatures::MoreEmoji.to_str(), "MORE_EMOJI"); + assert_eq!(GuildFeatures::MoreStickers.to_str(), "MORE_STICKERS"); + assert_eq!(GuildFeatures::News.to_str(), "NEWS"); + assert_eq!( + GuildFeatures::NewThreadPermissions.to_str(), + "NEW_THREAD_PERMISSIONS" + ); + assert_eq!(GuildFeatures::Partnered.to_str(), "PARTNERED"); + assert_eq!( + GuildFeatures::PremiumTier3Override.to_str(), + "PREMIUM_TIER_3_OVERRIDE" + ); + assert_eq!(GuildFeatures::PreviewEnabled.to_str(), "PREVIEW_ENABLED"); + assert_eq!( + GuildFeatures::RaidAlertsDisabled.to_str(), + "RAID_ALERTS_DISABLED" + ); + assert_eq!(GuildFeatures::RelayEnabled.to_str(), "RELAY_ENABLED"); + assert_eq!( + GuildFeatures::RestrictSpamRiskGuild.to_str(), + "RESTRICT_SPAM_RISK_GUILD" + ); + assert_eq!(GuildFeatures::RoleIcons.to_str(), "ROLE_ICONS"); + assert_eq!( + GuildFeatures::RoleSubscriptionsAvailableForPurchase.to_str(), + "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" + ); + assert_eq!( + GuildFeatures::RoleSubscriptionsEnabled.to_str(), + "ROLE_SUBSCRIPTIONS_ENABLED" + ); + assert_eq!( + GuildFeatures::RoleSubscriptionsEnabledForPurchase.to_str(), + "ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE" + ); + assert_eq!(GuildFeatures::Shard.to_str(), "SHARD"); + assert_eq!( + GuildFeatures::SharedCanvasFriendsAndFamilyTest.to_str(), + "SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST" + ); + assert_eq!(GuildFeatures::Soundboard.to_str(), "SOUNDBOARD"); + assert_eq!( + GuildFeatures::SummariesEnabled.to_str(), + "SUMMARIES_ENABLED" + ); + assert_eq!( + GuildFeatures::SummariesEnabledGa.to_str(), + "SUMMARIES_ENABLED_GA" + ); + assert_eq!( + GuildFeatures::SummariesDisabledByUser.to_str(), + "SUMMARIES_DISABLED_BY_USER" + ); + assert_eq!( + GuildFeatures::SummariesEnabledByUser.to_str(), + "SUMMARIES_ENABLED_BY_USER" + ); + assert_eq!( + GuildFeatures::TextInStageEnabled.to_str(), + "TEXT_IN_STAGE_ENABLED" + ); + assert_eq!( + GuildFeatures::TextInVoiceEnabled.to_str(), + "TEXT_IN_VOICE_ENABLED" + ); + assert_eq!( + GuildFeatures::ThreadsEnabledTesting.to_str(), + "THREADS_ENABLED_TESTING" + ); + assert_eq!(GuildFeatures::ThreadsEnabled.to_str(), "THREADS_ENABLED"); + assert_eq!( + GuildFeatures::ThreadDefaultAutoArchiveDuration.to_str(), + "THREAD_DEFAULT_AUTO_ARCHIVE_DURATION" + ); + assert_eq!( + GuildFeatures::ThreadsOnlyChannel.to_str(), + "THREADS_ONLY_CHANNEL" + ); + assert_eq!( + GuildFeatures::TicketedEventsEnabled.to_str(), + "TICKETED_EVENTS_ENABLED" + ); + assert_eq!( + GuildFeatures::TicketingEnabled.to_str(), + "TICKETING_ENABLED" + ); + assert_eq!(GuildFeatures::VanityUrl.to_str(), "VANITY_URL"); + assert_eq!(GuildFeatures::Verified.to_str(), "VERIFIED"); + assert_eq!(GuildFeatures::VipRegions.to_str(), "VIP_REGIONS"); + assert_eq!( + GuildFeatures::VoiceChannelEffects.to_str(), + "VOICE_CHANNEL_EFFECTS" + ); + assert_eq!( + GuildFeatures::WelcomeScreenEnabled.to_str(), + "WELCOME_SCREEN_ENABLED" + ); + assert_eq!(GuildFeatures::AliasableNames.to_str(), "ALIASABLE_NAMES"); + assert_eq!( + GuildFeatures::AllowInvalidChannelName.to_str(), + "ALLOW_INVALID_CHANNEL_NAME" + ); + assert_eq!( + GuildFeatures::AllowUnnamedChannels.to_str(), + "ALLOW_UNNAMED_CHANNELS" + ); + assert_eq!( + GuildFeatures::CrossChannelReplies.to_str(), + "CROSS_CHANNEL_REPLIES" + ); + assert_eq!( + GuildFeatures::IrcLikeCategoryNames.to_str(), + "IRC_LIKE_CATEGORY_NAMES" + ); + assert_eq!(GuildFeatures::InvitesClosed.to_str(), "INVITES_CLOSED"); + } + } + + mod domains_configuration { + use chorus::types::types::domains_configuration::Domains; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn display_domains() { + let domains = Domains { + cdn: "http://localhost:3020/cdn/".to_string(), + gateway: "ws://localhost:3020/".to_string(), + api_endpoint: "http://localhost:3020/".to_string(), + default_api_version: "9".to_string(), + }; + let fmt_domains = domains.to_string(); + assert!(fmt_domains.contains("CDN URL: http://localhost:3020/cdn/")); + assert!(fmt_domains.contains("Gateway URL: ws://localhost:3020/")); + assert!(fmt_domains.contains("API Endpoint: http://localhost:3020/")); + assert!(fmt_domains.contains("Default API Version: 9")); + } + } +} + +mod entities { + use std::sync::{Arc, RwLock}; + + use chorus::types::{ApplicationFlags, ConfigEntity, Emoji, User}; + use serde_json::json; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn application() { + let application = chorus::types::Application::default(); + assert!(application.name == *""); + assert!(application.verify_key == *""); + assert_ne!( + application.owner.read().unwrap().clone(), + Arc::new(RwLock::new(User::default())) + .read() + .unwrap() + .clone() + ); + let flags = ApplicationFlags::from_bits(0).unwrap(); + assert!(application.flags() == flags); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn config() { + let mut config_entity = ConfigEntity { + key: "ExampleKey".to_string(), + value: Some(json!(1)), + }; + config_entity.as_int().unwrap(); + assert!(config_entity.as_bool().is_none()); + assert!(config_entity.as_string().is_none()); + config_entity.value = Some(json!(true)); + config_entity.as_bool().unwrap(); + assert!(config_entity.as_int().is_none()); + assert!(config_entity.as_string().is_none()); + config_entity.value = Some(json!("Hello")); + config_entity.as_string().unwrap(); + assert!(config_entity.as_bool().is_none()); + assert!(config_entity.as_int().is_none()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn emoji() { + let emoji = Emoji::default(); + let another_emoji = Emoji::default(); + assert_ne!(emoji.id, another_emoji.id); + assert_ne!(emoji, another_emoji); + } + + mod guild { + use std::hash::{Hash, Hasher}; + + use chorus::types::{Guild, GuildInvite}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_hash() { + let id: u64 = 1; + let mut guild1 = Guild::default(); + let mut guild2 = Guild::default(); + guild1.id = id.into(); + guild2.id = id.into(); + let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); + guild1.hash(&mut hasher1); + + let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); + guild2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_invite_hash() { + let id: u64 = 1; + let mut invite1 = GuildInvite::default(); + let mut invite2 = GuildInvite::default(); + invite1.channel_id = id.into(); + invite2.channel_id = id.into(); + invite1.guild_id = id.into(); + invite2.guild_id = id.into(); + let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); + invite1.hash(&mut hasher1); + + let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); + invite2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_partial_eq() { + let id: u64 = 1; + let mut guild1 = Guild::default(); + let mut guild2 = Guild::default(); + guild1.id = id.into(); + guild2.id = id.into(); + + assert_eq!(guild1, guild2); + } + } + + mod relationship { + use chorus::types::{IntoShared, Relationship, User}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn relationship_partial_eq() { + let user = User::default(); + // These 2 users are different, because they do not have the same Snowflake "id". + let user_2 = User::default(); + let relationship_1 = Relationship { + id: 32_u64.into(), + relationship_type: chorus::types::RelationshipType::Friends, + nickname: Some("Xenia".to_string()), + user: user.into_public_user().into_shared(), + since: None, + }; + + let relationship_2 = Relationship { + id: 32_u64.into(), + relationship_type: chorus::types::RelationshipType::Friends, + nickname: Some("Xenia".to_string()), + user: user_2.into_public_user().into_shared(), + since: None, + }; + + // This should succeed, even though the two users' IDs are different. This is because + // `User` is only `PartialEq`, and the actual user object is not checked, since the + // `RwLock` would have to be locked. + assert_eq!(relationship_1, relationship_2); + } + } + + mod message { + use chorus::types::{Message, Snowflake}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn message_partial_eq() { + let id: Snowflake = 1_u64.into(); + let mut message1 = Message::default(); + let mut message2 = Message::default(); + message1.id = id; + message1.channel_id = id; + message2.id = id; + message2.channel_id = id; + + assert_eq!(message1, message2); + } + } +} diff --git a/tests/urlbundle.rs b/tests/urlbundle.rs index 790229b..5ecdafe 100644 --- a/tests/urlbundle.rs +++ b/tests/urlbundle.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::types::domains_configuration::WellKnownResponse; use chorus::UrlBundle; use serde_json::json; diff --git a/tests/user.rs b/tests/user.rs new file mode 100644 index 0000000..2fbc187 --- /dev/null +++ b/tests/user.rs @@ -0,0 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use chorus::types::{PublicUser, Snowflake, User}; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn to_public_user() { + let mut user = User::default(); + let mut public_user = PublicUser { + username: Some("".to_string()), + discriminator: Some("".to_string()), + ..Default::default() + }; + let id: Snowflake = 1_u64.into(); + user.id = id; + public_user.id = id; + + let from_user = user.into_public_user(); + assert_eq!(public_user, from_user); +} diff --git a/tests/wasm.rs b/tests/wasm.rs index d5d26c1..7c30719 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use wasm_bindgen_test::wasm_bindgen_test; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);