🎉 Release 0.16.0
* add WebSocketEvent macro derive, bump chorus-macros to 0.3.0 (#490) * feat: add WebSocketEvent derive, bump to 0.2.1 * change license, version of macros * Use WebSocketEvent derive instead of impl WebSocketEvent for .. (#491) use derive macro instead of manual impl blocks * Move Shared<T> to types/mod.rs, bump some dependencies (#492) * deps: bump rustls to 0.21.11 This is done to fix CVE-2024-32650, which practically shouldn't affect us but it's still better not to use vulnerable dependencies. * deps: bump h2 to 0.3.26 This is done to fix another vulnerability, which should also not affect us (non-critical, in h2 servers) * fix: move Shared<T> to types/mod.rs * Reuse gateway backends, don't duplicate them for voice gateway (#493) * Refactor / fix login and register (#495) Change login and register to only use one ChorusUser object, change the api of related methods which were also somewhat ugly * Add the repository field to Cargo.toml * Ci improvements (#498) * bump wasm-bindgen version to 0.2.92 * change wasm-gecko to using ubuntu-latest, since geckodriver is not supported on macos-latest * check some common non-default feature configurations * add experimental semver checks * Update poem dependency * Convert timestamp fields to DateTime's * Feature lock different types for UserSettings::status * Make config register_configuration use Rights bitflag object * Update tests to use DateTime's for timestamps * Fix tests * Allow joined_at to default if field is not in responses. * Allow last_pin_timestamp to default if field is not in responses. * Remove serde(with) for message timestamps * Fix some iso timestamps being strings, not DateTime<Utc> (#499) * fix: some iso timestamps being strings * fix: register uses dates, not datetimes * Add sqlx::FromRow derive to GuildMember * remove dep: prefix in backend feature list * Implement sqlx Encode, Decode, Type for Rights bitflag object. * Use Snowflake in Claims * Use ChannelType enum on ChannelModifySchema * Feature lock Shared, so backend feature gets a facade type * Remove erroneous serde with attributes * Add From<Vec<GuildFeatures>> impl for GuildFeaturesList * Add feature sqlx locks for user, roles on GuildMember * Use distinct type for explicit_content_filter * Remove unused imports * Revertc4452132
* Remove final usages of erroneous serde impl * Fix errors in documentation tests * update dev-dependencies * actually fix linux tests * clear warnings * Update Cargo.lock * Expand documentation to explain facade type * Fix oversight for premium_since * Backend related updates (#501) (by Quat3rnion) Some updates relating to usage with Symfonia: * Using distinct types instead of primitives on some objects * Add sqlx derives and implementations * Make a facade type for Shared to be used in non-client contexts * Distinguish InviteType and InviteTargetType * Add sqlx Type, Encode, Decode impl for InviteFlags bitflag object. * Use distinct type `DefaultReaction` * Update fields for backend/sqlx compatibility. * Update derive for backend/sqlx compatibility. * Write custom serialize/deserialize impl's for InviteFlags * Clear warnings * Add InviteFlags::VIEWED * Remove double bound for E * Use distinct types in `InviteGuild` object. * Implement `From<Guild> for InviteGuild` * Add `GetInvitesSchema` * Add forgotten import and pub use. * Fix compile error * Update docs aesthetics Co-authored-by: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> * Add type locks * Fix inverted type wrapping * Update Cargo.lock * Add SqlxBitFlags derive macro * Utilize new macros and use distinct Flag types * Add distinct MessageType enum * Fix error in macro * Make UserFlags deserialize from string * Add partial emoji and custom reaction types, refine SQLx mapping * Use chorus_macros from path, since it's there anyway * Fix test * Add forgotten feature locks * Remove unused imports, feature locks in macro * forgot a file :( * Feature lock the macro * Dirty hack * Fix test I feel silly. * Fix compilation for real, no dirty hack * Maybe fix tests, make UserFlags able to be deserialized from String or u64 * Add new SerdeBitFlags derive macro, to help reduce repetitive code * u8 -> u64 * Fix deserialization error w/ guild features list * chorus macros 0.4.0 * always update to latest release of macros * Update and add some types in support of the backend (#507) * Implement gateway options, zlib-stream compression (#508) * feat: add GatewayOptions * feat: implement zlib-stream compression This also changes how gateway messages work. Now each gateway backend converts its message into an intermediary RawGatewayMessage, from which we inflate and parse GatewayMessages. Thanks to ByteAlex and their zlib-stream-rs crate, which helped me understand how to parse a compressed websocket stream * Backend/guilds (#509) * Fix SQL encode/decode for GuildFeatures * Use distinct PermissionFlags type * Add Emoji schema types, modify GuildBan with feature lock * Add Schemas for pruning guild members * Add schemas for interfacing with stickers backend routes * Add schemas for interfacing with vanity-url backend routes * Add schema for interfacing with guilds/id/welcome-screen route * Make all Option<Vec> types Vec types with #[serde(default)] * Add various types to support guilds/* api routes * Add missing enums and structs for searching messages * Use proper distinct types * Add EmbedType enum * Use distinct PermissionFlags type * Changes supporting backend for VoiceState * Changes supporting backend for AuditLog's * Add custom deserializer for PermissionOverwriteType (#512) * Add custom deserializer for PermissionOverwriteType * Miscellaneous fixes (#514) - fix `PATCH /users/@me` - It incorrectly returned a required password error, even if the current password was set - fix `GET /users/@me/guilds` - It incorrectly sent body parameters instead of query ones - don't log debug! for every successful ratelimited request - use trace! so it's less spamy - update the max expected compression ratio (several times) from 20 to 200. let's hope that will be enough - fix deserialization errors relating to guild folders in user settings - fix a panic in `SqlxBitFlags` if there are extra flags. It now truncates them - update `chorus_macros` to 0.4.1 (due to the above fix) - log (trace!) event data if we fail to parse it or it's unrecognised, for debugging purposes - fix a deserialization error in the `MessageACK` event - fix `public_flags` in user objects not being `PublicFlags` bitflags * No openssl (#522) * Remove openssl from some packages' deps * Add shorthand wasm build script * Eliminate openssl dependency from crate * Build RootCertStore from webpki roots instead of native roots * Expand wasm documentation * Revert reqwest * Lock reqwest at 0.11.23 * Lock reqwest at 0.11.26, latest possible version * Add wasm test script * Replace `Observer` and `GatewayEvent` with `pubserve` crate * Fix voice, voice_udp features * Add one BILLION derives * Provide alternative implementations for PartialEq for some types when sqlx feature is enabled * Provide alternative implementations for PartialEq for some types when sqlx feature is enabled * Fix: Wrong function name * Fix: Turn unconditional import of sqlx::types::Json into conditional one * Fix: Compile error with no default features * Update CONTRIBUTING.md * Use cargo nextest * Use cargo nextest * Fix/Correct UnavailableGuild object * Fix testcase that relied on false behavior implemented by older spacebar servers * Increase limit integer size to match spacebars' possibilities * Cargo nextest on wasm, Parallelize "Check common non-default feat. cfg." * forgor installing nextest * Revert: nextest on wasm * Bump browser_version according to https://www.useragents.me/#most-common-desktop-useragents * Fix voice_simple example * Update documentation in gateway_observers example * Readd Observer trait as reexport * remove cargo lock from example * Fix #525 (pr #532) * change theme_colors from Vec<u32> to (u32, u32) * Custom ThemeColors type with sqlx::Encode and sqlx::Decode impls * impl sqlx::Type for ThemeColors * More accurate "GatewayHello::default()" * Manually implement std::default::Default for GatewayHeartbeat and GatewayHeartbeatAck * bump versions of packages to latest compatible versions * Update MSRV (#540) * Update msrv * sqlx related improvements (#542) * Bump sqlx to 0.8.0 * Update sqlx syntax to 0.8.0, change MySql for sqlx:: Any * Update sqlx syntax to 0.8.0, change MySql for sqlx:: Any * Modify chorus_macros::SqlxBitflagDerive to use sqlx::Any over sqlx::MySql (broken!) * Fix: `cannot infer type for type parameter `DB` declared on the trait...` * Change remaining impls of sqlx traits for MySql to sqlx::Any * Alter chorus_macros to correctly derive SqlxBitFlag for sqlx::Any * rustc/clippy>=v1.80.0: Do not warn when encountering cfg(tarpaulin_include) * Port compare_* methods to sqlx v0.8.0 * CI/CD: add cargo-doc job (#544) * CI/CD: add cargo-doc job * fix: make it rustdoc, totally not clippy * Initial support for PostgreSQL (#548) * Change sqlx::Any to sqlx::Postgres * Change sqlx::Any to sqlx::Postgres * Remove JSONified overrides when sqlx feature is enabled, where it makes sense * Add num-bigint dep * Remove generic impl for From<T> for Snowflake For some reason, this trait bound conflicts with another trait bound from the sqlx-pg-uint crate, even though I personally don't get why. * Remove num_bigint, adsd sqlx-pg-uint * swap u64 for PgU64 in some files * use v0.3.0 of sqlx-pg-uint * Lots of sqlx-postgres type changes * Lots of sqlx-postgres type changes * gwah * Change repr(i8) to repr(i16) in enums when sqlx feature is enabled, fix sqlx incompatibilities * impl sqlx::postgres::PgHasArrayType for Snowflake * Try: derive Type for FriendSourceFlags, GuildFolder * Try: Derive FromRow, Type for DefaultReaction * Try: Derive Type for CustomStatus * Try: Derive Type, FromRow for Tag * Replace conditional compiling of uNN/PgUNN with conditional compiled type alias * Fix: Conditional compiling errors and warnings * Bump: wasm-bindgen* crate versions * Bump sqlx-pg-uint * Uncomment and update decode_token() * Snowflake: PgU64 as base for sqlx (#551) * Bump sqlx-pg-uint, sqlx * Use PgU64 as base for implementations of sqlx traits for Snowflake * sqlx_bitflag_derive: Use PgU64 as translation base (#552) * sqlx_bitflag_derive: Use PgU64 as translation base * Bump version of chorus-macros * Prefer `&str` over `String` when possible (#553) Prefer &str over String where possible. * Tungstenite: Match scheme for "ws" or "wss" (#554) Match scheme for "ws" or "wss" and choose whether to connect with TLS connector for tungstenite * Add missing `impl From<Snowflake> for u64`, closes `From<Snowflake> for u64` missing #550 * Update README.md * Bump package versions * move up sending identify * Make instance GatewayOptions configurable for library consumers (#555) * Make instance GatewayOptions configurable for library consumers * Update example in README * Revertd846ce9948
* 0.16.0: fix lints (#558) * chore: fix doc lints * fix: use different chrono function due to deprecation * chore: format * Bump version to v0.16.0 --------- Co-authored-by: Gábor Szabó <gabor@szabgab.com> Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com> Co-authored-by: Quat3rnion <quat3rnion@proton.me> Co-authored-by: Quat3rnion <81202811+Quat3rnion@users.noreply.github.com> Co-authored-by: bitfl0wer <florian@pro-weber.com>
This commit is contained in:
parent
cb3551dcd4
commit
3e22b5c0ed
|
@ -2,133 +2,133 @@ name: Build and Test
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
branches: ["main", "dev"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
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@v4
|
||||
with:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Clone spacebar server
|
||||
run: |
|
||||
git clone https://github.com/bitfl0wer/server.git
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
cache: "npm"
|
||||
cache-dependency-path: server/package-lock.json
|
||||
- name: Prepare and start Spacebar server
|
||||
run: |
|
||||
npm install
|
||||
npm run setup
|
||||
npm run start &
|
||||
working-directory: ./server
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
prefix-key: "linux"
|
||||
- name: Build, Test and Publish Coverage
|
||||
run: |
|
||||
if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then
|
||||
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 cargo-tarpaulin --force
|
||||
cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120
|
||||
else
|
||||
echo "Code Coverage step is skipped on forks!"
|
||||
cargo build --verbose --all-features
|
||||
cargo test --verbose --all-features
|
||||
fi
|
||||
# wasm-safari:
|
||||
# runs-on: macos-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Clone spacebar server
|
||||
# run: |
|
||||
# git clone https://github.com/bitfl0wer/server.git
|
||||
# - uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: 18
|
||||
# cache: 'npm'
|
||||
# cache-dependency-path: server/package-lock.json
|
||||
# - name: Prepare and start Spacebar server
|
||||
# run: |
|
||||
# npm install
|
||||
# npm run setup
|
||||
# npm run start &
|
||||
# working-directory: ./server
|
||||
# - uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# cache-all-crates: "true"
|
||||
# prefix-key: "macos-safari"
|
||||
# - name: Run WASM tests with Safari, Firefox, Chrome
|
||||
# run: |
|
||||
# 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
|
||||
# SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast
|
||||
- name: Prepare and start Spacebar server
|
||||
run: |
|
||||
npm install
|
||||
npm run setup
|
||||
npm run start &
|
||||
working-directory: ./server
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
prefix-key: "linux"
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- name: Build, Test with nextest, Publish Coverage
|
||||
run: |
|
||||
if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then
|
||||
if [ "${{github.event.pull_request.head.ref}}" = "main" ]; then
|
||||
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 cargo-tarpaulin --force
|
||||
cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120
|
||||
else
|
||||
echo "Code Coverage step is skipped on non-main PRs and PRs from forks."
|
||||
cargo nextest run --verbose --all-features
|
||||
fi
|
||||
else
|
||||
echo "Code Coverage step is skipped on non-main PRs and PRs from forks."
|
||||
cargo nextest run --verbose --all-features
|
||||
fi
|
||||
linux-non-default-features:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check common non-default feature configurations
|
||||
run: |
|
||||
echo "No features:"
|
||||
cargo check --features="" --no-default-features
|
||||
echo "Only client:"
|
||||
cargo check --features="client" --no-default-features
|
||||
echo "Only backend:"
|
||||
cargo check --features="backend" --no-default-features
|
||||
echo "Only voice:"
|
||||
cargo check --features="voice" --no-default-features
|
||||
echo "Only voice gateway:"
|
||||
cargo check --features="voice_gateway" --no-default-features
|
||||
echo "Backend + client:"
|
||||
cargo check --features="backend, client" --no-default-features
|
||||
echo "Backend + voice:"
|
||||
cargo check --features="backend, voice" --no-default-features
|
||||
echo "Backend + voice gateway:"
|
||||
cargo check --features="backend, voice_gateway" --no-default-features
|
||||
echo "Client + voice gateway:"
|
||||
cargo check --features="client, voice_gateway" --no-default-features
|
||||
wasm-gecko:
|
||||
runs-on: macos-latest
|
||||
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@v4
|
||||
with:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Clone spacebar server
|
||||
run: |
|
||||
git clone https://github.com/bitfl0wer/server.git
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
cache: "npm"
|
||||
cache-dependency-path: server/package-lock.json
|
||||
- name: Prepare and start Spacebar server
|
||||
run: |
|
||||
npm install
|
||||
npm run setup
|
||||
npm run start &
|
||||
working-directory: ./server
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
prefix-key: "macos"
|
||||
- name: Run WASM tests with Safari, Firefox, Chrome
|
||||
run: |
|
||||
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, voice_gateway"
|
||||
- name: Prepare and start Spacebar server
|
||||
run: |
|
||||
npm install
|
||||
npm run setup
|
||||
npm run start &
|
||||
working-directory: ./server
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
prefix-key: "macos"
|
||||
- name: Run WASM tests with Safari, Firefox, Chrome
|
||||
run: |
|
||||
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.93" --force
|
||||
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
|
||||
wasm-chrome:
|
||||
runs-on: macos-latest
|
||||
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@v4
|
||||
with:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Clone spacebar server
|
||||
run: |
|
||||
git clone https://github.com/bitfl0wer/server.git
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
cache: "npm"
|
||||
cache-dependency-path: server/package-lock.json
|
||||
- name: Prepare and start Spacebar server
|
||||
run: |
|
||||
npm install
|
||||
npm run setup
|
||||
npm run start &
|
||||
working-directory: ./server
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
prefix-key: "macos"
|
||||
- name: Run WASM tests with Safari, Firefox, Chrome
|
||||
run: |
|
||||
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, voice_gateway"
|
||||
- name: Prepare and start Spacebar server
|
||||
run: |
|
||||
npm install
|
||||
npm run setup
|
||||
npm run start &
|
||||
working-directory: ./server
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
prefix-key: "macos"
|
||||
- name: Run WASM tests with Safari, Firefox, Chrome
|
||||
run: |
|
||||
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.93" --force
|
||||
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
name: cargo doc lints
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "preserve/*" ]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
|
||||
jobs:
|
||||
cargo-doc-lints:
|
||||
name: Run cargo doc for doc lints
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install aditional components for sarif
|
||||
run: cargo install clippy-sarif sarif-fmt
|
||||
|
||||
- name: Run cargo doc
|
||||
run: cargo doc --no-deps --all-features --locked --message-format=json | clippy-sarif | sed 's/rust-lang.github.io\/rust-clippy/doc.rust-lang.org\/rustdoc\/lints.html/g' | sed 's/clippy/rustdoc/g' | tee cargo-doc-results.sarif | sarif-fmt
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: cargo-doc-results.sarif
|
||||
wait-for-processing: true
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
**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.**
|
||||
|
||||
Contributions should always fork from and merge back into the `dev` branch.
|
||||
|
||||
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).
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
91
Cargo.toml
91
Cargo.toml
|
@ -1,80 +1,93 @@
|
|||
[package]
|
||||
name = "chorus"
|
||||
description = "A library for interacting with multiple Spacebar-compatible Instances at once."
|
||||
version = "0.15.0"
|
||||
version = "0.16.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"
|
||||
rust-version = "1.70.0"
|
||||
|
||||
|
||||
[features]
|
||||
default = ["client", "rt-multi-thread"]
|
||||
backend = ["dep:poem", "dep:sqlx"]
|
||||
backend = ["poem", "sqlx", "sqlx-pg-uint"]
|
||||
rt-multi-thread = ["tokio/rt-multi-thread"]
|
||||
rt = ["tokio/rt"]
|
||||
client = []
|
||||
client = ["flate2"]
|
||||
voice = ["voice_udp", "voice_gateway"]
|
||||
voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
|
||||
voice_gateway = []
|
||||
sqlx-pg-uint = ["dep:sqlx-pg-uint", "sqlx-pg-uint/serde"]
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
tokio = { version = "1.39.3", features = ["macros", "sync"] }
|
||||
serde = { version = "1.0.209", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1.0.127", features = ["raw_value"] }
|
||||
serde-aux = "4.5.0"
|
||||
serde_with = "3.9.0"
|
||||
serde_repr = "0.1.19"
|
||||
reqwest = { features = [
|
||||
"multipart",
|
||||
"json",
|
||||
"rustls-tls-webpki-roots",
|
||||
], version = "=0.11.26", default-features = false }
|
||||
url = "2.5.2"
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
regex = "1.10.6"
|
||||
custom_error = "1.9.2"
|
||||
futures-util = "0.3.30"
|
||||
http = "0.2.11"
|
||||
http = "0.2.12"
|
||||
base64 = "0.21.7"
|
||||
bitflags = { version = "2.4.1", features = ["serde"] }
|
||||
lazy_static = "1.4.0"
|
||||
poem = { version = "1.3.59", optional = true }
|
||||
thiserror = "1.0.56"
|
||||
bitflags = { version = "2.6.0", features = ["serde"] }
|
||||
lazy_static = "1.5.0"
|
||||
poem = { version = "3.0.1", features = ["multipart"], optional = true }
|
||||
thiserror = "1.0.63"
|
||||
jsonwebtoken = "8.3.0"
|
||||
log = "0.4.20"
|
||||
async-trait = "0.1.77"
|
||||
chorus-macros = "0.2.0"
|
||||
sqlx = { version = "0.7.3", features = [
|
||||
"mysql",
|
||||
"sqlite",
|
||||
log = "0.4.22"
|
||||
async-trait = "0.1.81"
|
||||
chorus-macros = { path = "./chorus-macros", version = "0" } # Note: version here is used when releasing. This will use the latest release. Make sure to republish the crate when code in macros is changed!
|
||||
sqlx = { version = "0.8.1", features = [
|
||||
"json",
|
||||
"chrono",
|
||||
"ipnetwork",
|
||||
"runtime-tokio-native-tls",
|
||||
"any",
|
||||
"runtime-tokio-rustls",
|
||||
"postgres",
|
||||
"bigdecimal",
|
||||
], optional = true }
|
||||
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] }
|
||||
discortp = { version = "0.5.0", optional = true, features = [
|
||||
"rtp",
|
||||
"discord",
|
||||
"demux",
|
||||
] }
|
||||
crypto_secretbox = { version = "0.1.1", optional = true }
|
||||
rand = "0.8.5"
|
||||
flate2 = { version = "1.0.33", optional = true }
|
||||
webpki-roots = "0.26.3"
|
||||
pubserve = { version = "1.1.0", features = ["async", "send"] }
|
||||
sqlx-pg-uint = { version = "0.5.0", features = ["serde"], optional = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rustls = "0.21.10"
|
||||
rustls-native-certs = "0.6.3"
|
||||
rustls = "0.21.12"
|
||||
tokio-tungstenite = { version = "0.20.1", features = [
|
||||
"rustls-tls-native-roots",
|
||||
"rustls-native-certs",
|
||||
"rustls-tls-webpki-roots",
|
||||
] }
|
||||
native-tls = "0.2.11"
|
||||
hostname = "0.3.1"
|
||||
getrandom = { version = "0.2.12" }
|
||||
getrandom = { version = "0.2.15" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.12", features = ["js"] }
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
ws_stream_wasm = "0.7.4"
|
||||
wasm-bindgen-futures = "0.4.39"
|
||||
wasm-bindgen-futures = "0.4.43"
|
||||
wasmtimer = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-test = "0.3.39"
|
||||
wasm-bindgen = "0.2.89"
|
||||
lazy_static = "1.5.0"
|
||||
wasm-bindgen-test = "0.3.43"
|
||||
wasm-bindgen = "0.2.93"
|
||||
simple_logger = { version = "5.0.0", default-features = false }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
|
154
README.md
154
README.md
|
@ -28,14 +28,15 @@
|
|||
|
||||
</div>
|
||||
|
||||
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/)
|
||||
and Discord. It is designed to be easy to use, and to be compatible with both Discord and Spacebar Chat.
|
||||
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
|
||||
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
|
||||
flexibility one would expect from a library like this.
|
||||
|
||||
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
|
||||
|
||||
## A Tour of Chorus
|
||||
|
||||
Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
|
||||
Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
|
||||
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
|
||||
a WebSocket connection to the Gateway. This means that you can focus on building your application,
|
||||
instead of worrying about the underlying implementation details.
|
||||
|
@ -44,19 +45,19 @@ To get started with Chorus, import it into your project by adding the following
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
chorus = "0.15.0"
|
||||
chorus = "0.16.0"
|
||||
```
|
||||
|
||||
### Establishing a Connection
|
||||
|
||||
To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
||||
To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
||||
|
||||
```rs
|
||||
use chorus::instance::Instance;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let instance = Instance::new("https://example.com")
|
||||
let instance = Instance::new("https://example.com", None)
|
||||
.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.
|
||||
|
@ -81,7 +82,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 separate thread. Depending on
|
||||
// Each user connects to the Gateway. Each users' 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)
|
||||
|
@ -97,15 +98,33 @@ All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aar
|
|||
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use
|
||||
Chorus in your browser, or in any other environment that supports WebAssembly.
|
||||
|
||||
We recommend checking out the examples directory, as well as the documentation for more information.
|
||||
To compile for `wasm32-unknown-unknown`, execute the following command:
|
||||
|
||||
```sh
|
||||
cargo build --target=wasm32-unknown-unknown --no-default-features
|
||||
```
|
||||
|
||||
The following features are supported on `wasm32-unknown-unknown`:
|
||||
|
||||
| Feature | WASM Support |
|
||||
| ----------------- | ------------ |
|
||||
| `client` | ✅ |
|
||||
| `rt` | ✅ |
|
||||
| `rt-multi-thread` | ❌ |
|
||||
| `backend` | ❌ |
|
||||
| `voice` | ❌ |
|
||||
| `voice_udp` | ❌ |
|
||||
| `voice_gateway` | ✅ |
|
||||
|
||||
We recommend checking out the "examples" directory, as well as the documentation for more information.
|
||||
|
||||
## MSRV (Minimum Supported Rust Version)
|
||||
|
||||
Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0.
|
||||
Rust **1.70.0**. This number might change at any point while Chorus is not yet at version 1.0.0.
|
||||
|
||||
## Development Setup
|
||||
|
||||
Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version`
|
||||
Make sure that you have at least Rust 1.70.0 installed. You can check your Rust version by running `cargo --version`
|
||||
in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target.
|
||||
You can do this by running `rustup target add wasm32-unknown-unknown`.
|
||||
|
||||
|
@ -130,98 +149,23 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
|
|||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
<details>
|
||||
<summary>Progress Tracker/Roadmap</summary>
|
||||
|
||||
### Core Functionality
|
||||
- [x] Rate Limiter (hint: couldn't be fully tested due to [an Issue with the Spacebar Server](https://github.com/spacebarchat/server/issues/1022))
|
||||
- [x] [Login (the conventional way)](https://github.com/polyphony-chat/chorus/issues/1)
|
||||
- [ ] [2FA](https://github.com/polyphony-chat/chorus/issues/40)
|
||||
- [x] [Registration](https://github.com/polyphony-chat/chorus/issues/1)
|
||||
|
||||
### Messaging
|
||||
- [x] [Sending messages](https://github.com/polyphony-chat/chorus/issues/23)
|
||||
- [x] [Events (Message, User, Channel, etc.)](https://github.com/polyphony-chat/chorus/issues/51)
|
||||
- [x] Channel creation
|
||||
- [x] Channel deletion
|
||||
- [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48)
|
||||
- [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45)
|
||||
- [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45)
|
||||
- [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89)
|
||||
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
|
||||
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
|
||||
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
|
||||
- [ ] Message Search
|
||||
- [ ] Message history
|
||||
- [ ] Emoji
|
||||
- [ ] Stickers
|
||||
- [ ] [Forum channels](https://github.com/polyphony-chat/chorus/issues/90)
|
||||
|
||||
### User Management
|
||||
- [ ] [User profile customization](https://github.com/polyphony-chat/chorus/issues/41)
|
||||
- [x] Gettings users and user profiles
|
||||
- [x] [Friend requests](https://github.com/polyphony-chat/chorus/issues/92)
|
||||
- [x] [Blocking users](https://github.com/polyphony-chat/chorus/issues/92)
|
||||
- [ ] User presence (online, offline, idle, etc.)
|
||||
- [ ] User status (custom status, etc.)
|
||||
- [x] Account deletion
|
||||
|
||||
### Additional Features
|
||||
- [ ] Server discovery
|
||||
- [ ] Server templates
|
||||
|
||||
### Voice and Video
|
||||
- [ ] [Voice chat support](https://github.com/polyphony-chat/chorus/issues/49)
|
||||
- [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49)
|
||||
|
||||
### Permissions and Roles
|
||||
- [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification)
|
||||
- [x] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions)
|
||||
- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88)
|
||||
- [x] Role-based access control
|
||||
|
||||
### Guild Management
|
||||
- [x] Guild creation
|
||||
- [x] Guild deletion
|
||||
- [ ] [Guild settings (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/43)
|
||||
- [ ] Guild invites
|
||||
|
||||
### Moderation
|
||||
- [ ] Channel moderation (slow mode, etc.)
|
||||
- [ ] User sanctions (mute, kick, ban)
|
||||
- [ ] Audit logs
|
||||
|
||||
### Embeds and Rich Content
|
||||
- [x] Sending rich content in messages (links, images, videos)
|
||||
- [ ] Customizing embed appearance (title, description, color, fields)
|
||||
|
||||
### Webhooks
|
||||
- [ ] Webhook creation and management
|
||||
- [ ] Handling incoming webhook events
|
||||
|
||||
### Documentation and Examples
|
||||
- [ ] Comprehensive documentation
|
||||
- [ ] Example usage and code snippets
|
||||
- [ ] Tutorials and guides
|
||||
|
||||
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
|
||||
[Rust-url]: https://www.rust-lang.org/
|
||||
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
|
||||
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
|
||||
[clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
|
||||
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
|
||||
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
|
||||
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
|
||||
[coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
|
||||
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
|
||||
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
|
||||
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
|
||||
[issues-url]: https://github.com/polyphony-chat/chorus/issues
|
||||
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
|
||||
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
|
||||
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
|
||||
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
|
||||
</details>
|
||||
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
|
||||
[Rust-url]: https://www.rust-lang.org/
|
||||
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
|
||||
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
|
||||
[clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
|
||||
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
|
||||
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
|
||||
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
|
||||
[coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
|
||||
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
|
||||
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
|
||||
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
|
||||
[issues-url]: https://github.com/polyphony-chat/chorus/issues
|
||||
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
|
||||
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
|
||||
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
|
||||
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
|
||||
|
|
|
@ -0,0 +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 https://mozilla.org/MPL/2.0/.
|
||||
#!/bin/sh
|
||||
|
||||
cargo build --no-default-features --target=wasm32-unknown-unknown "$@"
|
|
@ -15,7 +15,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chorus-macros"
|
||||
version = "0.1.0"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"quote",
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "chorus-macros"
|
||||
version = "0.2.0"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0"
|
||||
license = "MPL-2.0"
|
||||
description = "Macros for the chorus crate."
|
||||
repository = "https://github.com/polyphony-chat/chorus"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
|
|
@ -6,6 +6,18 @@ 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();
|
||||
|
@ -143,3 +155,89 @@ pub fn composite_derive(input: TokenStream) -> TokenStream {
|
|||
_ => panic!("Composite derive macro only supports structs"),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(SqlxBitFlags)]
|
||||
pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
|
||||
let name = &ast.ident;
|
||||
|
||||
quote!{
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::Postgres> for #name {
|
||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||
<sqlx_pg_uint::PgU64 as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for #name {
|
||||
fn encode_by_ref(&self, buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||
<sqlx_pg_uint::PgU64 as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&self.bits().into(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Decode<'q, sqlx::Postgres> for #name {
|
||||
fn decode(value: <sqlx::Postgres as sqlx::Database>::ValueRef<'q>) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
<sqlx_pg_uint::PgU64 as sqlx::Decode<sqlx::Postgres>>::decode(value).map(|v| Self::from_bits_truncate(v.to_uint()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [Vec<u8>] to an unsigned, 64 bit integer. The [u64] is created using [u64::from_be_bytes].
|
||||
///
|
||||
/// Empty vectors will result in an output of `0_u64`. Only the first 8 values from the vector are
|
||||
/// being processed. Any additional values will be skipped.
|
||||
///
|
||||
/// Vectors holding less than 8 values will be treated as a vector holding 8 values, where the
|
||||
/// missing values are padded with `0_u8`.
|
||||
fn vec_u8_to_u64(vec: Vec<u8>) -> u64 {
|
||||
let mut buf: [u8; 8] = [0; 8];
|
||||
let mut position = 0;
|
||||
for read in vec.iter() {
|
||||
buf[position] = *read;
|
||||
position += 1;
|
||||
if position > 7 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
u64::from_be_bytes(buf)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(SerdeBitFlags)]
|
||||
pub fn serde_bitflag_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
|
||||
let name = &ast.ident;
|
||||
|
||||
quote! {
|
||||
impl std::str::FromStr for #name {
|
||||
type Err = std::num::ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<#name, Self::Err> {
|
||||
s.parse::<u64>().map(#name::from_bits).map(|f| f.unwrap_or(#name::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for #name {
|
||||
fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.bits().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for #name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<#name, D::Error> where D: serde::de::Deserializer<'de> + Sized {
|
||||
// let s = String::deserialize(deserializer)?.parse::<u64>().map_err(serde::de::Error::custom)?;
|
||||
let s = crate::types::serde::string_or_u64(deserializer)?;
|
||||
|
||||
// Note: while truncating may not be ideal, it's better than a panic if there are
|
||||
// extra flags
|
||||
Ok(Self::from_bits_truncate(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// This example showcase how to properly use gateway observers.
|
||||
// (This assumes you have a manually created gateway, if you created
|
||||
// a ChorusUser by e.g. logging in, you can access the gateway with user.gateway)
|
||||
//
|
||||
// To properly run it, you will need to change the token below.
|
||||
|
||||
|
@ -12,12 +14,12 @@ const TOKEN: &str = "";
|
|||
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chorus::gateway::Gateway;
|
||||
use chorus::gateway::{Gateway, GatewayOptions};
|
||||
use chorus::{
|
||||
self,
|
||||
gateway::Observer,
|
||||
types::{GatewayIdentifyPayload, GatewayReady},
|
||||
};
|
||||
use pubserve::Subscriber;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::{self};
|
||||
|
||||
|
@ -32,11 +34,16 @@ use wasmtimer::tokio::sleep;
|
|||
#[derive(Debug)]
|
||||
pub struct ExampleObserver {}
|
||||
|
||||
// This struct can observe GatewayReady events when subscribed, because it implements the trait Observer<GatewayReady>.
|
||||
// The Observer trait can be implemented for a struct for a given websocketevent to handle observing it
|
||||
// This struct can observe GatewayReady events when subscribed, because it implements the trait Subscriber<GatewayReady>.
|
||||
// The Subscriber trait can be implemented for a struct for a given websocketevent to handle observing it
|
||||
//
|
||||
// Note that this trait is quite generic and can be use to observe any type.
|
||||
//
|
||||
// It is just used for WebSocketEvents in chorus.
|
||||
//
|
||||
// One struct can be an observer of multiple websocketevents, if needed
|
||||
#[async_trait]
|
||||
impl Observer<GatewayReady> for ExampleObserver {
|
||||
impl Subscriber<GatewayReady> for ExampleObserver {
|
||||
// After we subscribe to an event this function is called every time we receive it
|
||||
async fn update(&self, _data: &GatewayReady) {
|
||||
println!("Observed Ready!");
|
||||
|
@ -45,10 +52,18 @@ impl Observer<GatewayReady> for ExampleObserver {
|
|||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let gateway_websocket_url = GATEWAY_URL.to_string();
|
||||
let gateway_websocket_url = GATEWAY_URL;
|
||||
|
||||
// These options specify the encoding format, compression, etc
|
||||
//
|
||||
// For most cases the defaults should work, though some implementations
|
||||
// might only support some formats or not support compression
|
||||
let options = GatewayOptions::default();
|
||||
|
||||
// Initiate the gateway connection
|
||||
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
|
||||
let gateway = Gateway::spawn(gateway_websocket_url, options)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create an instance of our observer
|
||||
let observer = ExampleObserver {};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// 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)
|
||||
// (e. g. not through ChorusUser or Instance)
|
||||
//
|
||||
// To properly run it, you will need to modify the token below.
|
||||
|
||||
|
@ -14,7 +14,7 @@ const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
|
|||
|
||||
use std::time::Duration;
|
||||
|
||||
use chorus::gateway::Gateway;
|
||||
use chorus::gateway::{Gateway, GatewayOptions};
|
||||
use chorus::{self, types::GatewayIdentifyPayload};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -25,10 +25,18 @@ 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() {
|
||||
let gateway_websocket_url = GATEWAY_URL.to_string();
|
||||
let gateway_websocket_url = GATEWAY_URL;
|
||||
|
||||
// These options specify the encoding format, compression, etc
|
||||
//
|
||||
// For most cases the defaults should work, though some implementations
|
||||
// might only support some formats or not support compression
|
||||
let options = GatewayOptions::default();
|
||||
|
||||
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
|
||||
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
|
||||
let gateway = Gateway::spawn(gateway_websocket_url, options)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use chorus::instance::Instance;
|
|||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let instance = Instance::new("https://example.com/")
|
||||
let instance = Instance::new("https://example.com/", None)
|
||||
.await
|
||||
.expect("Failed to connect to the Spacebar server");
|
||||
dbg!(instance.instance_info);
|
||||
|
|
|
@ -7,7 +7,7 @@ use chorus::types::LoginSchema;
|
|||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let mut instance = Instance::new("https://example.com/")
|
||||
let mut instance = Instance::new("https://example.com/", None)
|
||||
.await
|
||||
.expect("Failed to connect to the Spacebar server");
|
||||
// Assume, you already have an account created on this instance. Registering an account works
|
||||
|
|
|
@ -22,11 +22,9 @@ use simplelog::{TermLogger, Config, WriteLogger};
|
|||
use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration};
|
||||
|
||||
use chorus::{
|
||||
gateway::{Observer, Gateway},
|
||||
gateway::{Gateway, GatewayOptions, Observer},
|
||||
types::{
|
||||
GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking,
|
||||
SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol,
|
||||
VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState,
|
||||
GatewayIdentifyPayload, GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking, SpeakingBitflags, SsrcDefinition, UpdateVoiceState, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol, VoiceReady, VoiceServerUpdate
|
||||
},
|
||||
voice::{
|
||||
gateway::{VoiceGateway, VoiceGatewayHandle},
|
||||
|
@ -219,7 +217,7 @@ impl Observer<Speaking> for VoiceHandler {
|
|||
println!(
|
||||
"Received Speaking! (SRRC: {}, flags: {:?})",
|
||||
data.ssrc,
|
||||
SpeakingBitflags::from_bits(data.speaking).unwrap()
|
||||
SpeakingBitflags::from_bits(data.speaking.into()).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -253,7 +251,7 @@ async fn main() {
|
|||
])
|
||||
.unwrap();
|
||||
|
||||
let gateway = Gateway::spawn(GATEWAY_URL.to_string())
|
||||
let gateway = Gateway::spawn(GATEWAY_URL.to_string(), GatewayOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
name: Semver release checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
semver-checks:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: obi1kenobi/cargo-semver-checks-action@v2
|
|
@ -11,7 +11,7 @@ use crate::errors::ChorusResult;
|
|||
use crate::gateway::Gateway;
|
||||
use crate::instance::{ChorusUser, Instance};
|
||||
use crate::ratelimiter::ChorusRequest;
|
||||
use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema};
|
||||
use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema, User};
|
||||
|
||||
impl Instance {
|
||||
/// Logs into an existing account on the spacebar server.
|
||||
|
@ -30,27 +30,21 @@ impl Instance {
|
|||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||
// request (since login is an instance wide limit), which is why we are just cloning the
|
||||
// instances' limits to pass them on as user_rate_limits later.
|
||||
let mut shell =
|
||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
||||
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
|
||||
|
||||
let login_result = chorus_request
|
||||
.deserialize_response::<LoginResult>(&mut shell)
|
||||
.deserialize_response::<LoginResult>(&mut user)
|
||||
.await?;
|
||||
let object = self.get_user(login_result.token.clone(), None).await?;
|
||||
if self.limits_information.is_some() {
|
||||
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap();
|
||||
}
|
||||
user.set_token(&login_result.token);
|
||||
user.settings = login_result.settings;
|
||||
|
||||
let object = User::get(&mut user, None).await?;
|
||||
*user.object.write().unwrap() = object;
|
||||
|
||||
let mut identify = GatewayIdentifyPayload::common();
|
||||
let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap();
|
||||
identify.token = login_result.token.clone();
|
||||
gateway.send_identify(identify).await;
|
||||
let user = ChorusUser::new(
|
||||
Arc::new(RwLock::new(self.clone())),
|
||||
login_result.token,
|
||||
self.clone_limits_if_some(),
|
||||
login_result.settings,
|
||||
Arc::new(RwLock::new(object)),
|
||||
gateway,
|
||||
);
|
||||
identify.token = user.token();
|
||||
user.gateway.send_identify(identify).await;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,27 +22,19 @@ pub mod register;
|
|||
|
||||
impl Instance {
|
||||
/// Logs into an existing account on the spacebar server, using only a token.
|
||||
pub async fn login_with_token(&mut self, token: String) -> ChorusResult<ChorusUser> {
|
||||
let object_result = self.get_user(token.clone(), None).await;
|
||||
if let Err(e) = object_result {
|
||||
return Result::Err(e);
|
||||
}
|
||||
pub async fn login_with_token(&mut self, token: &str) -> ChorusResult<ChorusUser> {
|
||||
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
|
||||
|
||||
let object = User::get(&mut user, None).await?;
|
||||
let settings = User::get_settings(&mut user).await?;
|
||||
|
||||
*user.object.write().unwrap() = object;
|
||||
*user.settings.write().unwrap() = settings;
|
||||
|
||||
let user_settings = User::get_settings(&token, &self.urls.api, &mut self.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let mut identify = GatewayIdentifyPayload::common();
|
||||
let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap();
|
||||
identify.token = token.clone();
|
||||
gateway.send_identify(identify).await;
|
||||
let user = ChorusUser::new(
|
||||
Arc::new(RwLock::new(self.clone())),
|
||||
token.clone(),
|
||||
self.clone_limits_if_some(),
|
||||
Arc::new(RwLock::new(user_settings)),
|
||||
Arc::new(RwLock::new(object_result.unwrap())),
|
||||
gateway,
|
||||
);
|
||||
identify.token = user.token();
|
||||
user.gateway.send_identify(identify).await;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use reqwest::Client;
|
|||
use serde_json::to_string;
|
||||
|
||||
use crate::gateway::{Gateway, GatewayHandle};
|
||||
use crate::types::GatewayIdentifyPayload;
|
||||
use crate::types::{GatewayIdentifyPayload, User};
|
||||
use crate::{
|
||||
errors::ChorusResult,
|
||||
instance::{ChorusUser, Instance, Token},
|
||||
|
@ -37,29 +37,24 @@ impl Instance {
|
|||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||
// request (since register is an instance wide limit), which is why we are just cloning
|
||||
// the instances' limits to pass them on as user_rate_limits later.
|
||||
let mut shell =
|
||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
||||
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
|
||||
|
||||
let token = chorus_request
|
||||
.deserialize_response::<Token>(&mut shell)
|
||||
.deserialize_response::<Token>(&mut user)
|
||||
.await?
|
||||
.token;
|
||||
if self.limits_information.is_some() {
|
||||
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap();
|
||||
}
|
||||
let user_object = self.get_user(token.clone(), None).await.unwrap();
|
||||
let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), self).await?;
|
||||
user.set_token(&token);
|
||||
|
||||
let object = User::get(&mut user, None).await?;
|
||||
let settings = User::get_settings(&mut user).await?;
|
||||
|
||||
*user.object.write().unwrap() = object;
|
||||
*user.settings.write().unwrap() = settings;
|
||||
|
||||
let mut identify = GatewayIdentifyPayload::common();
|
||||
let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap();
|
||||
identify.token = token.clone();
|
||||
gateway.send_identify(identify).await;
|
||||
let user = ChorusUser::new(
|
||||
Arc::new(RwLock::new(self.clone())),
|
||||
token.clone(),
|
||||
self.clone_limits_if_some(),
|
||||
Arc::new(RwLock::new(settings)),
|
||||
Arc::new(RwLock::new(user_object)),
|
||||
gateway,
|
||||
);
|
||||
identify.token = user.token();
|
||||
user.gateway.send_identify(identify).await;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use crate::types::{
|
|||
};
|
||||
|
||||
impl Message {
|
||||
#[allow(clippy::useless_conversion)]
|
||||
/// Sends a message in the channel with the provided channel_id.
|
||||
/// Returns the sent message.
|
||||
///
|
||||
|
@ -40,7 +41,7 @@ impl Message {
|
|||
chorus_request.deserialize_response::<Message>(user).await
|
||||
} else {
|
||||
for (index, attachment) in message.attachments.iter_mut().enumerate() {
|
||||
attachment.get_mut(index).unwrap().id = Some(index as i16);
|
||||
attachment.get_mut(index).unwrap().id = Some((index as u64).into());
|
||||
}
|
||||
let mut form = reqwest::multipart::Form::new();
|
||||
let payload_json = to_string(&message).unwrap();
|
||||
|
@ -111,7 +112,7 @@ impl Message {
|
|||
let result = request.send_request(user).await?;
|
||||
let result_json = result.json::<Value>().await.unwrap();
|
||||
if !result_json.is_object() {
|
||||
return Err(search_error(result_json.to_string()));
|
||||
return Err(search_error(result_json.to_string().as_str()));
|
||||
}
|
||||
let value_map = result_json.as_object().unwrap();
|
||||
if let Some(messages) = value_map.get("messages") {
|
||||
|
@ -122,7 +123,7 @@ impl Message {
|
|||
}
|
||||
// The code below might be incorrect. We'll cross that bridge when we come to it
|
||||
if !value_map.contains_key("code") || !value_map.contains_key("retry_after") {
|
||||
return Err(search_error(result_json.to_string()));
|
||||
return Err(search_error(result_json.to_string().as_str()));
|
||||
}
|
||||
let code = value_map.get("code").unwrap().as_u64().unwrap();
|
||||
let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();
|
||||
|
@ -481,7 +482,7 @@ impl Message {
|
|||
}
|
||||
}
|
||||
|
||||
fn search_error(result_text: String) -> ChorusError {
|
||||
fn search_error(result_text: &str) -> ChorusError {
|
||||
ChorusError::InvalidResponse {
|
||||
error: format!(
|
||||
"Got unexpected Response, or Response which is not valid JSON. Response: \n{}",
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
};
|
||||
|
||||
/// Useful metadata for working with [`types::Reaction`], bundled together nicely.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
|
||||
pub struct ReactionMeta {
|
||||
pub message_id: types::Snowflake,
|
||||
pub channel_id: types::Snowflake,
|
||||
|
|
|
@ -44,6 +44,16 @@ impl ChorusUser {
|
|||
&mut self,
|
||||
query: Option<GetUserGuildSchema>,
|
||||
) -> ChorusResult<Vec<Guild>> {
|
||||
|
||||
let query_parameters = {
|
||||
if let Some(query_some) = query {
|
||||
query_some.to_query()
|
||||
}
|
||||
else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let url = format!(
|
||||
"{}/users/@me/guilds",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
|
@ -53,7 +63,7 @@ impl ChorusUser {
|
|||
.get(url)
|
||||
.header("Authorization", self.token())
|
||||
.header("Content-Type", "application/json")
|
||||
.body(to_string(&query).unwrap()),
|
||||
.query(&query_parameters),
|
||||
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
|
|
|
@ -30,13 +30,9 @@ impl ChorusUser {
|
|||
/// Gets the user's settings.
|
||||
///
|
||||
/// # Notes
|
||||
/// This functions is a wrapper around [`User::get_settings`].
|
||||
pub async fn get_settings(
|
||||
token: &String,
|
||||
url_api: &String,
|
||||
instance: &mut Instance,
|
||||
) -> ChorusResult<UserSettings> {
|
||||
User::get_settings(token, url_api, instance).await
|
||||
/// This function is a wrapper around [`User::get_settings`].
|
||||
pub async fn get_settings(&mut self) -> ChorusResult<UserSettings> {
|
||||
User::get_settings(self).await
|
||||
}
|
||||
|
||||
/// Modifies the current user's representation. (See [`User`])
|
||||
|
@ -44,12 +40,18 @@ impl ChorusUser {
|
|||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#modify-current-user>
|
||||
pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult<User> {
|
||||
if modify_schema.new_password.is_some()
|
||||
|
||||
// See <https://docs.discord.sex/resources/user#json-params>, note 1
|
||||
let requires_current_password = modify_schema.username.is_some()
|
||||
|| modify_schema.discriminator.is_some()
|
||||
|| modify_schema.email.is_some()
|
||||
|| modify_schema.code.is_some()
|
||||
{
|
||||
|| modify_schema.date_of_birth.is_some()
|
||||
|| modify_schema.new_password.is_some();
|
||||
|
||||
if requires_current_password && modify_schema.current_password.is_none() {
|
||||
return Err(ChorusError::PasswordRequired);
|
||||
}
|
||||
|
||||
let request = Client::new()
|
||||
.patch(format!(
|
||||
"{}/users/@me",
|
||||
|
@ -118,56 +120,21 @@ impl User {
|
|||
///
|
||||
/// # Reference
|
||||
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/user_settings.html#get-usersmesettings>
|
||||
pub async fn get_settings(
|
||||
token: &String,
|
||||
url_api: &String,
|
||||
instance: &mut Instance,
|
||||
) -> ChorusResult<UserSettings> {
|
||||
pub async fn get_settings(user: &mut ChorusUser) -> ChorusResult<UserSettings> {
|
||||
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
|
||||
let request: reqwest::RequestBuilder = Client::new()
|
||||
.get(format!("{}/users/@me/settings", url_api))
|
||||
.header("Authorization", token);
|
||||
let mut user =
|
||||
ChorusUser::shell(Arc::new(RwLock::new(instance.clone())), token.clone()).await;
|
||||
.header("Authorization", user.token());
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
let result = match chorus_request.send_request(&mut user).await {
|
||||
Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()),
|
||||
match chorus_request.send_request(user).await {
|
||||
Ok(result) => {
|
||||
let result_text = result.text().await.unwrap();
|
||||
Ok(serde_json::from_str(&result_text).unwrap())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
if instance.limits_information.is_some() {
|
||||
instance.limits_information.as_mut().unwrap().ratelimits = user
|
||||
.belongs_to
|
||||
.read()
|
||||
.unwrap()
|
||||
.clone_limits_if_some()
|
||||
.unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
/// Gets a user by id, or if the id is None, gets the current user.
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#get-user> and
|
||||
/// <https://discord-userdoccers.vercel.app/resources/user#get-current-user>
|
||||
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
|
||||
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
|
||||
let result = User::get(&mut user, id).await;
|
||||
if self.limits_information.is_some() {
|
||||
self.limits_information.as_mut().unwrap().ratelimits = user
|
||||
.belongs_to
|
||||
.read()
|
||||
.unwrap()
|
||||
.clone_limits_if_some()
|
||||
.unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use custom_error::custom_error;
|
||||
|
||||
use crate::types::WebSocketEvent;
|
||||
use chorus_macros::WebSocketEvent;
|
||||
|
||||
custom_error! {
|
||||
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||
|
@ -72,7 +73,7 @@ custom_error! {
|
|||
/// 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]
|
||||
|
@ -92,22 +93,20 @@ custom_error! {
|
|||
DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
|
||||
|
||||
// Errors when initiating a gateway connection
|
||||
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}",
|
||||
CannotConnect{error: String} = "Cannot connect due to a websocket 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}",
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GatewayError {}
|
||||
|
||||
custom_error! {
|
||||
/// Voice Gateway errors
|
||||
///
|
||||
/// Similar to [GatewayError].
|
||||
///
|
||||
/// See <https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes>;
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
#[derive(Clone, Default, PartialEq, Eq, WebSocketEvent)]
|
||||
pub VoiceGatewayError
|
||||
// Errors we receive
|
||||
#[default]
|
||||
|
@ -125,18 +124,16 @@ custom_error! {
|
|||
UnknownEncryptionMode = "Server failed to decrypt data",
|
||||
|
||||
// Errors when initiating a gateway connection
|
||||
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}",
|
||||
CannotConnect{error: String} = "Cannot connect due to a websocket 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}",
|
||||
}
|
||||
|
||||
impl WebSocketEvent for VoiceGatewayError {}
|
||||
|
||||
custom_error! {
|
||||
/// Voice UDP errors.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq, WebSocketEvent)]
|
||||
pub VoiceUdpError
|
||||
|
||||
// General errors
|
||||
|
@ -155,4 +152,3 @@ custom_error! {
|
|||
CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}",
|
||||
}
|
||||
|
||||
impl WebSocketEvent for VoiceUdpError {}
|
||||
|
|
|
@ -2,19 +2,21 @@
|
|||
// 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 custom_error::custom_error;
|
||||
use futures_util::{
|
||||
stream::{SplitSink, SplitStream},
|
||||
StreamExt,
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{
|
||||
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream,
|
||||
connect_async_tls_with_config, connect_async_with_config, tungstenite, Connector,
|
||||
MaybeTlsStream, WebSocketStream,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::errors::GatewayError;
|
||||
use crate::gateway::GatewayMessage;
|
||||
use crate::gateway::{GatewayMessage, RawGatewayMessage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TungsteniteBackend;
|
||||
|
||||
// These could be made into inherent associated types when that's stabilized
|
||||
|
@ -22,46 +24,70 @@ pub type TungsteniteSink =
|
|||
SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, tungstenite::Message>;
|
||||
pub type TungsteniteStream = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
|
||||
|
||||
custom_error! {
|
||||
pub TungsteniteBackendError
|
||||
FailedToLoadCerts{error: std::io::Error} = "failed to load platform native certs: {error}",
|
||||
TungsteniteError{error: tungstenite::error::Error} = "encountered a tungstenite error: {error}",
|
||||
}
|
||||
|
||||
impl TungsteniteBackend {
|
||||
pub async fn connect(
|
||||
websocket_url: &str,
|
||||
) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> {
|
||||
let mut roots = rustls::RootCertStore::empty();
|
||||
let certs = rustls_native_certs::load_native_certs();
|
||||
) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> {
|
||||
let websocket_url_parsed =
|
||||
Url::parse(websocket_url).map_err(|_| TungsteniteBackendError::TungsteniteError {
|
||||
error: tungstenite::error::Error::Url(
|
||||
tungstenite::error::UrlError::UnsupportedUrlScheme,
|
||||
),
|
||||
})?;
|
||||
if websocket_url_parsed.scheme() == "ws" {
|
||||
let (websocket_stream, _) =
|
||||
match connect_async_with_config(websocket_url, None, false).await {
|
||||
Ok(websocket_stream) => websocket_stream,
|
||||
Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
|
||||
};
|
||||
|
||||
if let Err(e) = certs {
|
||||
log::error!("Failed to load platform native certs! {:?}", e);
|
||||
return Err(GatewayError::CannotConnect {
|
||||
error: format!("{:?}", e),
|
||||
});
|
||||
Ok(websocket_stream.split())
|
||||
} else if websocket_url_parsed.scheme() == "wss" {
|
||||
let certs = webpki_roots::TLS_SERVER_ROOTS;
|
||||
let roots = rustls::RootCertStore {
|
||||
roots: certs
|
||||
.iter()
|
||||
.map(|cert| {
|
||||
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
cert.subject.to_vec(),
|
||||
cert.subject_public_key_info.to_vec(),
|
||||
cert.name_constraints.as_ref().map(|der| der.to_vec()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
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(TungsteniteBackendError::TungsteniteError { error: e }),
|
||||
};
|
||||
|
||||
Ok(websocket_stream.split())
|
||||
} else {
|
||||
Err(TungsteniteBackendError::TungsteniteError {
|
||||
error: tungstenite::error::Error::Url(
|
||||
tungstenite::error::UrlError::UnsupportedUrlScheme,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
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(GatewayError::CannotConnect {
|
||||
error: e.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(websocket_stream.split())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,3 +102,22 @@ impl From<tungstenite::Message> for GatewayMessage {
|
|||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawGatewayMessage> for tungstenite::Message {
|
||||
fn from(message: RawGatewayMessage) -> Self {
|
||||
match message {
|
||||
RawGatewayMessage::Text(text) => tungstenite::Message::Text(text),
|
||||
RawGatewayMessage::Bytes(bytes) => tungstenite::Message::Binary(bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tungstenite::Message> for RawGatewayMessage {
|
||||
fn from(value: tungstenite::Message) -> Self {
|
||||
match value {
|
||||
tungstenite::Message::Binary(bytes) => RawGatewayMessage::Bytes(bytes),
|
||||
tungstenite::Message::Text(text) => RawGatewayMessage::Text(text),
|
||||
_ => RawGatewayMessage::Text(value.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@ use futures_util::{
|
|||
|
||||
use ws_stream_wasm::*;
|
||||
|
||||
use crate::errors::GatewayError;
|
||||
use crate::gateway::GatewayMessage;
|
||||
use crate::gateway::{GatewayMessage, RawGatewayMessage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WasmBackend;
|
||||
|
@ -22,13 +21,8 @@ pub type WasmStream = SplitStream<WsStream>;
|
|||
impl WasmBackend {
|
||||
pub async fn connect(
|
||||
websocket_url: &str,
|
||||
) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> {
|
||||
let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await {
|
||||
Ok(stream) => Ok(stream),
|
||||
Err(e) => Err(GatewayError::CannotConnect {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}?;
|
||||
) -> Result<(WasmSink, WasmStream), ws_stream_wasm::WsErr> {
|
||||
let (_, websocket_stream) = WsMeta::connect(websocket_url, None).await?;
|
||||
|
||||
Ok(websocket_stream.split())
|
||||
}
|
||||
|
@ -52,3 +46,21 @@ impl From<WsMessage> for GatewayMessage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawGatewayMessage> for WsMessage {
|
||||
fn from(message: RawGatewayMessage) -> Self {
|
||||
match message {
|
||||
RawGatewayMessage::Text(text) => WsMessage::Text(text),
|
||||
RawGatewayMessage::Bytes(bytes) => WsMessage::Binary(bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WsMessage> for RawGatewayMessage {
|
||||
fn from(value: WsMessage) -> Self {
|
||||
match value {
|
||||
WsMessage::Binary(bytes) => RawGatewayMessage::Bytes(bytes),
|
||||
WsMessage::Text(text) => RawGatewayMessage::Text(text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// 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 pubserve::Publisher;
|
||||
|
||||
use super::*;
|
||||
use crate::types;
|
||||
|
||||
|
@ -23,144 +25,144 @@ pub struct Events {
|
|||
pub call: Call,
|
||||
pub voice: Voice,
|
||||
pub webhooks: Webhooks,
|
||||
pub gateway_identify_payload: GatewayEvent<types::GatewayIdentifyPayload>,
|
||||
pub gateway_resume: GatewayEvent<types::GatewayResume>,
|
||||
pub error: GatewayEvent<GatewayError>,
|
||||
pub gateway_identify_payload: Publisher<types::GatewayIdentifyPayload>,
|
||||
pub gateway_resume: Publisher<types::GatewayResume>,
|
||||
pub error: Publisher<GatewayError>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Application {
|
||||
pub command_permissions_update: GatewayEvent<types::ApplicationCommandPermissionsUpdate>,
|
||||
pub command_permissions_update: Publisher<types::ApplicationCommandPermissionsUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AutoModeration {
|
||||
pub rule_create: GatewayEvent<types::AutoModerationRuleCreate>,
|
||||
pub rule_update: GatewayEvent<types::AutoModerationRuleUpdate>,
|
||||
pub rule_delete: GatewayEvent<types::AutoModerationRuleDelete>,
|
||||
pub action_execution: GatewayEvent<types::AutoModerationActionExecution>,
|
||||
pub rule_create: Publisher<types::AutoModerationRuleCreate>,
|
||||
pub rule_update: Publisher<types::AutoModerationRuleUpdate>,
|
||||
pub rule_delete: Publisher<types::AutoModerationRuleDelete>,
|
||||
pub action_execution: Publisher<types::AutoModerationActionExecution>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Session {
|
||||
pub ready: GatewayEvent<types::GatewayReady>,
|
||||
pub ready_supplemental: GatewayEvent<types::GatewayReadySupplemental>,
|
||||
pub replace: GatewayEvent<types::SessionsReplace>,
|
||||
pub reconnect: GatewayEvent<types::GatewayReconnect>,
|
||||
pub invalid: GatewayEvent<types::GatewayInvalidSession>,
|
||||
pub ready: Publisher<types::GatewayReady>,
|
||||
pub ready_supplemental: Publisher<types::GatewayReadySupplemental>,
|
||||
pub replace: Publisher<types::SessionsReplace>,
|
||||
pub reconnect: Publisher<types::GatewayReconnect>,
|
||||
pub invalid: Publisher<types::GatewayInvalidSession>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StageInstance {
|
||||
pub create: GatewayEvent<types::StageInstanceCreate>,
|
||||
pub update: GatewayEvent<types::StageInstanceUpdate>,
|
||||
pub delete: GatewayEvent<types::StageInstanceDelete>,
|
||||
pub create: Publisher<types::StageInstanceCreate>,
|
||||
pub update: Publisher<types::StageInstanceUpdate>,
|
||||
pub delete: Publisher<types::StageInstanceDelete>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Message {
|
||||
pub create: GatewayEvent<types::MessageCreate>,
|
||||
pub update: GatewayEvent<types::MessageUpdate>,
|
||||
pub delete: GatewayEvent<types::MessageDelete>,
|
||||
pub delete_bulk: GatewayEvent<types::MessageDeleteBulk>,
|
||||
pub reaction_add: GatewayEvent<types::MessageReactionAdd>,
|
||||
pub reaction_remove: GatewayEvent<types::MessageReactionRemove>,
|
||||
pub reaction_remove_all: GatewayEvent<types::MessageReactionRemoveAll>,
|
||||
pub reaction_remove_emoji: GatewayEvent<types::MessageReactionRemoveEmoji>,
|
||||
pub ack: GatewayEvent<types::MessageACK>,
|
||||
pub create: Publisher<types::MessageCreate>,
|
||||
pub update: Publisher<types::MessageUpdate>,
|
||||
pub delete: Publisher<types::MessageDelete>,
|
||||
pub delete_bulk: Publisher<types::MessageDeleteBulk>,
|
||||
pub reaction_add: Publisher<types::MessageReactionAdd>,
|
||||
pub reaction_remove: Publisher<types::MessageReactionRemove>,
|
||||
pub reaction_remove_all: Publisher<types::MessageReactionRemoveAll>,
|
||||
pub reaction_remove_emoji: Publisher<types::MessageReactionRemoveEmoji>,
|
||||
pub ack: Publisher<types::MessageACK>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct User {
|
||||
pub update: GatewayEvent<types::UserUpdate>,
|
||||
pub guild_settings_update: GatewayEvent<types::UserGuildSettingsUpdate>,
|
||||
pub presence_update: GatewayEvent<types::PresenceUpdate>,
|
||||
pub typing_start: GatewayEvent<types::TypingStartEvent>,
|
||||
pub update: Publisher<types::UserUpdate>,
|
||||
pub guild_settings_update: Publisher<types::UserGuildSettingsUpdate>,
|
||||
pub presence_update: Publisher<types::PresenceUpdate>,
|
||||
pub typing_start: Publisher<types::TypingStartEvent>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Relationship {
|
||||
pub add: GatewayEvent<types::RelationshipAdd>,
|
||||
pub remove: GatewayEvent<types::RelationshipRemove>,
|
||||
pub add: Publisher<types::RelationshipAdd>,
|
||||
pub remove: Publisher<types::RelationshipRemove>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Channel {
|
||||
pub create: GatewayEvent<types::ChannelCreate>,
|
||||
pub update: GatewayEvent<types::ChannelUpdate>,
|
||||
pub unread_update: GatewayEvent<types::ChannelUnreadUpdate>,
|
||||
pub delete: GatewayEvent<types::ChannelDelete>,
|
||||
pub pins_update: GatewayEvent<types::ChannelPinsUpdate>,
|
||||
pub create: Publisher<types::ChannelCreate>,
|
||||
pub update: Publisher<types::ChannelUpdate>,
|
||||
pub unread_update: Publisher<types::ChannelUnreadUpdate>,
|
||||
pub delete: Publisher<types::ChannelDelete>,
|
||||
pub pins_update: Publisher<types::ChannelPinsUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Thread {
|
||||
pub create: GatewayEvent<types::ThreadCreate>,
|
||||
pub update: GatewayEvent<types::ThreadUpdate>,
|
||||
pub delete: GatewayEvent<types::ThreadDelete>,
|
||||
pub list_sync: GatewayEvent<types::ThreadListSync>,
|
||||
pub member_update: GatewayEvent<types::ThreadMemberUpdate>,
|
||||
pub members_update: GatewayEvent<types::ThreadMembersUpdate>,
|
||||
pub create: Publisher<types::ThreadCreate>,
|
||||
pub update: Publisher<types::ThreadUpdate>,
|
||||
pub delete: Publisher<types::ThreadDelete>,
|
||||
pub list_sync: Publisher<types::ThreadListSync>,
|
||||
pub member_update: Publisher<types::ThreadMemberUpdate>,
|
||||
pub members_update: Publisher<types::ThreadMembersUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Guild {
|
||||
pub create: GatewayEvent<types::GuildCreate>,
|
||||
pub update: GatewayEvent<types::GuildUpdate>,
|
||||
pub delete: GatewayEvent<types::GuildDelete>,
|
||||
pub audit_log_entry_create: GatewayEvent<types::GuildAuditLogEntryCreate>,
|
||||
pub ban_add: GatewayEvent<types::GuildBanAdd>,
|
||||
pub ban_remove: GatewayEvent<types::GuildBanRemove>,
|
||||
pub emojis_update: GatewayEvent<types::GuildEmojisUpdate>,
|
||||
pub stickers_update: GatewayEvent<types::GuildStickersUpdate>,
|
||||
pub integrations_update: GatewayEvent<types::GuildIntegrationsUpdate>,
|
||||
pub member_add: GatewayEvent<types::GuildMemberAdd>,
|
||||
pub member_remove: GatewayEvent<types::GuildMemberRemove>,
|
||||
pub member_update: GatewayEvent<types::GuildMemberUpdate>,
|
||||
pub members_chunk: GatewayEvent<types::GuildMembersChunk>,
|
||||
pub role_create: GatewayEvent<types::GuildRoleCreate>,
|
||||
pub role_update: GatewayEvent<types::GuildRoleUpdate>,
|
||||
pub role_delete: GatewayEvent<types::GuildRoleDelete>,
|
||||
pub role_scheduled_event_create: GatewayEvent<types::GuildScheduledEventCreate>,
|
||||
pub role_scheduled_event_update: GatewayEvent<types::GuildScheduledEventUpdate>,
|
||||
pub role_scheduled_event_delete: GatewayEvent<types::GuildScheduledEventDelete>,
|
||||
pub role_scheduled_event_user_add: GatewayEvent<types::GuildScheduledEventUserAdd>,
|
||||
pub role_scheduled_event_user_remove: GatewayEvent<types::GuildScheduledEventUserRemove>,
|
||||
pub passive_update_v1: GatewayEvent<types::PassiveUpdateV1>,
|
||||
pub create: Publisher<types::GuildCreate>,
|
||||
pub update: Publisher<types::GuildUpdate>,
|
||||
pub delete: Publisher<types::GuildDelete>,
|
||||
pub audit_log_entry_create: Publisher<types::GuildAuditLogEntryCreate>,
|
||||
pub ban_add: Publisher<types::GuildBanAdd>,
|
||||
pub ban_remove: Publisher<types::GuildBanRemove>,
|
||||
pub emojis_update: Publisher<types::GuildEmojisUpdate>,
|
||||
pub stickers_update: Publisher<types::GuildStickersUpdate>,
|
||||
pub integrations_update: Publisher<types::GuildIntegrationsUpdate>,
|
||||
pub member_add: Publisher<types::GuildMemberAdd>,
|
||||
pub member_remove: Publisher<types::GuildMemberRemove>,
|
||||
pub member_update: Publisher<types::GuildMemberUpdate>,
|
||||
pub members_chunk: Publisher<types::GuildMembersChunk>,
|
||||
pub role_create: Publisher<types::GuildRoleCreate>,
|
||||
pub role_update: Publisher<types::GuildRoleUpdate>,
|
||||
pub role_delete: Publisher<types::GuildRoleDelete>,
|
||||
pub role_scheduled_event_create: Publisher<types::GuildScheduledEventCreate>,
|
||||
pub role_scheduled_event_update: Publisher<types::GuildScheduledEventUpdate>,
|
||||
pub role_scheduled_event_delete: Publisher<types::GuildScheduledEventDelete>,
|
||||
pub role_scheduled_event_user_add: Publisher<types::GuildScheduledEventUserAdd>,
|
||||
pub role_scheduled_event_user_remove: Publisher<types::GuildScheduledEventUserRemove>,
|
||||
pub passive_update_v1: Publisher<types::PassiveUpdateV1>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Invite {
|
||||
pub create: GatewayEvent<types::InviteCreate>,
|
||||
pub delete: GatewayEvent<types::InviteDelete>,
|
||||
pub create: Publisher<types::InviteCreate>,
|
||||
pub delete: Publisher<types::InviteDelete>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Integration {
|
||||
pub create: GatewayEvent<types::IntegrationCreate>,
|
||||
pub update: GatewayEvent<types::IntegrationUpdate>,
|
||||
pub delete: GatewayEvent<types::IntegrationDelete>,
|
||||
pub create: Publisher<types::IntegrationCreate>,
|
||||
pub update: Publisher<types::IntegrationUpdate>,
|
||||
pub delete: Publisher<types::IntegrationDelete>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Interaction {
|
||||
pub create: GatewayEvent<types::InteractionCreate>,
|
||||
pub create: Publisher<types::InteractionCreate>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Call {
|
||||
pub create: GatewayEvent<types::CallCreate>,
|
||||
pub update: GatewayEvent<types::CallUpdate>,
|
||||
pub delete: GatewayEvent<types::CallDelete>,
|
||||
pub create: Publisher<types::CallCreate>,
|
||||
pub update: Publisher<types::CallUpdate>,
|
||||
pub delete: Publisher<types::CallDelete>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Voice {
|
||||
pub state_update: GatewayEvent<types::VoiceStateUpdate>,
|
||||
pub server_update: GatewayEvent<types::VoiceServerUpdate>,
|
||||
pub state_update: Publisher<types::VoiceStateUpdate>,
|
||||
pub server_update: Publisher<types::VoiceServerUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Webhooks {
|
||||
pub update: GatewayEvent<types::WebhooksUpdate>,
|
||||
pub update: Publisher<types::WebhooksUpdate>,
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
use std::time::Duration;
|
||||
|
||||
use flate2::Decompress;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use pubserve::Publisher;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::task;
|
||||
|
||||
|
@ -19,6 +21,9 @@ use crate::types::{
|
|||
WebSocketEvent,
|
||||
};
|
||||
|
||||
/// Tells us we have received enough of the buffer to decompress it
|
||||
const ZLIB_SUFFIX: [u8; 4] = [0, 0, 255, 255];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Gateway {
|
||||
events: Arc<Mutex<Events>>,
|
||||
|
@ -28,14 +33,36 @@ pub struct Gateway {
|
|||
kill_send: tokio::sync::broadcast::Sender<()>,
|
||||
kill_receive: tokio::sync::broadcast::Receiver<()>,
|
||||
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
|
||||
/// Url which was used to initialize the gateway
|
||||
url: String,
|
||||
/// Options which were used to initialize the gateway
|
||||
options: GatewayOptions,
|
||||
zlib_inflate: Option<flate2::Decompress>,
|
||||
zlib_buffer: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Gateway {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub async fn spawn(websocket_url: String) -> Result<GatewayHandle, GatewayError> {
|
||||
let (websocket_send, mut websocket_receive) =
|
||||
WebSocketBackend::connect(&websocket_url).await?;
|
||||
/// Creates / opens a new gateway connection.
|
||||
///
|
||||
/// # Note
|
||||
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
|
||||
pub async fn spawn(
|
||||
websocket_url: &str,
|
||||
options: GatewayOptions,
|
||||
) -> Result<GatewayHandle, GatewayError> {
|
||||
let url = options.add_to_url(websocket_url);
|
||||
|
||||
debug!("GW: Connecting to {}", url);
|
||||
|
||||
let (websocket_send, mut websocket_receive) = match WebSocketBackend::connect(&url).await {
|
||||
Ok(streams) => streams,
|
||||
Err(e) => {
|
||||
return Err(GatewayError::CannotConnect {
|
||||
error: format!("{:?}", e),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let shared_websocket_send = Arc::new(Mutex::new(websocket_send));
|
||||
|
||||
|
@ -45,10 +72,34 @@ impl Gateway {
|
|||
// 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: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into();
|
||||
let received: RawGatewayMessage = websocket_receive.next().await.unwrap().unwrap().into();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let msg: GatewayMessage = websocket_receive.next().await.unwrap().into();
|
||||
let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap();
|
||||
let received: RawGatewayMessage = websocket_receive.next().await.unwrap().into();
|
||||
|
||||
let message: GatewayMessage;
|
||||
|
||||
let zlib_buffer;
|
||||
let zlib_inflate;
|
||||
|
||||
match options.transport_compression {
|
||||
GatewayTransportCompression::None => {
|
||||
zlib_buffer = None;
|
||||
zlib_inflate = None;
|
||||
message = GatewayMessage::from_raw_json_message(received).unwrap();
|
||||
}
|
||||
GatewayTransportCompression::ZLibStream => {
|
||||
zlib_buffer = Some(Vec::new());
|
||||
let mut inflate = Decompress::new(true);
|
||||
|
||||
message =
|
||||
GatewayMessage::from_zlib_stream_json_message(received, &mut inflate).unwrap();
|
||||
|
||||
zlib_inflate = Some(inflate);
|
||||
}
|
||||
}
|
||||
|
||||
let gateway_payload: types::GatewayReceivePayload =
|
||||
serde_json::from_str(&message.0).unwrap();
|
||||
|
||||
if gateway_payload.op_code != GATEWAY_HELLO {
|
||||
return Err(GatewayError::NonHelloOnInitiate {
|
||||
|
@ -78,7 +129,10 @@ impl Gateway {
|
|||
kill_send: kill_send.clone(),
|
||||
kill_receive: kill_send.subscribe(),
|
||||
store: store.clone(),
|
||||
url: websocket_url.clone(),
|
||||
url: url.clone(),
|
||||
options,
|
||||
zlib_inflate,
|
||||
zlib_buffer,
|
||||
};
|
||||
|
||||
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello
|
||||
|
@ -92,7 +146,7 @@ impl Gateway {
|
|||
});
|
||||
|
||||
Ok(GatewayHandle {
|
||||
url: websocket_url.clone(),
|
||||
url: url.clone(),
|
||||
events: shared_events,
|
||||
websocket_send: shared_websocket_send.clone(),
|
||||
kill_send: kill_send.clone(),
|
||||
|
@ -101,7 +155,7 @@ impl Gateway {
|
|||
}
|
||||
|
||||
/// The main gateway listener task;
|
||||
pub async fn gateway_listen_task(&mut self) {
|
||||
async fn gateway_listen_task(&mut self) {
|
||||
loop {
|
||||
let msg;
|
||||
|
||||
|
@ -118,12 +172,12 @@ impl Gateway {
|
|||
// PRETTYFYME: Remove inline conditional compiling
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(Ok(message)) = msg {
|
||||
self.handle_message(message.into()).await;
|
||||
self.handle_raw_message(message.into()).await;
|
||||
continue;
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(message) = msg {
|
||||
self.handle_message(message.into()).await;
|
||||
self.handle_raw_message(message.into()).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -144,7 +198,7 @@ impl Gateway {
|
|||
#[allow(dead_code)] // TODO: Remove this allow annotation
|
||||
async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>(
|
||||
data: &'a str,
|
||||
event: &mut GatewayEvent<T>,
|
||||
event: &mut Publisher<T>,
|
||||
) -> Result<(), serde_json::Error> {
|
||||
let data_deserialize_result: Result<T, serde_json::Error> = serde_json::from_str(data);
|
||||
|
||||
|
@ -152,12 +206,46 @@ impl Gateway {
|
|||
return Err(data_deserialize_result.err().unwrap());
|
||||
}
|
||||
|
||||
event.notify(data_deserialize_result.unwrap()).await;
|
||||
event.publish(data_deserialize_result.unwrap()).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes a [RawGatewayMessage], converts it to [GatewayMessage] based
|
||||
/// of connection options and calls handle_message
|
||||
async fn handle_raw_message(&mut self, raw_message: RawGatewayMessage) {
|
||||
let message;
|
||||
|
||||
match self.options.transport_compression {
|
||||
GatewayTransportCompression::None => {
|
||||
message = GatewayMessage::from_raw_json_message(raw_message).unwrap()
|
||||
}
|
||||
GatewayTransportCompression::ZLibStream => {
|
||||
let message_bytes = raw_message.into_bytes();
|
||||
|
||||
let can_decompress = message_bytes.len() > 4
|
||||
&& message_bytes[message_bytes.len() - 4..] == ZLIB_SUFFIX;
|
||||
|
||||
let zlib_buffer = self.zlib_buffer.as_mut().unwrap();
|
||||
zlib_buffer.extend(message_bytes.clone());
|
||||
|
||||
if !can_decompress {
|
||||
return;
|
||||
}
|
||||
|
||||
let zlib_buffer = self.zlib_buffer.as_ref().unwrap();
|
||||
let inflate = self.zlib_inflate.as_mut().unwrap();
|
||||
|
||||
message =
|
||||
GatewayMessage::from_zlib_stream_json_bytes(zlib_buffer, inflate).unwrap();
|
||||
self.zlib_buffer = Some(Vec::new());
|
||||
}
|
||||
};
|
||||
|
||||
self.handle_message(message).await;
|
||||
}
|
||||
|
||||
/// 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: GatewayMessage) {
|
||||
async fn handle_message(&mut self, msg: GatewayMessage) {
|
||||
if msg.0.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
@ -166,7 +254,7 @@ impl Gateway {
|
|||
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;
|
||||
self.events.lock().await.error.publish(error).await;
|
||||
} else {
|
||||
warn!(
|
||||
"Message unrecognised: {:?}, please open an issue on the chorus github",
|
||||
|
@ -194,7 +282,10 @@ impl Gateway {
|
|||
let event = &mut self.events.lock().await.$($path).+;
|
||||
let json = gateway_payload.event_data.unwrap().get();
|
||||
match serde_json::from_str(json) {
|
||||
Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"),
|
||||
Err(err) => {
|
||||
warn!("Failed to parse gateway event {event_name} ({err})");
|
||||
trace!("Event data: {json}");
|
||||
},
|
||||
Ok(message) => {
|
||||
$(
|
||||
let mut message: $message_type = message;
|
||||
|
@ -202,7 +293,7 @@ impl Gateway {
|
|||
let id = if message.id().is_some() {
|
||||
message.id().unwrap()
|
||||
} else {
|
||||
event.notify(message).await;
|
||||
event.publish(message).await;
|
||||
return;
|
||||
};
|
||||
if let Some(to_update) = store.get(&id) {
|
||||
|
@ -224,25 +315,22 @@ impl Gateway {
|
|||
}
|
||||
}
|
||||
)?
|
||||
event.notify(message).await;
|
||||
event.publish(message).await;
|
||||
}
|
||||
}
|
||||
},)*
|
||||
"RESUMED" => (),
|
||||
"SESSIONS_REPLACE" => {
|
||||
let result: Result<Vec<types::Session>, serde_json::Error> =
|
||||
serde_json::from_str(gateway_payload.event_data.unwrap().get());
|
||||
let json = gateway_payload.event_data.unwrap().get();
|
||||
let result: Result<Vec<types::Session>, serde_json::Error> = serde_json::from_str(json);
|
||||
match result {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to parse gateway event {} ({})",
|
||||
event_name,
|
||||
err
|
||||
);
|
||||
warn!("Failed to parse gateway event {event_name} ({err})");
|
||||
trace!("Event data: {json}");
|
||||
return;
|
||||
}
|
||||
Ok(sessions) => {
|
||||
self.events.lock().await.session.replace.notify(
|
||||
self.events.lock().await.session.replace.publish(
|
||||
types::SessionsReplace {sessions}
|
||||
).await;
|
||||
}
|
||||
|
@ -250,6 +338,7 @@ impl Gateway {
|
|||
},
|
||||
_ => {
|
||||
warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it");
|
||||
trace!("Event data: {}", gateway_payload.event_data.unwrap().get());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -358,7 +447,7 @@ impl Gateway {
|
|||
.await
|
||||
.session
|
||||
.reconnect
|
||||
.notify(reconnect)
|
||||
.publish(reconnect)
|
||||
.await;
|
||||
}
|
||||
GATEWAY_INVALID_SESSION => {
|
||||
|
@ -383,7 +472,7 @@ impl Gateway {
|
|||
.await
|
||||
.session
|
||||
.invalid
|
||||
.notify(invalid_session)
|
||||
.publish(invalid_session)
|
||||
.await;
|
||||
}
|
||||
// Starts our heartbeat
|
||||
|
|
|
@ -8,11 +8,12 @@ use log::*;
|
|||
use std::fmt::Debug;
|
||||
|
||||
use super::{events::Events, *};
|
||||
use crate::types::{self, Composite};
|
||||
use crate::types::{self, Composite, Shared};
|
||||
|
||||
/// Represents a handle to a Gateway connection. A Gateway connection will create observable
|
||||
/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently
|
||||
/// implemented types with the trait [`WebSocketEvent`]
|
||||
/// Represents a handle to a Gateway connection.
|
||||
///
|
||||
/// A Gateway connection will create observable [`Events`], which you can subscribe to.
|
||||
///
|
||||
/// Using this handle you can also send Gateway Events directly.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayHandle {
|
||||
|
@ -154,7 +155,7 @@ impl GatewayHandle {
|
|||
|
||||
/// Sends a call sync to the server
|
||||
pub async fn send_call_sync(&self, to_send: types::CallSync) {
|
||||
let to_send_value = serde_json::to_value(&to_send).unwrap();
|
||||
let to_send_value = serde_json::to_value(to_send).unwrap();
|
||||
|
||||
trace!("GW: Sending Call Sync..");
|
||||
|
||||
|
|
|
@ -2,11 +2,41 @@
|
|||
// 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::string::FromUtf8Error;
|
||||
|
||||
use crate::types;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
|
||||
/// Defines a raw gateway message, being either string json or bytes
|
||||
///
|
||||
/// This is used as an intermediary type between types from different websocket implementations
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum RawGatewayMessage {
|
||||
Text(String),
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
impl RawGatewayMessage {
|
||||
/// Attempt to consume the message into a String, will try to convert binary to utf8
|
||||
pub fn into_text(self) -> Result<String, FromUtf8Error> {
|
||||
match self {
|
||||
RawGatewayMessage::Text(text) => Ok(text),
|
||||
RawGatewayMessage::Bytes(bytes) => String::from_utf8(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the message into bytes, will convert text to binary
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
RawGatewayMessage::Text(text) => text.as_bytes().to_vec(),
|
||||
RawGatewayMessage::Bytes(bytes) => bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a json 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);
|
||||
|
@ -44,4 +74,48 @@ impl GatewayMessage {
|
|||
pub fn payload(&self) -> Result<types::GatewayReceivePayload, serde_json::Error> {
|
||||
serde_json::from_str(&self.0)
|
||||
}
|
||||
|
||||
/// Create self from an uncompressed json [RawGatewayMessage]
|
||||
pub(crate) fn from_raw_json_message(
|
||||
message: RawGatewayMessage,
|
||||
) -> Result<GatewayMessage, FromUtf8Error> {
|
||||
let text = message.into_text()?;
|
||||
Ok(GatewayMessage(text))
|
||||
}
|
||||
|
||||
/// Attempt to create self by decompressing zlib-stream bytes
|
||||
// Thanks to <https://github.com/ByteAlex/zlib-stream-rs>, their
|
||||
// code helped a lot with the stream implementation
|
||||
pub(crate) fn from_zlib_stream_json_bytes(
|
||||
bytes: &[u8],
|
||||
inflate: &mut flate2::Decompress,
|
||||
) -> Result<GatewayMessage, std::io::Error> {
|
||||
|
||||
// Note: is there a better way to handle the size of this output buffer?
|
||||
//
|
||||
// This used to be 10, I measured it at 11.5, so a safe bet feels like 20
|
||||
//
|
||||
// ^ - This dude is naive. apparently not even 20x is okay. Measured at 47.9x!!!!
|
||||
// If it is >100x ever, I will literally explode
|
||||
//
|
||||
// About an hour later, you ^ will literally explode.
|
||||
// 133 vs 13994 -- 105.21805x ratio
|
||||
// Let's hope it doesn't go above 200??
|
||||
let mut output = Vec::with_capacity(bytes.len() * 200);
|
||||
let _status = inflate.decompress_vec(bytes, &mut output, flate2::FlushDecompress::Sync)?;
|
||||
|
||||
output.shrink_to_fit();
|
||||
|
||||
let string = String::from_utf8(output).unwrap();
|
||||
|
||||
Ok(GatewayMessage(string))
|
||||
}
|
||||
|
||||
/// Attempt to create self by decompressing a zlib-stream bytes raw message
|
||||
pub(crate) fn from_zlib_stream_json_message(
|
||||
message: RawGatewayMessage,
|
||||
inflate: &mut flate2::Decompress,
|
||||
) -> Result<GatewayMessage, std::io::Error> {
|
||||
Self::from_zlib_stream_json_bytes(&message.into_bytes(), inflate)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// 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;
|
||||
pub mod events;
|
||||
|
@ -10,15 +9,17 @@ pub mod gateway;
|
|||
pub mod handle;
|
||||
pub mod heartbeat;
|
||||
pub mod message;
|
||||
pub mod options;
|
||||
|
||||
pub use backends::*;
|
||||
pub use gateway::*;
|
||||
pub use handle::*;
|
||||
use heartbeat::*;
|
||||
pub use message::*;
|
||||
pub use options::*;
|
||||
|
||||
use crate::errors::GatewayError;
|
||||
use crate::types::{Snowflake, WebSocketEvent};
|
||||
use crate::types::Snowflake;
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
|
@ -76,67 +77,11 @@ const GATEWAY_LAZY_REQUEST: u8 = 14;
|
|||
|
||||
pub type ObservableObject = dyn Send + Sync + Any;
|
||||
|
||||
/// Note: this is a reexport of [pubserve::Subscriber],
|
||||
/// exported not to break the public api and make development easier
|
||||
pub use pubserve::Subscriber as Observer;
|
||||
|
||||
/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake.
|
||||
pub trait Updateable: 'static + Send + Sync {
|
||||
fn id(&self) -> Snowflake;
|
||||
}
|
||||
|
||||
/// Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to
|
||||
/// an Observable. The Observer is notified when the Observable's data changes.
|
||||
/// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent.
|
||||
/// Note that `Debug` is used to tell `Observer`s apart when unsubscribing.
|
||||
#[async_trait]
|
||||
pub trait Observer<T>: Sync + Send + std::fmt::Debug {
|
||||
async fn update(&self, data: &T);
|
||||
}
|
||||
|
||||
/// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a
|
||||
/// change in the WebSocketEvent. GatewayEvents are observable.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct GatewayEvent<T: WebSocketEvent> {
|
||||
observers: Vec<Arc<dyn Observer<T>>>,
|
||||
}
|
||||
|
||||
impl<T: WebSocketEvent> GatewayEvent<T> {
|
||||
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()
|
||||
}
|
||||
|
||||
/// Subscribes an Observer to the GatewayEvent.
|
||||
pub fn subscribe(&mut self, observable: Arc<dyn Observer<T>>) {
|
||||
self.observers.push(observable);
|
||||
}
|
||||
|
||||
/// Unsubscribes an Observer from the GatewayEvent.
|
||||
pub fn unsubscribe(&mut self, observable: &dyn Observer<T>) {
|
||||
// .retain()'s closure retains only those elements of the vector, which have a different
|
||||
// pointer value than observable.
|
||||
// The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same
|
||||
// anddd there is no way to do that without using format
|
||||
let to_remove = format!("{:?}", observable);
|
||||
self.observers
|
||||
.retain(|obs| format!("{:?}", obs) != to_remove);
|
||||
}
|
||||
|
||||
/// Notifies the observers of the GatewayEvent.
|
||||
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<RwLock<T>>`], 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<T> = Arc<RwLock<T>>;
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
// 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/.
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug, Default, Copy)]
|
||||
/// Options passed when initializing the gateway connection.
|
||||
///
|
||||
/// E.g. compression
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Discord allows specifying the api version (v10, v9, ...) as well, but chorus is built upon one
|
||||
/// main version (v9).
|
||||
///
|
||||
/// Similarly, discord also supports etf encoding, while chorus does not (yet).
|
||||
/// We are looking into supporting it as an option, since it is faster and more lightweight.
|
||||
///
|
||||
/// See <https://docs.discord.sex/topics/gateway#connections>
|
||||
pub struct GatewayOptions {
|
||||
pub encoding: GatewayEncoding,
|
||||
pub transport_compression: GatewayTransportCompression,
|
||||
}
|
||||
|
||||
impl GatewayOptions {
|
||||
/// Adds the options to an existing gateway url
|
||||
///
|
||||
/// Returns the new url
|
||||
pub(crate) fn add_to_url(&self, url: &str) -> String {
|
||||
let mut url = url.to_string();
|
||||
|
||||
let mut parameters = Vec::with_capacity(2);
|
||||
|
||||
let encoding = self.encoding.to_url_parameter();
|
||||
parameters.push(encoding);
|
||||
|
||||
let compression = self.transport_compression.to_url_parameter();
|
||||
if let Some(some_compression) = compression {
|
||||
parameters.push(some_compression);
|
||||
}
|
||||
|
||||
let mut has_parameters = url.contains('?') && url.contains('=');
|
||||
|
||||
if !has_parameters {
|
||||
// Insure it ends in a /, so we don't get a 400 error
|
||||
if !url.ends_with('/') {
|
||||
url.push('/');
|
||||
}
|
||||
|
||||
// Lets hope that if it already has parameters the person knew to add '/'
|
||||
}
|
||||
|
||||
for parameter in parameters {
|
||||
if !has_parameters {
|
||||
url = format!("{}?{}", url, parameter);
|
||||
has_parameters = true;
|
||||
} else {
|
||||
url = format!("{}&{}", url, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
|
||||
/// Possible transport compression options for the gateway.
|
||||
///
|
||||
/// See <https://docs.discord.sex/topics/gateway#transport-compression>
|
||||
pub enum GatewayTransportCompression {
|
||||
/// Do not transport compress packets
|
||||
None,
|
||||
/// Transport compress using zlib stream
|
||||
#[default]
|
||||
ZLibStream,
|
||||
}
|
||||
|
||||
impl GatewayTransportCompression {
|
||||
/// Returns the option as a url parameter.
|
||||
///
|
||||
/// If set to [GatewayTransportCompression::None] returns [None].
|
||||
///
|
||||
/// If set to anything else, returns a string like "compress=zlib-stream"
|
||||
pub(crate) fn to_url_parameter(self) -> Option<String> {
|
||||
match self {
|
||||
Self::None => None,
|
||||
Self::ZLibStream => Some(String::from("compress=zlib-stream")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
|
||||
/// See <https://docs.discord.sex/topics/gateway#encoding-and-compression>
|
||||
pub enum GatewayEncoding {
|
||||
/// Javascript object notation, a standard for websocket connections,
|
||||
/// but contains a lot of overhead
|
||||
#[default]
|
||||
Json,
|
||||
/// A binary format originating from Erlang
|
||||
///
|
||||
/// Should be lighter and faster than json.
|
||||
///
|
||||
/// !! Chorus does not implement ETF yet !!
|
||||
ETF,
|
||||
}
|
||||
|
||||
impl GatewayEncoding {
|
||||
/// Returns the option as a url parameter.
|
||||
///
|
||||
/// Returns a string like "encoding=json"
|
||||
pub(crate) fn to_url_parameter(self) -> String {
|
||||
match self {
|
||||
Self::Json => String::from("encoding=json"),
|
||||
Self::ETF => String::from("encoding=etf"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,11 +13,11 @@ use reqwest::Client;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::ChorusResult;
|
||||
use crate::gateway::{Gateway, GatewayHandle, Shared};
|
||||
use crate::gateway::{Gateway, GatewayHandle, GatewayOptions};
|
||||
use crate::ratelimiter::ChorusRequest;
|
||||
use crate::types::types::subconfigs::limits::rates::RateLimits;
|
||||
use crate::types::{
|
||||
GeneralConfiguration, Limit, LimitType, LimitsConfiguration, User, UserSettings,
|
||||
GeneralConfiguration, Limit, LimitType, LimitsConfiguration, Shared, User, UserSettings,
|
||||
};
|
||||
use crate::UrlBundle;
|
||||
|
||||
|
@ -31,24 +31,8 @@ pub struct Instance {
|
|||
pub limits_information: Option<LimitsInformation>,
|
||||
#[serde(skip)]
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl PartialEq for Instance {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.urls == other.urls
|
||||
&& self.instance_info == other.instance_info
|
||||
&& self.limits_information == other.limits_information
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Instance {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.urls.hash(state);
|
||||
self.instance_info.hash(state);
|
||||
if let Some(inf) = &self.limits_information {
|
||||
inf.hash(state);
|
||||
}
|
||||
}
|
||||
#[serde(skip)]
|
||||
pub gateway_options: GatewayOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq)]
|
||||
|
@ -67,6 +51,7 @@ impl std::hash::Hash for LimitsInformation {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for LimitsInformation {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ratelimits.iter().eq(other.ratelimits.iter())
|
||||
|
@ -84,8 +69,13 @@ impl Instance {
|
|||
|
||||
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
|
||||
///
|
||||
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
|
||||
///
|
||||
/// To create an Instance from one singular url, use [`Instance::new()`].
|
||||
pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> {
|
||||
pub async fn from_url_bundle(
|
||||
urls: UrlBundle,
|
||||
options: Option<GatewayOptions>,
|
||||
) -> ChorusResult<Instance> {
|
||||
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
|
||||
let limit_information;
|
||||
|
||||
|
@ -104,6 +94,7 @@ impl Instance {
|
|||
instance_info: GeneralConfiguration::default(),
|
||||
limits_information: limit_information,
|
||||
client: Client::new(),
|
||||
gateway_options: options.unwrap_or_default(),
|
||||
};
|
||||
instance.instance_info = match instance.general_configuration_schema().await {
|
||||
Ok(schema) => schema,
|
||||
|
@ -117,14 +108,16 @@ impl Instance {
|
|||
|
||||
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
|
||||
///
|
||||
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
|
||||
///
|
||||
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
|
||||
pub async fn new(root_url: &str) -> ChorusResult<Instance> {
|
||||
pub async fn new(root_url: &str, options: Option<GatewayOptions>) -> ChorusResult<Instance> {
|
||||
let urls = UrlBundle::from_root_url(root_url).await?;
|
||||
Instance::from_url_bundle(urls).await
|
||||
Instance::from_url_bundle(urls, options).await
|
||||
}
|
||||
|
||||
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
|
||||
let api_url = UrlBundle::parse_url(api_url.to_string());
|
||||
let api_url = UrlBundle::parse_url(api_url);
|
||||
let client = Client::new();
|
||||
let request = client
|
||||
.get(format!("{}/policies/instance/limits", &api_url))
|
||||
|
@ -139,6 +132,13 @@ impl Instance {
|
|||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`GatewayOptions`] the instance will use when spawning new connections.
|
||||
///
|
||||
/// These options are used on the gateways created when logging in and registering.
|
||||
pub fn set_gateway_options(&mut self, options: GatewayOptions) {
|
||||
self.gateway_options = options;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
@ -165,21 +165,13 @@ pub struct ChorusUser {
|
|||
pub gateway: GatewayHandle,
|
||||
}
|
||||
|
||||
impl PartialEq for ChorusUser {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.token == other.token
|
||||
&& self.limits == other.limits
|
||||
&& self.gateway.url == other.gateway.url
|
||||
}
|
||||
}
|
||||
|
||||
impl ChorusUser {
|
||||
pub fn token(&self) -> String {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
pub fn set_token(&mut self, token: String) {
|
||||
self.token = token;
|
||||
pub fn set_token(&mut self, token: &str) {
|
||||
self.token = token.to_string();
|
||||
}
|
||||
|
||||
/// Creates a new [ChorusUser] from existing data.
|
||||
|
@ -210,14 +202,15 @@ 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: Shared<Instance>, token: String) -> ChorusUser {
|
||||
pub(crate) async fn shell(instance: Shared<Instance>, token: &str) -> 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();
|
||||
let wss_url = &instance.read().unwrap().urls.wss.clone();
|
||||
let gateway_options = instance.read().unwrap().gateway_options;
|
||||
// Dummy gateway object
|
||||
let gateway = Gateway::spawn(wss_url).await.unwrap();
|
||||
let gateway = Gateway::spawn(wss_url, gateway_options).await.unwrap();
|
||||
ChorusUser {
|
||||
token,
|
||||
token: token.to_string(),
|
||||
belongs_to: instance.clone(),
|
||||
limits: instance
|
||||
.read()
|
||||
|
|
118
src/lib.rs
118
src/lib.rs
|
@ -3,27 +3,29 @@
|
|||
// 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.
|
||||
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
|
||||
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
|
||||
flexibility one would expect from a library like this.
|
||||
|
||||
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
|
||||
|
||||
## A Tour of Chorus
|
||||
|
||||
Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
|
||||
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
|
||||
a WebSocket connection to the Gateway. This means that you can focus on building your application,
|
||||
instead of worrying about the underlying implementation details.
|
||||
|
||||
### Establishing a Connection
|
||||
|
||||
To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
||||
To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
||||
|
||||
```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.
|
||||
|
@ -36,7 +38,7 @@ This Instance can now be used to log in, register and from there on, interact wi
|
|||
|
||||
### Logging In
|
||||
|
||||
Logging in correctly provides you with an instance of [`ChorusUser`](https://docs.rs/chorus/latest/chorus/instance/struct.ChorusUser.html), with which you can interact with the server and
|
||||
Logging in correctly provides you with an instance of `ChorusUser`, with which you can interact with the server and
|
||||
manipulate the account. Assuming you already have an account on the server, you can log in like this:
|
||||
|
||||
```rs
|
||||
|
@ -48,7 +50,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 separate thread. Depending on
|
||||
// Each user connects to the Gateway. Each users' 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)
|
||||
|
@ -64,15 +66,33 @@ All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aar
|
|||
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use
|
||||
Chorus in your browser, or in any other environment that supports WebAssembly.
|
||||
|
||||
We recommend checking out the examples directory, as well as the documentation for more information.
|
||||
To compile for `wasm32-unknown-unknown`, execute the following command:
|
||||
|
||||
```sh
|
||||
cargo build --target=wasm32-unknown-unknown --no-default-features
|
||||
```
|
||||
|
||||
The following features are supported on `wasm32-unknown-unknown`:
|
||||
|
||||
| Feature | WASM Support |
|
||||
| ----------------- | ------------ |
|
||||
| `client` | ✅ |
|
||||
| `rt` | ✅ |
|
||||
| `rt-multi-thread` | ❌ |
|
||||
| `backend` | ❌ |
|
||||
| `voice` | ❌ |
|
||||
| `voice_udp` | ❌ |
|
||||
| `voice_gateway` | ✅ |
|
||||
|
||||
We recommend checking out the "examples" directory, as well as the documentation for more information.
|
||||
|
||||
## MSRV (Minimum Supported Rust Version)
|
||||
|
||||
Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0.
|
||||
Rust **1.70.0**. This number might change at any point while Chorus is not yet at version 1.0.0.
|
||||
|
||||
## Development Setup
|
||||
|
||||
Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version`
|
||||
Make sure that you have at least Rust 1.70.0 installed. You can check your Rust version by running `cargo --version`
|
||||
in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target.
|
||||
You can do this by running `rustup target add wasm32-unknown-unknown`.
|
||||
|
||||
|
@ -86,31 +106,36 @@ 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 --<chrome/firefox/safari> --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features`
|
||||
`wasm-pack test --<chrome/firefox/safari> --headless -- --target wasm32-unknown-unknown --features="rt, client, voice_gateway" --no-default-features`
|
||||
to run the tests for wasm.
|
||||
|
||||
## Versioning
|
||||
|
||||
This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
!*/
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png"
|
||||
)]
|
||||
#![allow(clippy::module_inception)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
clippy::extra_unused_lifetimes,
|
||||
clippy::from_over_into,
|
||||
clippy::needless_borrow,
|
||||
clippy::new_without_default,
|
||||
clippy::useless_conversion
|
||||
clippy::new_without_default
|
||||
)]
|
||||
#![warn(
|
||||
clippy::todo,
|
||||
clippy::unimplemented,
|
||||
clippy::dbg_macro,
|
||||
clippy::print_stdout,
|
||||
clippy::print_stderr
|
||||
clippy::print_stderr,
|
||||
missing_debug_implementations,
|
||||
missing_copy_implementations,
|
||||
clippy::useless_conversion
|
||||
)]
|
||||
#[cfg(all(feature = "rt", feature = "rt_multi_thread"))]
|
||||
compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time");
|
||||
|
@ -138,6 +163,27 @@ pub mod types;
|
|||
))]
|
||||
pub mod voice;
|
||||
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub type UInt128 = u128;
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub type UInt128 = sqlx_pg_uint::PgU128;
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub type UInt64 = u64;
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub type UInt64 = sqlx_pg_uint::PgU64;
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub type UInt32 = u32;
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub type UInt32 = sqlx_pg_uint::PgU32;
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub type UInt16 = u16;
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub type UInt16 = sqlx_pg_uint::PgU16;
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub type UInt8 = u8;
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub type UInt8 = sqlx_pg_uint::PgU8;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
/// A URLBundle bundles together the API-, Gateway- and CDN-URLs of a Spacebar instance.
|
||||
///
|
||||
|
@ -164,7 +210,7 @@ pub struct UrlBundle {
|
|||
|
||||
impl UrlBundle {
|
||||
/// Creates a new UrlBundle from the relevant urls.
|
||||
pub fn new(root: String, api: String, wss: String, cdn: String) -> Self {
|
||||
pub fn new(root: &str, api: &str, wss: &str, cdn: &str) -> Self {
|
||||
Self {
|
||||
root: UrlBundle::parse_url(root),
|
||||
api: UrlBundle::parse_url(api),
|
||||
|
@ -181,17 +227,17 @@ impl UrlBundle {
|
|||
/// let url = parse_url("localhost:3000");
|
||||
/// ```
|
||||
/// `-> Outputs "http://localhost:3000".`
|
||||
pub fn parse_url(url: String) -> String {
|
||||
let url = match Url::parse(&url) {
|
||||
pub fn parse_url(url: &str) -> String {
|
||||
let url = match Url::parse(url) {
|
||||
Ok(url) => {
|
||||
if url.scheme() == "localhost" {
|
||||
return UrlBundle::parse_url(format!("http://{}", url));
|
||||
return UrlBundle::parse_url(&format!("http://{}", url));
|
||||
}
|
||||
url
|
||||
}
|
||||
Err(ParseError::RelativeUrlWithoutBase) => {
|
||||
let url_fmt = format!("http://{}", url);
|
||||
return UrlBundle::parse_url(url_fmt);
|
||||
return UrlBundle::parse_url(&url_fmt);
|
||||
}
|
||||
Err(_) => panic!("Invalid URL"), // TODO: should not panic here
|
||||
};
|
||||
|
@ -214,7 +260,7 @@ impl UrlBundle {
|
|||
/// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that
|
||||
/// a wrong URL was provided.
|
||||
pub async fn from_root_url(url: &str) -> ChorusResult<UrlBundle> {
|
||||
let parsed = UrlBundle::parse_url(url.to_string());
|
||||
let parsed = UrlBundle::parse_url(url);
|
||||
let client = reqwest::Client::new();
|
||||
let request_wellknown = client
|
||||
.get(format!("{}/.well-known/spacebar", &parsed))
|
||||
|
@ -252,10 +298,10 @@ impl UrlBundle {
|
|||
.await
|
||||
{
|
||||
Ok(UrlBundle::new(
|
||||
url.to_string(),
|
||||
body.api_endpoint,
|
||||
body.gateway,
|
||||
body.cdn,
|
||||
url,
|
||||
&body.api_endpoint,
|
||||
&body.gateway,
|
||||
&body.cdn,
|
||||
))
|
||||
} else {
|
||||
Err(ChorusError::RequestFailed {
|
||||
|
@ -272,13 +318,13 @@ mod lib {
|
|||
|
||||
#[test]
|
||||
fn test_parse_url() {
|
||||
let mut result = UrlBundle::parse_url(String::from("localhost:3000/"));
|
||||
assert_eq!(result, String::from("http://localhost:3000"));
|
||||
result = UrlBundle::parse_url(String::from("https://some.url.com/"));
|
||||
assert_eq!(result, String::from("https://some.url.com"));
|
||||
result = UrlBundle::parse_url(String::from("https://some.url.com/"));
|
||||
assert_eq!(result, String::from("https://some.url.com"));
|
||||
result = UrlBundle::parse_url(String::from("https://some.url.com"));
|
||||
let mut result = UrlBundle::parse_url("localhost:3000/");
|
||||
assert_eq!(result, "http://localhost:3000");
|
||||
result = UrlBundle::parse_url("https://some.url.com/");
|
||||
assert_eq!(result, String::from("https://some.url.com"));
|
||||
result = UrlBundle::parse_url("https://some.url.com/");
|
||||
assert_eq!(result, "https://some.url.com");
|
||||
result = UrlBundle::parse_url("https://some.url.com");
|
||||
assert_eq!(result, "https://some.url.com");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use log::{self, debug};
|
||||
use reqwest::{Client, RequestBuilder, Response};
|
||||
use serde::Deserialize;
|
||||
use serde_json::from_str;
|
||||
|
@ -88,7 +87,7 @@ impl ChorusRequest {
|
|||
let client = user.belongs_to.read().unwrap().client.clone();
|
||||
let result = match client.execute(self.request.build().unwrap()).await {
|
||||
Ok(result) => {
|
||||
debug!("Request successful: {:?}", result);
|
||||
log::trace!("Request successful: {:?}", result);
|
||||
result
|
||||
}
|
||||
Err(error) => {
|
||||
|
@ -494,7 +493,7 @@ impl ChorusRequest {
|
|||
user: &mut ChorusUser,
|
||||
) -> ChorusResult<T> {
|
||||
let response = self.send_request(user).await?;
|
||||
debug!("Got response: {:?}", response);
|
||||
log::trace!("Got response: {:?}", response);
|
||||
let response_text = match response.text().await {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DefaultsConfiguration {
|
||||
pub guild: GuildDefaults,
|
||||
|
|
|
@ -3,19 +3,10 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
#[cfg(feature = "sqlx")]
|
||||
use std::io::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "sqlx")]
|
||||
use sqlx::{
|
||||
database::{HasArguments, HasValueRef},
|
||||
encode::IsNull,
|
||||
error::BoxDynError,
|
||||
Decode, MySql,
|
||||
};
|
||||
|
||||
use crate::types::config::types::subconfigs::guild::{
|
||||
autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration,
|
||||
|
@ -171,9 +162,11 @@ impl Display for GuildFeaturesList {
|
|||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
|
||||
fn decode(value: <MySql as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
|
||||
let v = <&str as Decode<sqlx::MySql>>::decode(value)?;
|
||||
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for GuildFeaturesList {
|
||||
fn decode(
|
||||
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let v = <String as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
|
||||
Ok(Self(
|
||||
v.split(',')
|
||||
.filter(|f| !f.is_empty())
|
||||
|
@ -184,10 +177,13 @@ impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
|
|||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
|
||||
fn encode_by_ref(&self, buf: &mut <MySql as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for GuildFeaturesList {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
||||
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
|
||||
if self.is_empty() {
|
||||
return IsNull::Yes;
|
||||
return Ok(sqlx::encode::IsNull::Yes);
|
||||
}
|
||||
let features = self
|
||||
.iter()
|
||||
|
@ -195,30 +191,18 @@ impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
|
|||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
let _ = buf.write(features.as_bytes());
|
||||
IsNull::No
|
||||
<String as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&features, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::MySql> for GuildFeaturesList {
|
||||
fn type_info() -> sqlx::mysql::MySqlTypeInfo {
|
||||
<&str as sqlx::Type<sqlx::MySql>>::type_info()
|
||||
impl sqlx::Type<sqlx::Postgres> for GuildFeaturesList {
|
||||
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
|
||||
<String as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool {
|
||||
<&str as sqlx::Type<sqlx::MySql>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::TypeInfo for GuildFeaturesList {
|
||||
fn is_null(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"TEXT"
|
||||
fn compatible(ty: &<sqlx::Postgres as sqlx::Database>::TypeInfo) -> bool {
|
||||
<String as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,6 +360,12 @@ impl FromStr for GuildFeatures {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vec<GuildFeatures>> for GuildFeaturesList {
|
||||
fn from(features: Vec<GuildFeatures>) -> GuildFeaturesList {
|
||||
Self(features)
|
||||
}
|
||||
}
|
||||
|
||||
impl GuildFeatures {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match *self {
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginConfiguration {
|
||||
pub require_captcha: bool,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd, Copy, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetricsConfiguration {
|
||||
pub timeout: u64,
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasswordResetConfiguration {
|
||||
pub require_captcha: bool,
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_number_from_string;
|
||||
|
||||
use crate::types::config::types::subconfigs::register::{
|
||||
use crate::types::{config::types::subconfigs::register::{
|
||||
DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration,
|
||||
};
|
||||
}, Rights};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -22,7 +23,8 @@ pub struct RegisterConfiguration {
|
|||
pub allow_multiple_accounts: bool,
|
||||
pub block_proxies: bool,
|
||||
pub incrementing_discriminators: bool,
|
||||
pub default_rights: String,
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
pub default_rights: Rights,
|
||||
}
|
||||
|
||||
impl Default for RegisterConfiguration {
|
||||
|
@ -39,7 +41,7 @@ impl Default for RegisterConfiguration {
|
|||
allow_multiple_accounts: true,
|
||||
block_proxies: true,
|
||||
incrementing_discriminators: false,
|
||||
default_rights: String::from("875069521787904"),
|
||||
default_rights: Rights::from_bits(648540060672).expect("failed to parse default_rights"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GuildDefaults {
|
||||
pub max_presences: u64,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserDefaults {
|
||||
pub premium: bool,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiscoverConfiguration {
|
||||
pub show_all_guilds: bool,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChannelLimits {
|
||||
pub max_pins: u16,
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
pub struct GlobalRateLimit {
|
||||
pub limit: u16,
|
||||
pub limit: u64,
|
||||
pub window: u64,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ impl Default for GlobalRateLimit {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GlobalRateLimits {
|
||||
pub register: GlobalRateLimit,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GuildLimits {
|
||||
pub max_roles: u16,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageLimits {
|
||||
pub max_characters: u32,
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
pub struct AuthRateLimit {
|
||||
pub login: RateLimitOptions,
|
||||
pub register: RateLimitOptions,
|
||||
|
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
pub mod auth;
|
||||
pub mod route;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RateLimitOptions {
|
||||
pub bot: Option<u64>,
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::types::config::types::subconfigs::limits::ratelimits::{
|
|||
auth::AuthRateLimit, RateLimitOptions,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, PartialOrd, Ord)]
|
||||
pub struct RouteRateLimit {
|
||||
pub guild: RateLimitOptions,
|
||||
pub webhook: RateLimitOptions,
|
||||
|
|
|
@ -50,14 +50,14 @@ impl Default for RateLimits {
|
|||
impl RateLimits {
|
||||
pub fn to_hash_map(&self) -> HashMap<LimitType, RateLimitOptions> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(LimitType::AuthLogin, self.routes.auth.login.clone());
|
||||
map.insert(LimitType::AuthRegister, self.routes.auth.register.clone());
|
||||
map.insert(LimitType::ChannelBaseline, self.routes.channel.clone());
|
||||
map.insert(LimitType::Error, self.error.clone());
|
||||
map.insert(LimitType::Global, self.global.clone());
|
||||
map.insert(LimitType::Ip, self.ip.clone());
|
||||
map.insert(LimitType::WebhookBaseline, self.routes.webhook.clone());
|
||||
map.insert(LimitType::GuildBaseline, self.routes.guild.clone());
|
||||
map.insert(LimitType::AuthLogin, self.routes.auth.login);
|
||||
map.insert(LimitType::AuthRegister, self.routes.auth.register);
|
||||
map.insert(LimitType::ChannelBaseline, self.routes.channel);
|
||||
map.insert(LimitType::Error, self.error);
|
||||
map.insert(LimitType::Global, self.global);
|
||||
map.insert(LimitType::Ip, self.ip);
|
||||
map.insert(LimitType::WebhookBaseline, self.routes.webhook);
|
||||
map.insert(LimitType::GuildBaseline, self.routes.guild);
|
||||
map
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Copy, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserLimits {
|
||||
pub max_guilds: u64,
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd, Copy)]
|
||||
pub struct LatLong {
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)]
|
||||
pub struct Region {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
pub struct DateOfBirthConfiguration {
|
||||
pub required: bool,
|
||||
pub minimum: u8,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasswordConfiguration {
|
||||
pub required: bool,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TwoFactorConfiguration {
|
||||
pub generate_backup_codes: bool,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TemplateConfiguration {
|
||||
pub enabled: bool,
|
||||
|
|
|
@ -7,10 +7,13 @@ 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::Shared;
|
||||
use crate::types::{Team, User};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use super::{arc_rwlock_ptr_eq, option_arc_rwlock_ptr_eq};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
/// # Reference
|
||||
|
@ -31,7 +34,7 @@ pub struct Application {
|
|||
pub verify_key: String,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub owner: Shared<User>,
|
||||
pub flags: u64,
|
||||
pub flags: ApplicationFlags,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
|
@ -59,6 +62,64 @@ pub struct Application {
|
|||
pub team: Option<Team>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Application {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.name == other.name
|
||||
&& self.icon == other.icon
|
||||
&& self.description == other.description
|
||||
&& self.summary == other.summary
|
||||
&& self.r#type == other.r#type
|
||||
&& self.hook == other.hook
|
||||
&& self.bot_public == other.bot_public
|
||||
&& self.bot_require_code_grant == other.bot_require_code_grant
|
||||
&& self.verify_key == other.verify_key
|
||||
&& arc_rwlock_ptr_eq(&self.owner, &other.owner)
|
||||
&& self.flags == other.flags
|
||||
&& self.redirect_uris == other.redirect_uris
|
||||
&& self.rpc_application_state == other.rpc_application_state
|
||||
&& self.store_application_state == other.store_application_state
|
||||
&& self.verification_state == other.verification_state
|
||||
&& self.interactions_endpoint_url == other.interactions_endpoint_url
|
||||
&& self.integration_public == other.integration_public
|
||||
&& self.integration_require_code_grant == other.integration_require_code_grant
|
||||
&& self.discoverability_state == other.discoverability_state
|
||||
&& self.discovery_eligibility_flags == other.discovery_eligibility_flags
|
||||
&& self.tags == other.tags
|
||||
&& self.cover_image == other.cover_image
|
||||
&& compare_install_params(&self.install_params, &other.install_params)
|
||||
&& self.terms_of_service_url == other.terms_of_service_url
|
||||
&& self.privacy_policy_url == other.privacy_policy_url
|
||||
&& self.team == other.team
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(feature = "sqlx")]
|
||||
fn compare_install_params(
|
||||
a: &Option<sqlx::types::Json<InstallParams>>,
|
||||
b: &Option<sqlx::types::Json<InstallParams>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
fn compare_install_params(
|
||||
a: &Option<Shared<InstallParams>>,
|
||||
b: &Option<Shared<InstallParams>>,
|
||||
) -> bool {
|
||||
option_arc_rwlock_ptr_eq(a, b)
|
||||
}
|
||||
|
||||
impl Default for Application {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -73,7 +134,7 @@ impl Default for Application {
|
|||
bot_require_code_grant: false,
|
||||
verify_key: "".to_string(),
|
||||
owner: Default::default(),
|
||||
flags: 0,
|
||||
flags: ApplicationFlags::empty(),
|
||||
redirect_uris: None,
|
||||
rpc_application_state: 0,
|
||||
store_application_state: 1,
|
||||
|
@ -93,12 +154,6 @@ impl Default for Application {
|
|||
}
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub fn flags(&self) -> ApplicationFlags {
|
||||
ApplicationFlags::from_bits(self.flags.to_owned()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/resources/application#install-params-object>
|
||||
|
@ -108,7 +163,8 @@ pub struct InstallParams {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/resources/application#application-object-application-flags>
|
||||
pub struct ApplicationFlags: u64 {
|
||||
|
@ -168,7 +224,8 @@ pub struct ApplicationCommandOptionChoice {
|
|||
|
||||
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(i32)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types>
|
||||
pub enum ApplicationCommandOptionType {
|
||||
|
@ -212,7 +269,9 @@ pub struct GuildApplicationCommandPermissions {
|
|||
pub permissions: Vec<Shared<ApplicationCommandPermission>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, PartialEq, Serialize, Deserialize, Copy, Eq, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure>
|
||||
pub struct ApplicationCommandPermission {
|
||||
pub id: Snowflake,
|
||||
|
@ -222,9 +281,22 @@ pub struct ApplicationCommandPermission {
|
|||
pub permission: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type>
|
||||
pub enum ApplicationCommandPermissionType {
|
||||
#[default]
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::utils::Snowflake;
|
||||
use crate::UInt64;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -16,11 +17,11 @@ pub struct Attachment {
|
|||
/// Max 1024 characters
|
||||
pub description: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub size: u64,
|
||||
pub size: UInt64,
|
||||
pub url: String,
|
||||
pub proxy_url: String,
|
||||
pub height: Option<u64>,
|
||||
pub width: Option<u64>,
|
||||
pub height: Option<UInt64>,
|
||||
pub width: Option<UInt64>,
|
||||
pub ephemeral: Option<bool>,
|
||||
/// The duration of the audio file (only for voice messages)
|
||||
pub duration_secs: Option<f32>,
|
||||
|
@ -37,12 +38,12 @@ pub struct Attachment {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct PartialDiscordFileAttachment {
|
||||
pub id: Option<i16>,
|
||||
pub id: Option<UInt64>,
|
||||
pub filename: String,
|
||||
/// Max 1024 characters
|
||||
pub description: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub size: Option<i64>,
|
||||
pub size: Option<UInt64>,
|
||||
pub url: Option<String>,
|
||||
pub proxy_url: Option<String>,
|
||||
pub height: Option<i32>,
|
||||
|
|
|
@ -2,25 +2,92 @@
|
|||
// 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};
|
||||
#[allow(unused_imports)]
|
||||
use super::option_vec_arc_rwlock_ptr_eq;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::utils::Snowflake;
|
||||
use crate::types::{
|
||||
AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared,
|
||||
};
|
||||
use crate::UInt64;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
/// See <https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object>
|
||||
pub struct AuditLogEntry {
|
||||
pub target_id: Option<String>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub changes: sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub changes: Option<Vec<Shared<AuditLogChange>>>,
|
||||
pub user_id: Option<Snowflake>,
|
||||
pub id: Snowflake,
|
||||
// to:do implement an enum for these types
|
||||
pub action_type: u8,
|
||||
// to:do add better options type
|
||||
pub options: Option<serde_json::Value>,
|
||||
pub action_type: AuditLogActionType,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub options: Option<sqlx::types::Json<AuditEntryInfo>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub options: Option<AuditEntryInfo>,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl PartialEq for AuditLogEntry {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.target_id == other.target_id
|
||||
&& self.user_id == other.user_id
|
||||
&& self.id == other.id
|
||||
&& self.action_type == other.action_type
|
||||
&& compare_options(&self.options, &other.options)
|
||||
&& self.reason == other.reason
|
||||
&& compare_changes(&self.changes, &other.changes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(feature = "sqlx")]
|
||||
fn compare_options(
|
||||
a: &Option<sqlx::types::Json<AuditEntryInfo>>,
|
||||
b: &Option<sqlx::types::Json<AuditEntryInfo>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
fn compare_options(a: &Option<AuditEntryInfo>, b: &Option<AuditEntryInfo>) -> bool {
|
||||
a == b
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(feature = "sqlx")]
|
||||
fn compare_changes(
|
||||
a: &sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
|
||||
b: &sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
|
||||
) -> bool {
|
||||
match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
fn compare_changes(
|
||||
a: &Option<Vec<Shared<AuditLogChange>>>,
|
||||
b: &Option<Vec<Shared<AuditLogChange>>>,
|
||||
) -> bool {
|
||||
option_vec_arc_rwlock_ptr_eq(a, b)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
/// See <https://discord.com/developers/docs/resources/audit-log#audit-log-change-object>
|
||||
pub struct AuditLogChange {
|
||||
|
@ -28,3 +95,176 @@ pub struct AuditLogChange {
|
|||
pub old_value: Option<serde_json::Value>,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/audit-log#audit-log-events>
|
||||
pub enum AuditLogActionType {
|
||||
#[default]
|
||||
/// Guild settings were updated
|
||||
GuildUpdate = 1,
|
||||
/// Channel was created
|
||||
ChannelCreate = 10,
|
||||
/// Channel settings were updated
|
||||
ChannelUpdate = 11,
|
||||
/// Channel was deleted
|
||||
ChannelDelete = 12,
|
||||
/// Permission overwrite was added to a channel
|
||||
ChannelOverwriteCreate = 13,
|
||||
/// Permission overwrite was updated for a channel
|
||||
ChannelOverwriteUpdate = 14,
|
||||
/// Permission overwrite was deleted from a channel
|
||||
ChannelOverwriteDelete = 15,
|
||||
/// Member was removed from guild
|
||||
MemberKick = 20,
|
||||
/// Members were pruned from guild
|
||||
MemberPrune = 21,
|
||||
/// Member was banned from guild
|
||||
MemberBanAdd = 22,
|
||||
/// Member was unbanned from guild
|
||||
MemberBanRemove = 23,
|
||||
/// Member was updated in guild
|
||||
MemberUpdate = 24,
|
||||
/// Member was added or removed from a role
|
||||
MemberRoleUpdate = 25,
|
||||
/// Member was moved to a different voice channel
|
||||
MemberMove = 26,
|
||||
/// Member was disconnected from a voice channel
|
||||
MemberDisconnect = 27,
|
||||
/// Bot user was added to guild
|
||||
BotAdd = 28,
|
||||
/// Role was created
|
||||
RoleCreate = 30,
|
||||
/// Role was edited
|
||||
RoleUpdate = 31,
|
||||
/// Role was deleted
|
||||
RoleDelete = 32,
|
||||
/// Guild invite was created
|
||||
InviteCreate = 40,
|
||||
/// Guild invite was updated
|
||||
InviteUpdate = 41,
|
||||
/// Guild invite was deleted
|
||||
InviteDelete = 42,
|
||||
/// Webhook was created
|
||||
WebhookCreate = 50,
|
||||
/// Webhook properties or channel were updated
|
||||
WebhookUpdate = 51,
|
||||
/// Webhook was deleted
|
||||
WebhookDelete = 52,
|
||||
/// Emoji was created
|
||||
EmojiCreate = 60,
|
||||
/// Emoji name was updated
|
||||
EmojiUpdate = 61,
|
||||
/// Emoji was deleted
|
||||
EmojiDelete = 62,
|
||||
/// Single message was deleted
|
||||
MessageDelete = 72,
|
||||
/// Multiple messages were deleted
|
||||
MessageBulkDelete = 73,
|
||||
/// Message was pinned to a channel
|
||||
MessagePin = 74,
|
||||
/// Message was unpinned from a channel
|
||||
MessageUnpin = 75,
|
||||
/// Interaction was added to guild
|
||||
IntegrationCreate = 80,
|
||||
/// Integration was updated (e.g. its scopes were updated)
|
||||
IntegrationUpdate = 81,
|
||||
/// Integration was removed from guild
|
||||
IntegrationDelete = 82,
|
||||
/// Stage instance was created (stage channel becomes live)
|
||||
StageInstanceCreate = 83,
|
||||
/// Stage instance details were updated
|
||||
StageInstanceUpdate = 84,
|
||||
/// Stage instance was deleted (stage channel no longer live)
|
||||
StageInstanceDelete = 85,
|
||||
/// Sticker was created
|
||||
StickerCreate = 90,
|
||||
/// Sticker details were updated
|
||||
StickerUpdate = 91,
|
||||
/// Sticker was deleted
|
||||
StickerDelete = 92,
|
||||
/// Event was created
|
||||
GuildScheduledEventCreate = 100,
|
||||
/// Event was updated
|
||||
GuildScheduledEventUpdate = 101,
|
||||
/// Event was cancelled
|
||||
GuildScheduledEventDelete = 102,
|
||||
/// Thread was created in a channel
|
||||
ThreadCreate = 110,
|
||||
/// Thread was updated
|
||||
ThreadUpdate = 111,
|
||||
/// Thread was deleted
|
||||
ThreadDelete = 112,
|
||||
/// Permissions were updated for a command
|
||||
ApplicationCommandPermissionUpdate = 121,
|
||||
/// AutoMod rule created
|
||||
AutoModerationRuleCreate = 140,
|
||||
/// AutoMod rule was updated
|
||||
AutoModerationRuleUpdate = 141,
|
||||
/// AutoMod rule was deleted
|
||||
AutoModerationRuleDelete = 142,
|
||||
/// Message was blocked by AutoMod
|
||||
AutoModerationBlockMessage = 143,
|
||||
/// Message was flagged by AutoMod
|
||||
AutoModerationFlagToChannel = 144,
|
||||
/// Member was timed out by AutoMod
|
||||
AutoModerationUserCommunicationDisabled = 145,
|
||||
/// Member was quarantined by AutoMod
|
||||
AutoModerationQuarantineUser = 146,
|
||||
/// Creator monetization request was created
|
||||
CreatorMonetizationRequestCreated = 150,
|
||||
/// Creator monetization terms were accepted
|
||||
CreatorMonetizationTermsAccepted = 151,
|
||||
/// Onboarding prompt was created
|
||||
OnboardingPromptCreate = 163,
|
||||
/// Onboarding prompt was updated
|
||||
OnboardingPromptUpdate = 164,
|
||||
/// Onboarding prompt was deleted
|
||||
OnboardingPromptDelete = 165,
|
||||
/// Onboarding was created
|
||||
OnboardingCreate = 166,
|
||||
/// Onboarding was updated
|
||||
OnboardingUpdate = 167,
|
||||
/// Voice channel status was updated
|
||||
VoiceChannelStatusUpdate = 192,
|
||||
/// Voice channel status was deleted
|
||||
VoiceChannelStatusDelete = 193,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
pub struct AuditEntryInfo {
|
||||
pub application_id: Option<Snowflake>,
|
||||
pub auto_moderation_rule_name: Option<String>,
|
||||
pub auto_moderation_rule_trigger_type: Option<AutoModerationRuleTriggerType>,
|
||||
pub channel_id: Option<Snowflake>,
|
||||
// #[serde(option_string)]
|
||||
pub count: Option<UInt64>,
|
||||
// #[serde(option_string)]
|
||||
pub delete_member_days: Option<UInt64>,
|
||||
/// The ID of the overwritten entity
|
||||
pub id: Option<Snowflake>,
|
||||
pub integration_type: Option<IntegrationType>,
|
||||
// #[serde(option_string)]
|
||||
pub members_removed: Option<UInt64>,
|
||||
// #[serde(option_string)]
|
||||
pub message_id: Option<UInt64>,
|
||||
pub role_name: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub overwrite_type: Option<PermissionOverwriteType>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
// 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;
|
||||
use crate::types::Shared;
|
||||
use crate::UInt8;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use chorus_macros::Updateable;
|
||||
|
@ -31,8 +32,9 @@ pub struct AutoModerationRule {
|
|||
pub exempt_channels: Vec<Snowflake>,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types>
|
||||
pub enum AutoModerationRuleEventType {
|
||||
|
@ -40,8 +42,11 @@ pub enum AutoModerationRuleEventType {
|
|||
MessageSend = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types>
|
||||
pub enum AutoModerationRuleTriggerType {
|
||||
|
@ -52,7 +57,7 @@ pub enum AutoModerationRuleTriggerType {
|
|||
MentionSpam = 5,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(untagged)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
|
||||
pub enum AutoModerationRuleTriggerMetadata {
|
||||
|
@ -63,7 +68,7 @@ pub enum AutoModerationRuleTriggerMetadata {
|
|||
None,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
|
||||
pub struct AutoModerationRuleTriggerMetadataForKeyword {
|
||||
pub keyword_filter: Vec<String>,
|
||||
|
@ -71,23 +76,27 @@ pub struct AutoModerationRuleTriggerMetadataForKeyword {
|
|||
pub allow_list: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
|
||||
pub struct AutoModerationRuleTriggerMetadataForKeywordPreset {
|
||||
pub presets: Vec<AutoModerationRuleKeywordPresetType>,
|
||||
pub allow_list: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
|
||||
pub struct AutoModerationRuleTriggerMetadataForMentionSpam {
|
||||
/// Max 50
|
||||
pub mention_total_limit: u8,
|
||||
pub mention_total_limit: UInt8,
|
||||
pub mention_raid_protection_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types>
|
||||
pub enum AutoModerationRuleKeywordPresetType {
|
||||
|
@ -105,8 +114,21 @@ pub struct AutoModerationAction {
|
|||
pub metadata: Option<Shared<AutoModerationActionMetadata>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Clone,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Copy,
|
||||
Hash,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types>
|
||||
pub enum AutoModerationActionType {
|
||||
|
@ -116,7 +138,7 @@ pub enum AutoModerationActionType {
|
|||
Timeout = 3,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(untagged)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
|
||||
pub enum AutoModerationActionMetadata {
|
||||
|
@ -127,19 +149,19 @@ pub enum AutoModerationActionMetadata {
|
|||
None,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
|
||||
pub struct AutoModerationActionMetadataForBlockMessage {
|
||||
pub custom_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
|
||||
pub struct AutoModerationActionMetadataForSendAlertMessage {
|
||||
pub channel_id: Snowflake,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
|
||||
pub struct AutoModerationActionMetadataForTimeout {
|
||||
/// Max 2419200
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_string_from_number;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::{
|
||||
entities::{GuildMember, User},
|
||||
utils::Snowflake,
|
||||
PermissionFlags, Shared,
|
||||
};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
|
@ -22,9 +22,16 @@ use crate::gateway::GatewayHandle;
|
|||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Updateable;
|
||||
use crate::UInt64;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{observe_option_vec, Composite, Updateable};
|
||||
use serde::de::{Error, Visitor};
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
use sqlx::types::Json;
|
||||
|
||||
use super::{option_arc_rwlock_ptr_eq, option_vec_arc_rwlock_ptr_eq};
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -35,13 +42,7 @@ use chorus_macros::{observe_option_vec, Composite, Updateable};
|
|||
/// See <https://discord-userdoccers.vercel.app/resources/channel#channels-resource>
|
||||
pub struct Channel {
|
||||
pub application_id: Option<Snowflake>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub applied_tags: Option<sqlx::types::Json<Vec<String>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub applied_tags: Option<Vec<String>>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub available_tags: Option<sqlx::types::Json<Vec<Tag>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub available_tags: Option<Vec<Tag>>,
|
||||
pub bitrate: Option<i32>,
|
||||
#[serde(rename = "type")]
|
||||
|
@ -49,9 +50,7 @@ pub struct Channel {
|
|||
pub created_at: Option<chrono::DateTime<Utc>>,
|
||||
pub default_auto_archive_duration: Option<i32>,
|
||||
pub default_forum_layout: Option<i32>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub default_reaction_emoji: Option<sqlx::types::Json<DefaultReaction>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
// DefaultReaction could be stored in a separate table. However, there are a lot of default emojis. How would we handle that?
|
||||
pub default_reaction_emoji: Option<DefaultReaction>,
|
||||
pub default_sort_order: Option<i32>,
|
||||
pub default_thread_rate_limit_per_user: Option<i32>,
|
||||
|
@ -64,7 +63,9 @@ pub struct Channel {
|
|||
pub managed: Option<bool>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub member: Option<ThreadMember>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub member_count: Option<i32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub message_count: Option<i32>,
|
||||
pub name: Option<String>,
|
||||
pub nsfw: Option<bool>,
|
||||
|
@ -75,6 +76,7 @@ pub struct Channel {
|
|||
#[cfg(not(feature = "sqlx"))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub permission_overwrites: Option<Vec<Shared<PermissionOverwrite>>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub permissions: Option<String>,
|
||||
pub position: Option<i32>,
|
||||
pub rate_limit_per_user: Option<i32>,
|
||||
|
@ -85,19 +87,28 @@ pub struct Channel {
|
|||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub thread_metadata: Option<ThreadMetadata>,
|
||||
pub topic: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub total_message_sent: Option<i32>,
|
||||
pub user_limit: Option<i32>,
|
||||
pub video_quality_mode: Option<i32>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
impl PartialEq for Channel {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.application_id == other.application_id
|
||||
&& self.applied_tags == other.applied_tags
|
||||
&& self.applied_tags == other.applied_tags
|
||||
&& self.available_tags == other.available_tags
|
||||
&& self.available_tags == other.available_tags
|
||||
&& self.bitrate == other.bitrate
|
||||
&& self.channel_type == other.channel_type
|
||||
&& self.created_at == other.created_at
|
||||
&& self.default_auto_archive_duration == other.default_auto_archive_duration
|
||||
&& self.default_forum_layout == other.default_forum_layout
|
||||
&& self.default_reaction_emoji == other.default_reaction_emoji
|
||||
&& self.default_reaction_emoji == other.default_reaction_emoji
|
||||
&& self.default_sort_order == other.default_sort_order
|
||||
&& self.default_thread_rate_limit_per_user == other.default_thread_rate_limit_per_user
|
||||
&& self.flags == other.flags
|
||||
|
@ -107,16 +118,23 @@ impl PartialEq for Channel {
|
|||
&& self.last_message_id == other.last_message_id
|
||||
&& self.last_pin_timestamp == other.last_pin_timestamp
|
||||
&& self.managed == other.managed
|
||||
&& self.member == other.member
|
||||
&& self.member_count == other.member_count
|
||||
&& self.message_count == other.message_count
|
||||
&& self.name == other.name
|
||||
&& self.nsfw == other.nsfw
|
||||
&& self.owner_id == other.owner_id
|
||||
&& self.parent_id == other.parent_id
|
||||
&& compare_permission_overwrites(
|
||||
&self.permission_overwrites,
|
||||
&other.permission_overwrites,
|
||||
)
|
||||
&& self.permissions == other.permissions
|
||||
&& self.position == other.position
|
||||
&& self.rate_limit_per_user == other.rate_limit_per_user
|
||||
&& option_vec_arc_rwlock_ptr_eq(&self.recipients, &other.recipients)
|
||||
&& self.rtc_region == other.rtc_region
|
||||
&& self.thread_metadata == other.thread_metadata
|
||||
&& self.topic == other.topic
|
||||
&& self.total_message_sent == other.total_message_sent
|
||||
&& self.user_limit == other.user_limit
|
||||
|
@ -124,11 +142,38 @@ impl PartialEq for Channel {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(feature = "sqlx")]
|
||||
fn compare_permission_overwrites(
|
||||
a: &Option<Json<Vec<PermissionOverwrite>>>,
|
||||
b: &Option<Json<Vec<PermissionOverwrite>>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
fn compare_permission_overwrites(
|
||||
a: &Option<Vec<Shared<PermissionOverwrite>>>,
|
||||
b: &Option<Vec<Shared<PermissionOverwrite>>>,
|
||||
) -> bool {
|
||||
option_vec_arc_rwlock_ptr_eq(a, b)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
/// A tag that can be applied to a thread in a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/channel#forum-tag-object>
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
|
||||
pub struct Tag {
|
||||
pub id: Snowflake,
|
||||
/// The name of the tag (max 20 characters)
|
||||
|
@ -144,26 +189,105 @@ pub struct Tag {
|
|||
pub struct PermissionOverwrite {
|
||||
pub id: Snowflake,
|
||||
#[serde(rename = "type")]
|
||||
#[serde(deserialize_with = "deserialize_string_from_number")]
|
||||
pub overwrite_type: String,
|
||||
pub overwrite_type: PermissionOverwriteType,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_string_from_number")]
|
||||
pub allow: String,
|
||||
pub allow: PermissionFlags,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_string_from_number")]
|
||||
pub deny: String,
|
||||
pub deny: PermissionFlags,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// # Reference
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/channel#permission-overwrite-type>
|
||||
pub enum PermissionOverwriteType {
|
||||
Role = 0,
|
||||
Member = 1,
|
||||
}
|
||||
|
||||
impl From<u8> for PermissionOverwriteType {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
0 => PermissionOverwriteType::Role,
|
||||
1 => PermissionOverwriteType::Member,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PermissionOverwriteType {
|
||||
type Err = serde::de::value::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"role" => Ok(PermissionOverwriteType::Role),
|
||||
"member" => Ok(PermissionOverwriteType::Member),
|
||||
_ => Err(Self::Err::custom("invalid permission overwrite type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PermissionOverwriteTypeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PermissionOverwriteTypeVisitor {
|
||||
type Value = PermissionOverwriteType;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a valid permission overwrite type")
|
||||
}
|
||||
|
||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(PermissionOverwriteType::from(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
self.visit_u8(v as u8)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
PermissionOverwriteType::from_str(v).map_err(E::custom)
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
self.visit_str(v.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PermissionOverwriteType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let val = deserializer.deserialize_any(PermissionOverwriteTypeVisitor)?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/channel#thread-metadata-object>
|
||||
pub struct ThreadMetadata {
|
||||
pub archived: bool,
|
||||
pub auto_archive_duration: i32,
|
||||
pub archive_timestamp: String,
|
||||
pub archive_timestamp: DateTime<Utc>,
|
||||
pub locked: bool,
|
||||
pub invitable: Option<bool>,
|
||||
pub create_timestamp: Option<String>,
|
||||
pub create_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
|
||||
|
@ -172,16 +296,29 @@ pub struct ThreadMetadata {
|
|||
pub struct ThreadMember {
|
||||
pub id: Option<Snowflake>,
|
||||
pub user_id: Option<Snowflake>,
|
||||
pub join_timestamp: Option<String>,
|
||||
pub flags: Option<u64>,
|
||||
pub join_timestamp: Option<DateTime<Utc>>,
|
||||
pub flags: Option<UInt64>,
|
||||
pub member: Option<Shared<GuildMember>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for ThreadMember {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.user_id == other.user_id
|
||||
&& self.join_timestamp == other.join_timestamp
|
||||
&& self.flags == other.flags
|
||||
&& option_arc_rwlock_ptr_eq(&self.member, &other.member)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd)]
|
||||
/// Specifies the emoji to use as the default way to react to a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel post.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/channel#default-reaction-object>
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
|
||||
pub struct DefaultReaction {
|
||||
#[serde(default)]
|
||||
pub emoji_id: Option<Snowflake>,
|
||||
|
@ -203,7 +340,7 @@ pub struct DefaultReaction {
|
|||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[repr(u32)]
|
||||
#[repr(i32)]
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/channel#channel-type>
|
||||
pub enum ChannelType {
|
||||
|
@ -256,3 +393,11 @@ pub enum ChannelType {
|
|||
// TODO: Couldn't find reference
|
||||
Unhandled = 255,
|
||||
}
|
||||
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/message#followed-channel-object>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
|
||||
pub struct FollowedChannel {
|
||||
pub channel_id: Snowflake,
|
||||
pub webhook_id: Snowflake,
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ use std::fmt::Debug;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::entities::User;
|
||||
use crate::types::Snowflake;
|
||||
use crate::types::{PartialEmoji, Shared};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::GatewayHandle;
|
||||
|
@ -22,6 +22,8 @@ use crate::gateway::Updateable;
|
|||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{Composite, Updateable};
|
||||
|
||||
use super::option_arc_rwlock_ptr_eq;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -30,9 +32,6 @@ use chorus_macros::{Composite, Updateable};
|
|||
pub struct Emoji {
|
||||
pub id: Snowflake,
|
||||
pub name: Option<String>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub roles: Option<sqlx::types::Json<Vec<Snowflake>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub roles: Option<Vec<Snowflake>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<User>>,
|
||||
|
@ -42,27 +41,33 @@ pub struct Emoji {
|
|||
pub available: Option<bool>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Emoji {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.name.hash(state);
|
||||
self.roles.hash(state);
|
||||
self.roles.hash(state);
|
||||
self.require_colons.hash(state);
|
||||
self.managed.hash(state);
|
||||
self.animated.hash(state);
|
||||
self.available.hash(state);
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
impl PartialEq for Emoji {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.name == other.name
|
||||
&& self.roles == other.roles
|
||||
&& self.roles == other.roles
|
||||
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
|
||||
&& self.require_colons == other.require_colons
|
||||
&& self.managed == other.managed
|
||||
&& self.animated == other.animated
|
||||
&& self.available == other.available
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Emoji {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
!(self.id != other.id
|
||||
|| self.name != other.name
|
||||
|| self.roles != other.roles
|
||||
|| self.require_colons != other.require_colons
|
||||
|| self.managed != other.managed
|
||||
|| self.animated != other.animated
|
||||
|| self.available != other.available)
|
||||
impl From<PartialEmoji> for Emoji {
|
||||
fn from(value: PartialEmoji) -> Self {
|
||||
Self {
|
||||
id: value.id.unwrap_or_default(), // TODO: Make this method an impl to TryFrom<> instead
|
||||
name: Some(value.name),
|
||||
roles: None,
|
||||
user: None,
|
||||
require_colons: Some(value.animated),
|
||||
managed: None,
|
||||
animated: Some(value.animated),
|
||||
available: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,27 +3,29 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
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::Shared;
|
||||
use crate::types::{
|
||||
entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook},
|
||||
interfaces::WelcomeScreenObject,
|
||||
utils::Snowflake,
|
||||
};
|
||||
use crate::UInt64;
|
||||
|
||||
use super::PublicUser;
|
||||
use super::{option_arc_rwlock_ptr_eq, vec_arc_rwlock_ptr_eq, PublicUser};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Updateable;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable};
|
||||
use chorus_macros::{observe_vec, Composite, Updateable};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::types::Composite;
|
||||
|
@ -46,10 +48,12 @@ pub struct Guild {
|
|||
pub approximate_presence_count: Option<i32>,
|
||||
pub banner: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub bans: Option<Vec<GuildBan>>,
|
||||
#[serde(default)]
|
||||
pub bans: Vec<GuildBan>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub channels: Option<Vec<Shared<Channel>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
#[serde(default)]
|
||||
pub channels: Vec<Shared<Channel>>,
|
||||
pub default_message_notifications: Option<MessageNotificationLevel>,
|
||||
pub description: Option<String>,
|
||||
pub discovery_splash: Option<String>,
|
||||
|
@ -57,17 +61,19 @@ pub struct Guild {
|
|||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
#[serde(default)]
|
||||
pub emojis: Vec<Shared<Emoji>>,
|
||||
pub explicit_content_filter: Option<i32>,
|
||||
pub explicit_content_filter: Option<ExplicitContentFilterLevel>,
|
||||
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
|
||||
pub features: Option<GuildFeaturesList>,
|
||||
#[serde(default)]
|
||||
pub features: GuildFeaturesList,
|
||||
pub icon: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub icon_hash: Option<String>,
|
||||
pub id: Snowflake,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub invites: Option<Vec<GuildInvite>>,
|
||||
#[serde(default)]
|
||||
pub invites: Vec<GuildInvite>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub joined_at: Option<String>,
|
||||
pub joined_at: Option<DateTime<Utc>>,
|
||||
pub large: Option<bool>,
|
||||
pub max_members: Option<i32>,
|
||||
pub max_presences: Option<i32>,
|
||||
|
@ -91,86 +97,39 @@ pub struct Guild {
|
|||
pub public_updates_channel_id: Option<Snowflake>,
|
||||
pub region: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub roles: Option<Vec<Shared<RoleObject>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
#[serde(default)]
|
||||
pub roles: Vec<Shared<RoleObject>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub rules_channel: Option<String>,
|
||||
pub rules_channel_id: Option<Snowflake>,
|
||||
pub splash: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub stickers: Option<Vec<Sticker>>,
|
||||
pub system_channel_flags: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub stickers: Vec<Sticker>,
|
||||
pub system_channel_flags: Option<SystemChannelFlags>,
|
||||
pub system_channel_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub vanity_url_code: Option<String>,
|
||||
pub verification_level: Option<VerificationLevel>,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub voice_states: Option<Vec<Shared<VoiceState>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
pub voice_states: Vec<Shared<VoiceState>>,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub webhooks: Option<Vec<Shared<Webhook>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
pub webhooks: Vec<Shared<Webhook>>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>,
|
||||
pub welcome_screen: sqlx::types::Json<Option<WelcomeScreenObject>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub welcome_screen: Option<WelcomeScreenObject>,
|
||||
pub widget_channel_id: Option<Snowflake>,
|
||||
pub widget_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Guild {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.afk_channel_id.hash(state);
|
||||
self.afk_timeout.hash(state);
|
||||
self.application_id.hash(state);
|
||||
self.approximate_member_count.hash(state);
|
||||
self.approximate_presence_count.hash(state);
|
||||
self.banner.hash(state);
|
||||
self.bans.hash(state);
|
||||
self.default_message_notifications.hash(state);
|
||||
self.description.hash(state);
|
||||
self.discovery_splash.hash(state);
|
||||
self.explicit_content_filter.hash(state);
|
||||
self.features.hash(state);
|
||||
self.icon.hash(state);
|
||||
self.icon_hash.hash(state);
|
||||
self.id.hash(state);
|
||||
self.invites.hash(state);
|
||||
self.joined_at.hash(state);
|
||||
self.large.hash(state);
|
||||
self.max_members.hash(state);
|
||||
self.max_presences.hash(state);
|
||||
self.max_stage_video_channel_users.hash(state);
|
||||
self.max_video_channel_users.hash(state);
|
||||
self.mfa_level.hash(state);
|
||||
self.name.hash(state);
|
||||
self.nsfw_level.hash(state);
|
||||
self.owner.hash(state);
|
||||
self.owner_id.hash(state);
|
||||
self.permissions.hash(state);
|
||||
self.preferred_locale.hash(state);
|
||||
self.premium_progress_bar_enabled.hash(state);
|
||||
self.premium_subscription_count.hash(state);
|
||||
self.premium_tier.hash(state);
|
||||
self.primary_category_id.hash(state);
|
||||
self.public_updates_channel_id.hash(state);
|
||||
self.region.hash(state);
|
||||
self.rules_channel.hash(state);
|
||||
self.rules_channel_id.hash(state);
|
||||
self.splash.hash(state);
|
||||
self.stickers.hash(state);
|
||||
self.system_channel_flags.hash(state);
|
||||
self.system_channel_id.hash(state);
|
||||
self.vanity_url_code.hash(state);
|
||||
self.verification_level.hash(state);
|
||||
self.welcome_screen.hash(state);
|
||||
self.welcome_screen.hash(state);
|
||||
self.widget_channel_id.hash(state);
|
||||
self.widget_enabled.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq for Guild {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Guild {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.afk_channel_id == other.afk_channel_id
|
||||
&& self.afk_timeout == other.afk_timeout
|
||||
|
@ -179,14 +138,17 @@ impl std::cmp::PartialEq for Guild {
|
|||
&& self.approximate_presence_count == other.approximate_presence_count
|
||||
&& self.banner == other.banner
|
||||
&& self.bans == other.bans
|
||||
&& vec_arc_rwlock_ptr_eq(&self.channels, &other.channels)
|
||||
&& self.default_message_notifications == other.default_message_notifications
|
||||
&& self.description == other.description
|
||||
&& self.discovery_splash == other.discovery_splash
|
||||
&& vec_arc_rwlock_ptr_eq(&self.emojis, &other.emojis)
|
||||
&& self.explicit_content_filter == other.explicit_content_filter
|
||||
&& self.features == other.features
|
||||
&& self.icon == other.icon
|
||||
&& self.icon_hash == other.icon_hash
|
||||
&& self.id == other.id
|
||||
&& self.invites == other.invites
|
||||
&& self.joined_at == other.joined_at
|
||||
&& self.large == other.large
|
||||
&& self.max_members == other.max_members
|
||||
|
@ -206,6 +168,7 @@ impl std::cmp::PartialEq for Guild {
|
|||
&& self.primary_category_id == other.primary_category_id
|
||||
&& self.public_updates_channel_id == other.public_updates_channel_id
|
||||
&& self.region == other.region
|
||||
&& vec_arc_rwlock_ptr_eq(&self.roles, &other.roles)
|
||||
&& self.rules_channel == other.rules_channel
|
||||
&& self.rules_channel_id == other.rules_channel_id
|
||||
&& self.splash == other.splash
|
||||
|
@ -214,6 +177,8 @@ impl std::cmp::PartialEq for Guild {
|
|||
&& self.system_channel_id == other.system_channel_id
|
||||
&& self.vanity_url_code == other.vanity_url_code
|
||||
&& self.verification_level == other.verification_level
|
||||
&& vec_arc_rwlock_ptr_eq(&self.voice_states, &other.voice_states)
|
||||
&& vec_arc_rwlock_ptr_eq(&self.webhooks, &other.webhooks)
|
||||
&& self.welcome_screen == other.welcome_screen
|
||||
&& self.welcome_screen == other.welcome_screen
|
||||
&& self.widget_channel_id == other.widget_channel_id
|
||||
|
@ -225,6 +190,7 @@ impl std::cmp::PartialEq for Guild {
|
|||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct GuildBan {
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: PublicUser,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
@ -252,32 +218,41 @@ pub struct GuildInvite {
|
|||
pub vanity_url: Option<bool>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for GuildInvite {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.code.hash(state);
|
||||
self.temporary.hash(state);
|
||||
self.uses.hash(state);
|
||||
self.max_uses.hash(state);
|
||||
self.max_age.hash(state);
|
||||
self.created_at.hash(state);
|
||||
self.expires_at.hash(state);
|
||||
self.guild_id.hash(state);
|
||||
self.channel_id.hash(state);
|
||||
self.inviter_id.hash(state);
|
||||
self.target_user_id.hash(state);
|
||||
self.target_user.hash(state);
|
||||
self.target_user_type.hash(state);
|
||||
self.vanity_url.hash(state);
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for GuildInvite {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.code == other.code
|
||||
&& self.temporary == other.temporary
|
||||
&& self.uses == other.uses
|
||||
&& self.max_uses == other.max_uses
|
||||
&& self.max_age == other.max_age
|
||||
&& self.created_at == other.created_at
|
||||
&& self.expires_at == other.expires_at
|
||||
&& self.guild_id == other.guild_id
|
||||
&& option_arc_rwlock_ptr_eq(&self.guild, &other.guild)
|
||||
&& self.channel_id == other.channel_id
|
||||
&& option_arc_rwlock_ptr_eq(&self.channel, &other.channel)
|
||||
&& self.inviter_id == other.inviter_id
|
||||
&& option_arc_rwlock_ptr_eq(&self.inviter, &other.inviter)
|
||||
&& self.target_user_id == other.target_user_id
|
||||
&& self.target_user == other.target_user
|
||||
&& self.target_user_type == other.target_user_type
|
||||
&& self.vanity_url == other.vanity_url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Copy,
|
||||
)]
|
||||
pub struct UnavailableGuild {
|
||||
pub id: Snowflake,
|
||||
pub unavailable: bool,
|
||||
pub unavailable: Option<bool>,
|
||||
pub geo_restricted: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy,
|
||||
)]
|
||||
pub struct GuildCreateResponse {
|
||||
pub id: Snowflake,
|
||||
}
|
||||
|
@ -299,20 +274,44 @@ pub struct GuildScheduledEvent {
|
|||
pub entity_id: Option<Snowflake>,
|
||||
pub entity_metadata: Option<GuildScheduledEventEntityMetadata>,
|
||||
pub creator: Option<Shared<User>>,
|
||||
pub user_count: Option<u64>,
|
||||
pub user_count: Option<UInt64>,
|
||||
pub image: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)]
|
||||
#[repr(u8)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for GuildScheduledEvent {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.guild_id == other.guild_id
|
||||
&& self.channel_id == other.channel_id
|
||||
&& self.creator_id == other.creator_id
|
||||
&& self.name == other.name
|
||||
&& self.description == other.description
|
||||
&& self.scheduled_start_time == other.scheduled_start_time
|
||||
&& self.scheduled_end_time == other.scheduled_end_time
|
||||
&& self.privacy_level == other.privacy_level
|
||||
&& self.status == other.status
|
||||
&& self.entity_type == other.entity_type
|
||||
&& self.entity_id == other.entity_id
|
||||
&& self.entity_metadata == other.entity_metadata
|
||||
&& option_arc_rwlock_ptr_eq(&self.creator, &other.creator)
|
||||
&& self.user_count == other.user_count
|
||||
&& self.image == other.image
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level>
|
||||
pub enum GuildScheduledEventPrivacyLevel {
|
||||
#[default]
|
||||
GuildOnly = 2,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)]
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status>
|
||||
pub enum GuildScheduledEventStatus {
|
||||
#[default]
|
||||
|
@ -322,8 +321,21 @@ pub enum GuildScheduledEventStatus {
|
|||
Canceled = 4,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)]
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Copy,
|
||||
Hash,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types>
|
||||
pub enum GuildScheduledEventEntityType {
|
||||
#[default]
|
||||
|
@ -332,7 +344,7 @@ pub enum GuildScheduledEventEntityType {
|
|||
External = 3,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata>
|
||||
pub struct GuildScheduledEventEntityMetadata {
|
||||
pub location: Option<String>,
|
||||
|
@ -347,9 +359,22 @@ pub struct VoiceRegion {
|
|||
custom: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#message-notification-level>
|
||||
pub enum MessageNotificationLevel {
|
||||
|
@ -358,9 +383,22 @@ pub enum MessageNotificationLevel {
|
|||
OnlyMentions = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#explicit-content-filter-level>
|
||||
pub enum ExplicitContentFilterLevel {
|
||||
|
@ -370,9 +408,22 @@ pub enum ExplicitContentFilterLevel {
|
|||
AllMembers = 2,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
pub enum VerificationLevel {
|
||||
|
@ -384,9 +435,22 @@ pub enum VerificationLevel {
|
|||
VeryHigh = 4,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
pub enum MFALevel {
|
||||
|
@ -395,9 +459,22 @@ pub enum MFALevel {
|
|||
Elevated = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
pub enum NSFWLevel {
|
||||
|
@ -408,9 +485,22 @@ pub enum NSFWLevel {
|
|||
AgeRestricted = 3,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
pub enum PremiumTier {
|
||||
|
@ -422,7 +512,8 @@ pub enum PremiumTier {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#system-channel-flags>
|
||||
pub struct SystemChannelFlags: u64 {
|
||||
|
|
|
@ -2,27 +2,52 @@
|
|||
// 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::PublicUser, Snowflake};
|
||||
use crate::types::{GuildMemberFlags, PermissionFlags, Shared};
|
||||
|
||||
use super::option_arc_rwlock_ptr_eq;
|
||||
|
||||
#[derive(Debug, Deserialize, Default, Serialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
/// Represents a participating user in a guild.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object>
|
||||
pub struct GuildMember {
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<PublicUser>>,
|
||||
pub nick: Option<String>,
|
||||
pub avatar: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub roles: Vec<Snowflake>,
|
||||
pub joined_at: String,
|
||||
pub premium_since: Option<String>,
|
||||
pub joined_at: DateTime<Utc>,
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub deaf: bool,
|
||||
pub mute: bool,
|
||||
pub flags: Option<i32>,
|
||||
pub flags: Option<GuildMemberFlags>,
|
||||
pub pending: Option<bool>,
|
||||
pub permissions: Option<String>,
|
||||
pub communication_disabled_until: Option<String>,
|
||||
#[serde(default)]
|
||||
pub permissions: PermissionFlags,
|
||||
pub communication_disabled_until: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for GuildMember {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.nick == other.nick
|
||||
&& self.avatar == other.avatar
|
||||
&& self.roles == other.roles
|
||||
&& self.joined_at == other.joined_at
|
||||
&& self.premium_since == other.premium_since
|
||||
&& self.deaf == other.deaf
|
||||
&& self.mute == other.mute
|
||||
&& self.flags == other.flags
|
||||
&& self.pending == other.pending
|
||||
&& self.permissions == other.permissions
|
||||
&& self.communication_disabled_until == other.communication_disabled_until
|
||||
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::{
|
||||
entities::{Application, User},
|
||||
utils::Snowflake,
|
||||
Shared,
|
||||
};
|
||||
use crate::{UInt16, UInt8};
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -18,13 +19,13 @@ pub struct Integration {
|
|||
pub id: Snowflake,
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub integration_type: String,
|
||||
pub integration_type: IntegrationType,
|
||||
pub enabled: bool,
|
||||
pub syncing: Option<bool>,
|
||||
pub role_id: Option<String>,
|
||||
pub enabled_emoticons: Option<bool>,
|
||||
pub expire_behaviour: Option<u8>,
|
||||
pub expire_grace_period: Option<u16>,
|
||||
pub expire_behaviour: Option<UInt8>,
|
||||
pub expire_grace_period: Option<UInt16>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<User>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
|
@ -43,3 +44,17 @@ pub struct IntegrationAccount {
|
|||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(rename_all = "snake_case"))]
|
||||
pub enum IntegrationType {
|
||||
#[default]
|
||||
Twitch,
|
||||
Youtube,
|
||||
Discord,
|
||||
GuildSubscription,
|
||||
}
|
||||
|
|
|
@ -5,37 +5,53 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::{Snowflake, WelcomeScreenObject};
|
||||
use crate::types::types::guild_configuration::GuildFeaturesList;
|
||||
use crate::types::{
|
||||
Guild, InviteFlags, InviteTargetType, InviteType, Shared, Snowflake, VerificationLevel,
|
||||
WelcomeScreenObject,
|
||||
};
|
||||
use crate::{UInt32, UInt8};
|
||||
|
||||
use super::guild::GuildScheduledEvent;
|
||||
use super::{Application, Channel, GuildMember, NSFWLevel, User};
|
||||
|
||||
/// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users.
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-object>
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct Invite {
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub approximate_member_count: Option<i32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub approximate_presence_count: Option<i32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub channel: Option<Channel>,
|
||||
pub code: String,
|
||||
pub created_at: Option<DateTime<Utc>>,
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
pub flags: Option<i32>,
|
||||
pub flags: Option<InviteFlags>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub guild: Option<InviteGuild>,
|
||||
pub guild_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub guild_scheduled_event: Option<Shared<GuildScheduledEvent>>,
|
||||
#[serde(rename = "type")]
|
||||
pub invite_type: Option<i32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(rename = "type"))]
|
||||
pub invite_type: Option<InviteType>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub inviter: Option<User>,
|
||||
pub max_age: Option<i32>,
|
||||
pub max_uses: Option<i32>,
|
||||
pub max_age: Option<UInt32>,
|
||||
pub max_uses: Option<UInt8>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub stage_instance: Option<InviteStageInstance>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub target_application: Option<Application>,
|
||||
pub target_type: Option<i32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(rename = "target_user_type"))]
|
||||
pub target_type: Option<InviteTargetType>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub target_user: Option<User>,
|
||||
pub temporary: Option<bool>,
|
||||
pub uses: Option<i32>,
|
||||
pub uses: Option<UInt32>,
|
||||
}
|
||||
|
||||
/// The guild an invite is for.
|
||||
|
@ -46,8 +62,8 @@ pub struct InviteGuild {
|
|||
pub name: String,
|
||||
pub icon: Option<String>,
|
||||
pub splash: Option<String>,
|
||||
pub verification_level: i32,
|
||||
pub features: Vec<String>,
|
||||
pub verification_level: VerificationLevel,
|
||||
pub features: GuildFeaturesList,
|
||||
pub vanity_url_code: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
|
@ -59,6 +75,29 @@ pub struct InviteGuild {
|
|||
pub welcome_screen: Option<WelcomeScreenObject>,
|
||||
}
|
||||
|
||||
impl From<Guild> for InviteGuild {
|
||||
fn from(value: Guild) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name.unwrap_or_default(),
|
||||
icon: value.icon,
|
||||
splash: value.splash,
|
||||
verification_level: value.verification_level.unwrap_or_default(),
|
||||
features: value.features,
|
||||
vanity_url_code: value.vanity_url_code,
|
||||
description: value.description,
|
||||
banner: value.banner,
|
||||
premium_subscription_count: value.premium_subscription_count,
|
||||
nsfw_deprecated: None,
|
||||
nsfw_level: value.nsfw_level.unwrap_or_default(),
|
||||
#[cfg(feature = "sqlx")]
|
||||
welcome_screen: value.welcome_screen.0,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
welcome_screen: value.welcome_screen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object>
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct InviteStageInstance {
|
||||
|
|
|
@ -2,16 +2,22 @@
|
|||
// 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};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::{
|
||||
entities::{
|
||||
Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData,
|
||||
Sticker, StickerItem, User,
|
||||
},
|
||||
utils::Snowflake,
|
||||
Shared,
|
||||
};
|
||||
use crate::{UInt32, UInt8};
|
||||
|
||||
use super::option_arc_rwlock_ptr_eq;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -25,8 +31,8 @@ pub struct Message {
|
|||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub author: Option<PublicUser>,
|
||||
pub content: Option<String>,
|
||||
pub timestamp: String,
|
||||
pub edited_timestamp: Option<String>,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub edited_timestamp: Option<DateTime<Utc>>,
|
||||
pub tts: Option<bool>,
|
||||
pub mention_everyone: bool,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
|
@ -38,7 +44,7 @@ pub struct Message {
|
|||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub attachments: Option<Vec<Attachment>>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub embeds: Vec<sqlx::types::Json<Embed>>,
|
||||
pub embeds: sqlx::types::Json<Vec<Embed>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub embeds: Option<Vec<Embed>>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
|
@ -49,7 +55,7 @@ pub struct Message {
|
|||
pub pinned: bool,
|
||||
pub webhook_id: Option<Snowflake>,
|
||||
#[serde(rename = "type")]
|
||||
pub message_type: i32,
|
||||
pub message_type: MessageType,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub activity: Option<sqlx::types::Json<MessageActivity>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
|
@ -61,17 +67,26 @@ pub struct Message {
|
|||
pub message_reference: Option<sqlx::types::Json<MessageReference>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub message_reference: Option<MessageReference>,
|
||||
pub flags: Option<u64>,
|
||||
pub flags: Option<MessageFlags>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub referenced_message: Option<Box<Message>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub interaction: Option<MessageInteraction>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub thread: Option<Channel>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub components: Option<sqlx::types::Json<Vec<Component>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub components: Option<Vec<Component>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub sticker_items: Option<Vec<StickerItem>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub stickers: Option<Vec<Sticker>>,
|
||||
pub position: Option<i32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub role_subscription_data: Option<RoleSubscriptionData>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Message {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
|
@ -88,46 +103,72 @@ impl PartialEq for Message {
|
|||
&& self.attachments == other.attachments
|
||||
&& self.embeds == other.embeds
|
||||
&& self.embeds == other.embeds
|
||||
&& self.reactions == other.reactions
|
||||
&& self.reactions == other.reactions
|
||||
&& self.nonce == other.nonce
|
||||
&& self.pinned == other.pinned
|
||||
&& self.webhook_id == other.webhook_id
|
||||
&& self.message_type == other.message_type
|
||||
&& self.activity == other.activity
|
||||
&& self.activity == other.activity
|
||||
&& self.application == other.application
|
||||
&& self.application_id == other.application_id
|
||||
&& self.message_reference == other.message_reference
|
||||
&& self.message_reference == other.message_reference
|
||||
&& self.flags == other.flags
|
||||
&& self.referenced_message == other.referenced_message
|
||||
&& self.interaction == other.interaction
|
||||
&& self.thread == other.thread
|
||||
&& self.components == other.components
|
||||
&& self.components == other.components
|
||||
&& self.sticker_items == other.sticker_items
|
||||
&& self.position == other.position
|
||||
&& self.stickers == other.stickers
|
||||
&& self.role_subscription_data == other.role_subscription_data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd, Copy)]
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/message#message-reference-object>
|
||||
pub struct MessageReference {
|
||||
#[serde(rename = "type")]
|
||||
pub reference_type: MessageReferenceType,
|
||||
pub message_id: Snowflake,
|
||||
pub channel_id: Snowflake,
|
||||
pub guild_id: Option<Snowflake>,
|
||||
pub fail_if_not_exists: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd, Copy)]
|
||||
pub enum MessageReferenceType {
|
||||
/// A standard reference used by replies and system messages
|
||||
Default = 0,
|
||||
/// A reference used to point to a message at a point in time
|
||||
Forward = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct MessageInteraction {
|
||||
pub id: Snowflake,
|
||||
#[serde(rename = "type")]
|
||||
pub interaction_type: u8,
|
||||
pub interaction_type: UInt8,
|
||||
pub name: String,
|
||||
pub user: User,
|
||||
pub member: Option<Shared<GuildMember>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for MessageInteraction {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.interaction_type == other.interaction_type
|
||||
&& self.name == other.name
|
||||
&& self.user == other.user
|
||||
&& option_arc_rwlock_ptr_eq(&self.member, &other.member)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct AllowedMention {
|
||||
parse: Vec<AllowedMentionType>,
|
||||
roles: Vec<Snowflake>,
|
||||
|
@ -135,7 +176,7 @@ pub struct AllowedMention {
|
|||
replied_user: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AllowedMentionType {
|
||||
Roles,
|
||||
|
@ -152,11 +193,11 @@ pub struct ChannelMention {
|
|||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Ord)]
|
||||
pub struct Embed {
|
||||
title: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
embed_type: Option<String>,
|
||||
embed_type: Option<EmbedType>,
|
||||
description: Option<String>,
|
||||
url: Option<String>,
|
||||
timestamp: Option<String>,
|
||||
|
@ -170,14 +211,32 @@ pub struct Embed {
|
|||
fields: Option<Vec<EmbedField>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EmbedType {
|
||||
#[deprecated]
|
||||
ApplicationNews,
|
||||
Article,
|
||||
AutoModerationMessage,
|
||||
AutoModerationNotification,
|
||||
Gift,
|
||||
#[serde(rename = "gifv")]
|
||||
GifVideo,
|
||||
Image,
|
||||
Link,
|
||||
PostPreview,
|
||||
Rich,
|
||||
Video,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct EmbedFooter {
|
||||
text: String,
|
||||
icon_url: Option<String>,
|
||||
proxy_icon_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
|
||||
pub struct EmbedImage {
|
||||
url: String,
|
||||
proxy_url: String,
|
||||
|
@ -185,7 +244,7 @@ pub struct EmbedImage {
|
|||
width: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
|
||||
pub struct EmbedThumbnail {
|
||||
url: String,
|
||||
proxy_url: Option<String>,
|
||||
|
@ -193,7 +252,7 @@ pub struct EmbedThumbnail {
|
|||
width: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
|
||||
struct EmbedVideo {
|
||||
url: Option<String>,
|
||||
proxy_url: Option<String>,
|
||||
|
@ -201,13 +260,13 @@ struct EmbedVideo {
|
|||
width: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
|
||||
pub struct EmbedProvider {
|
||||
name: Option<String>,
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
|
||||
pub struct EmbedAuthor {
|
||||
name: String,
|
||||
url: Option<String>,
|
||||
|
@ -215,7 +274,7 @@ pub struct EmbedAuthor {
|
|||
proxy_icon_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
|
||||
pub struct EmbedField {
|
||||
name: String,
|
||||
value: String,
|
||||
|
@ -224,15 +283,22 @@ pub struct EmbedField {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Reaction {
|
||||
pub count: u32,
|
||||
pub burst_count: u32,
|
||||
pub count: UInt32,
|
||||
pub burst_count: UInt32,
|
||||
#[serde(default)]
|
||||
pub me: bool,
|
||||
#[serde(default)]
|
||||
pub burst_me: bool,
|
||||
pub burst_colors: Vec<String>,
|
||||
pub emoji: Emoji,
|
||||
#[cfg(feature = "sqlx")]
|
||||
#[serde(skip)]
|
||||
pub user_ids: Vec<Snowflake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
pub enum Component {
|
||||
ActionRow = 1,
|
||||
Button = 2,
|
||||
|
@ -252,3 +318,159 @@ pub struct MessageActivity {
|
|||
pub activity_type: i64,
|
||||
pub party_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr, Eq, PartialOrd, Ord,
|
||||
)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/message#message-type>
|
||||
pub enum MessageType {
|
||||
/// A default message
|
||||
#[default]
|
||||
Default = 0,
|
||||
/// A message sent when a user is added to a group DM or thread
|
||||
RecipientAdd = 1,
|
||||
/// A message sent when a user is removed from a group DM or thread
|
||||
RecipientRemove = 2,
|
||||
/// A message sent when a user creates a call in a private channel
|
||||
Call = 3,
|
||||
/// A message sent when a group DM or thread's name is changed
|
||||
ChannelNameChange = 4,
|
||||
/// A message sent when a group DM's icon is changed
|
||||
ChannelIconChange = 5,
|
||||
/// A message sent when a message is pinned in a channel
|
||||
ChannelPinnedMessage = 6,
|
||||
/// A message sent when a user joins a guild
|
||||
GuildMemberJoin = 7,
|
||||
/// A message sent when a user subscribes to (boosts) a guild
|
||||
UserPremiumGuildSubscription = 8,
|
||||
/// A message sent when a user subscribes to (boosts) a guild to tier 1
|
||||
UserPremiumGuildSubscriptionTier1 = 9,
|
||||
/// A message sent when a user subscribes to (boosts) a guild to tier 2
|
||||
UserPremiumGuildSubscriptionTier2 = 10,
|
||||
/// A message sent when a user subscribes to (boosts) a guild to tier 3
|
||||
UserPremiumGuildSubscriptionTier3 = 11,
|
||||
/// A message sent when a news channel is followed
|
||||
ChannelFollowAdd = 12,
|
||||
/// A message sent when a user starts streaming in a guild (deprecated)
|
||||
#[deprecated]
|
||||
GuildStream = 13,
|
||||
/// A message sent when a guild is disqualified from discovery
|
||||
GuildDiscoveryDisqualified = 14,
|
||||
/// A message sent when a guild requalifies for discovery
|
||||
GuildDiscoveryRequalified = 15,
|
||||
/// A message sent when a guild has failed discovery requirements for a week
|
||||
GuildDiscoveryGracePeriodInitial = 16,
|
||||
/// A message sent when a guild has failed discovery requirements for 3 weeks
|
||||
GuildDiscoveryGracePeriodFinal = 17,
|
||||
/// A message sent when a thread is created
|
||||
ThreadCreated = 18,
|
||||
/// A message sent when a user replies to a message
|
||||
Reply = 19,
|
||||
/// A message sent when a user uses a slash command
|
||||
#[serde(rename = "CHAT_INPUT_COMMAND")]
|
||||
ApplicationCommand = 20,
|
||||
/// A message sent when a thread starter message is added to a thread
|
||||
ThreadStarterMessage = 21,
|
||||
/// A message sent to remind users to invite friends to a guild
|
||||
GuildInviteReminder = 22,
|
||||
/// A message sent when a user uses a context menu command
|
||||
ContextMenuCommand = 23,
|
||||
/// A message sent when auto moderation takes an action
|
||||
AutoModerationAction = 24,
|
||||
/// A message sent when a user purchases or renews a role subscription
|
||||
RoleSubscriptionPurchase = 25,
|
||||
/// A message sent when a user is upsold to a premium interaction
|
||||
InteractionPremiumUpsell = 26,
|
||||
/// A message sent when a stage channel starts
|
||||
StageStart = 27,
|
||||
/// A message sent when a stage channel ends
|
||||
StageEnd = 28,
|
||||
/// A message sent when a user starts speaking in a stage channel
|
||||
StageSpeaker = 29,
|
||||
/// A message sent when a user raises their hand in a stage channel
|
||||
StageRaiseHand = 30,
|
||||
/// A message sent when a stage channel's topic is changed
|
||||
StageTopic = 31,
|
||||
/// A message sent when a user purchases an application premium subscription
|
||||
GuildApplicationPremiumSubscription = 32,
|
||||
/// A message sent when a user adds an application to group DM
|
||||
PrivateChannelIntegrationAdded = 33,
|
||||
/// A message sent when a user removed an application from a group DM
|
||||
PrivateChannelIntegrationRemoved = 34,
|
||||
/// A message sent when a user gifts a premium (Nitro) referral
|
||||
PremiumReferral = 35,
|
||||
/// A message sent when a user enabled lockdown for the guild
|
||||
GuildIncidentAlertModeEnabled = 36,
|
||||
/// A message sent when a user disables lockdown for the guild
|
||||
GuildIncidentAlertModeDisabled = 37,
|
||||
/// A message sent when a user reports a raid for the guild
|
||||
GuildIncidentReportRaid = 38,
|
||||
/// A message sent when a user reports a false alarm for the guild
|
||||
GuildIncidentReportFalseAlarm = 39,
|
||||
/// A message sent when no one sends a message in the current channel for 1 hour
|
||||
GuildDeadchatRevivePrompt = 40,
|
||||
/// A message sent when a user buys another user a gift
|
||||
CustomGift = 41,
|
||||
GuildGamingStatsPrompt = 42,
|
||||
/// A message sent when a user purchases a guild product
|
||||
PurchaseNotification = 44,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/message#message-type>
|
||||
pub struct MessageFlags: u64 {
|
||||
/// This message has been published to subscribed channels (via Channel Following)
|
||||
const CROSSPOSTED = 1 << 0;
|
||||
/// This message originated from a message in another channel (via Channel Following)
|
||||
const IS_CROSSPOST = 1 << 1;
|
||||
/// Embeds will not be included when serializing this message
|
||||
const SUPPRESS_EMBEDS = 1 << 2;
|
||||
/// The source message for this crosspost has been deleted (via Channel Following)
|
||||
const SOURCE_MESSAGE_DELETED = 1 << 3;
|
||||
/// This message came from the urgent message system
|
||||
const URGENT = 1 << 4;
|
||||
/// This message has an associated thread, with the same ID as the message
|
||||
const HAS_THREAD = 1 << 5;
|
||||
/// This message is only visible to the user who invoked the interaction
|
||||
const EPHEMERAL = 1 << 6;
|
||||
/// This message is an interaction response and the bot is "thinking"
|
||||
const LOADING = 1 << 7;
|
||||
/// Some roles were not mentioned and added to the thread
|
||||
const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8;
|
||||
/// This message contains a link that impersonates Discord
|
||||
const SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10;
|
||||
/// This message will not trigger push and desktop notifications
|
||||
const SUPPRESS_NOTIFICATIONS = 1 << 12;
|
||||
/// This message's audio attachments are rendered as voice messages
|
||||
const VOICE_MESSAGE = 1 << 13;
|
||||
/// This message has a forwarded message snapshot attached
|
||||
const HAS_SNAPSHOT = 1 << 14;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct PartialEmoji {
|
||||
#[serde(default)]
|
||||
pub id: Option<Snowflake>,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub animated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, Eq, Hash)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
pub enum ReactionType {
|
||||
Normal = 0,
|
||||
Burst = 1, // The dreaded super reactions
|
||||
}
|
||||
|
|
|
@ -27,7 +27,10 @@ pub use user_settings::*;
|
|||
pub use voice_state::*;
|
||||
pub use webhook::*;
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::Shared;
|
||||
#[cfg(feature = "client")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Updateable;
|
||||
|
||||
|
@ -39,8 +42,6 @@ use async_trait::async_trait;
|
|||
|
||||
#[cfg(feature = "client")]
|
||||
use std::fmt::Debug;
|
||||
#[cfg(feature = "client")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
mod application;
|
||||
mod attachment;
|
||||
|
@ -127,15 +128,60 @@ pub trait Composite<T: Updateable + Clone + Debug> {
|
|||
pub trait IntoShared {
|
||||
/// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`.
|
||||
///
|
||||
/// [`Shared<Self>`] can then be observed using the [`Gateway`], turning the underlying
|
||||
/// [`Shared<Self>`] can then be observed using the gateway, turning the underlying
|
||||
/// `dyn Composite<Self>` 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.
|
||||
/// over the gateway connection, reducing the need for expensive network-API calls.
|
||||
fn into_shared(self) -> Shared<Self>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl<T: Sized> IntoShared for T {
|
||||
fn into_shared(self) -> Shared<Self> {
|
||||
Arc::new(RwLock::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal function to compare two `Shared<T>`s by comparing their pointers.
|
||||
#[cfg_attr(not(feature = "client"), allow(unused_variables))]
|
||||
pub(crate) fn arc_rwlock_ptr_eq<T>(a: &Shared<T>, b: &Shared<T>) -> bool {
|
||||
#[cfg(feature = "client")]
|
||||
{
|
||||
Shared::ptr_eq(a, b)
|
||||
}
|
||||
#[cfg(not(feature = "client"))]
|
||||
{
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal function to compare two `Vec<Shared<T>>`s by comparing their pointers.
|
||||
pub(crate) fn vec_arc_rwlock_ptr_eq<T>(a: &[Shared<T>], b: &[Shared<T>]) -> bool {
|
||||
for (a, b) in a.iter().zip(b.iter()) {
|
||||
if !arc_rwlock_ptr_eq(a, b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Internal function to compare two `Option<Shared<T>>`s by comparing their pointers.
|
||||
pub(crate) fn option_arc_rwlock_ptr_eq<T>(a: &Option<Shared<T>>, b: &Option<Shared<T>>) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => arc_rwlock_ptr_eq(a, b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal function to compare two `Option<Vec<Shared<T>>>`s by comparing their pointers.
|
||||
pub(crate) fn option_vec_arc_rwlock_ptr_eq<T>(
|
||||
a: &Option<Vec<Shared<T>>>,
|
||||
b: &Option<Vec<Shared<T>>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => vec_arc_rwlock_ptr_eq(a, b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ use crate::types::Snowflake;
|
|||
/// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants
|
||||
/// for when the Snowflake is not yet known.
|
||||
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize, PartialOrd, Ord,
|
||||
)]
|
||||
pub enum LimitType {
|
||||
AuthRegister,
|
||||
AuthLogin,
|
||||
|
@ -29,7 +31,7 @@ pub enum LimitType {
|
|||
|
||||
/// A struct that represents the current ratelimits, either instance-wide or user-wide.
|
||||
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
|
||||
pub struct Limit {
|
||||
pub bucket: LimitType,
|
||||
pub limit: u64,
|
||||
|
|
|
@ -6,10 +6,9 @@ use chrono::{DateTime, Utc};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::Snowflake;
|
||||
use crate::types::{Shared, Snowflake};
|
||||
|
||||
use super::PublicUser;
|
||||
use super::{arc_rwlock_ptr_eq, PublicUser};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-structure>
|
||||
|
@ -22,17 +21,32 @@ pub struct Relationship {
|
|||
pub since: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Relationship {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.relationship_type == other.relationship_type
|
||||
&& self.since == other.since
|
||||
&& self.nickname == other.nickname
|
||||
&& arc_rwlock_ptr_eq(&self.user, &other.user)
|
||||
&& self.since == other.since
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Copy,
|
||||
Hash,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type>
|
||||
pub enum RelationshipType {
|
||||
Suggestion = 6,
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number};
|
||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::types::utils::Snowflake;
|
||||
use crate::{UInt16, UInt32};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{Composite, Updateable};
|
||||
|
@ -32,10 +33,9 @@ pub struct RoleObject {
|
|||
pub hoist: bool,
|
||||
pub icon: Option<String>,
|
||||
pub unicode_emoji: Option<String>,
|
||||
pub position: u16,
|
||||
pub position: UInt16,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_string_from_number")]
|
||||
pub permissions: String,
|
||||
pub permissions: PermissionFlags,
|
||||
pub managed: bool,
|
||||
pub mentionable: bool,
|
||||
#[cfg(feature = "sqlx")]
|
||||
|
@ -48,11 +48,13 @@ pub struct RoleObject {
|
|||
pub struct RoleSubscriptionData {
|
||||
pub role_subscription_listing_id: Snowflake,
|
||||
pub tier_name: String,
|
||||
pub total_months_subscribed: u32,
|
||||
pub total_months_subscribed: UInt32,
|
||||
pub is_renewal: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure>
|
||||
pub struct RoleTags {
|
||||
#[serde(default)]
|
||||
|
@ -71,7 +73,8 @@ pub struct RoleTags {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq, PartialOrd, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
/// Permissions limit what users of certain roles can do on a Guild to Guild basis.
|
||||
///
|
||||
/// # Reference:
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::utils::Snowflake;
|
||||
use crate::UInt64;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -13,7 +14,7 @@ pub struct SecurityKey {
|
|||
pub user_id: String,
|
||||
pub key_id: String,
|
||||
pub public_key: String,
|
||||
pub counter: u64,
|
||||
pub counter: UInt64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
|
@ -24,7 +25,8 @@ impl Default for SecurityKey {
|
|||
user_id: String::new(),
|
||||
key_id: String::new(),
|
||||
public_key: String::new(),
|
||||
counter: 0,
|
||||
#[allow(clippy::useless_conversion)]
|
||||
counter: 0u64.into(),
|
||||
name: String::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,11 @@ pub struct StageInstance {
|
|||
pub guild_scheduled_event_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level>
|
||||
pub enum StageInstancePrivacyLevel {
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::{entities::User, utils::Snowflake};
|
||||
use crate::types::{entities::User, utils::Snowflake, Shared};
|
||||
|
||||
use super::option_arc_rwlock_ptr_eq;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -19,34 +21,21 @@ pub struct Sticker {
|
|||
pub pack_id: Option<Snowflake>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub tags: String,
|
||||
pub tags: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub asset: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub sticker_type: u8,
|
||||
pub format_type: u8,
|
||||
pub sticker_type: StickerType,
|
||||
pub format_type: StickerFormatType,
|
||||
pub available: Option<bool>,
|
||||
pub guild_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<User>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub sort_value: Option<u8>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Sticker {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.pack_id.hash(state);
|
||||
self.name.hash(state);
|
||||
self.description.hash(state);
|
||||
self.tags.hash(state);
|
||||
self.asset.hash(state);
|
||||
self.sticker_type.hash(state);
|
||||
self.format_type.hash(state);
|
||||
self.available.hash(state);
|
||||
self.guild_id.hash(state);
|
||||
self.sort_value.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Sticker {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
|
@ -59,53 +48,16 @@ impl PartialEq for Sticker {
|
|||
&& self.format_type == other.format_type
|
||||
&& self.available == other.available
|
||||
&& self.guild_id == other.guild_id
|
||||
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
|
||||
&& self.sort_value == other.sort_value
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Sticker {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match self.id.partial_cmp(&other.id) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.pack_id.partial_cmp(&other.pack_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.description.partial_cmp(&other.description) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.tags.partial_cmp(&other.tags) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.asset.partial_cmp(&other.asset) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.sticker_type.partial_cmp(&other.sticker_type) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.format_type.partial_cmp(&other.format_type) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.available.partial_cmp(&other.available) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
match self.guild_id.partial_cmp(&other.guild_id) {
|
||||
Some(core::cmp::Ordering::Equal) => {}
|
||||
ord => return ord,
|
||||
}
|
||||
self.sort_value.partial_cmp(&other.sort_value)
|
||||
impl Sticker {
|
||||
pub fn tags(&self) -> Vec<String> {
|
||||
self.tags.as_ref().map_or(vec![], |s| {
|
||||
s.split(',').map(|tag| tag.trim().to_string()).collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,5 +71,70 @@ impl PartialOrd for Sticker {
|
|||
pub struct StickerItem {
|
||||
pub id: Snowflake,
|
||||
pub name: String,
|
||||
pub format_type: u8,
|
||||
pub format_type: StickerFormatType,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename = "SCREAMING_SNAKE_CASE")]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/sticker#sticker-types>
|
||||
pub enum StickerType {
|
||||
/// An official sticker in a current or legacy purchasable pack
|
||||
Standard = 1,
|
||||
#[default]
|
||||
/// A sticker uploaded to a guild for the guild's members
|
||||
Guild = 2,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/sticker#sticker-format-types>
|
||||
pub enum StickerFormatType {
|
||||
#[default]
|
||||
/// A PNG image
|
||||
PNG = 1,
|
||||
/// An animated PNG image, using the APNG format - uses CDN
|
||||
APNG = 2,
|
||||
/// A lottie animation; requires the VERIFIED and/or PARTNERED guild feature - uses CDN
|
||||
LOTTIE = 3,
|
||||
/// An animated GIF image - does not use CDN
|
||||
GIF = 4,
|
||||
}
|
||||
|
||||
impl StickerFormatType {
|
||||
pub fn is_animated(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StickerFormatType::APNG | StickerFormatType::LOTTIE | StickerFormatType::GIF
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn to_mime(&self) -> &'static str {
|
||||
match self {
|
||||
StickerFormatType::PNG => "image/png",
|
||||
StickerFormatType::APNG => "image/apng",
|
||||
StickerFormatType::LOTTIE => "application/json",
|
||||
StickerFormatType::GIF => "image/gif",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_mime(mime: &str) -> Option<Self> {
|
||||
match mime {
|
||||
"image/png" => Some(StickerFormatType::PNG),
|
||||
"image/apng" => Some(StickerFormatType::APNG),
|
||||
"application/json" => Some(StickerFormatType::LOTTIE),
|
||||
"image/gif" => Some(StickerFormatType::GIF),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::entities::User;
|
||||
use crate::types::Shared;
|
||||
use crate::types::Snowflake;
|
||||
use crate::UInt8;
|
||||
|
||||
use super::arc_rwlock_ptr_eq;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -19,10 +22,31 @@ pub struct Team {
|
|||
pub owner_user_id: Snowflake,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Team {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.icon == other.icon
|
||||
&& self.id == other.id
|
||||
&& self.members == other.members
|
||||
&& self.name == other.name
|
||||
&& self.owner_user_id == other.owner_user_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct TeamMember {
|
||||
pub membership_state: u8,
|
||||
pub membership_state: UInt8,
|
||||
pub permissions: Vec<String>,
|
||||
pub team_id: Snowflake,
|
||||
pub user: Shared<User>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for TeamMember {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.membership_state == other.membership_state
|
||||
&& self.permissions == other.permissions
|
||||
&& self.team_id == other.team_id
|
||||
&& arc_rwlock_ptr_eq(&self.user, &other.user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::{
|
||||
entities::{Guild, User},
|
||||
utils::Snowflake,
|
||||
Shared,
|
||||
};
|
||||
use crate::UInt64;
|
||||
|
||||
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-template>
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
|
@ -18,7 +19,7 @@ pub struct GuildTemplate {
|
|||
pub code: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub usage_count: Option<u64>,
|
||||
pub usage_count: Option<UInt64>,
|
||||
pub creator_id: Snowflake,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub creator: Shared<User>,
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
// 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::ChorusError;
|
||||
use crate::types::utils::Snowflake;
|
||||
use crate::{UInt32, UInt8};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||
use std::array::TryFromSliceError;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
|
@ -45,7 +48,7 @@ pub struct User {
|
|||
pub bot: Option<bool>,
|
||||
pub system: Option<bool>,
|
||||
pub mfa_enabled: Option<bool>,
|
||||
pub accent_color: Option<u8>,
|
||||
pub accent_color: Option<UInt32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(default))]
|
||||
pub locale: Option<String>,
|
||||
pub verified: Option<bool>,
|
||||
|
@ -54,14 +57,14 @@ pub struct User {
|
|||
/// So we need to account for that
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_option_number_from_string")]
|
||||
pub flags: Option<i32>,
|
||||
pub flags: Option<UserFlags>,
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub premium_type: Option<u8>,
|
||||
pub premium_type: Option<UInt8>,
|
||||
pub pronouns: Option<String>,
|
||||
pub public_flags: Option<u32>,
|
||||
pub public_flags: Option<UserFlags>,
|
||||
pub banner: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub theme_colors: Option<Vec<u8>>,
|
||||
pub theme_colors: Option<ThemeColors>,
|
||||
pub phone: Option<String>,
|
||||
pub nsfw_allowed: Option<bool>,
|
||||
pub premium: Option<bool>,
|
||||
|
@ -70,21 +73,87 @@ pub struct User {
|
|||
pub disabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)]
|
||||
pub struct ThemeColors {
|
||||
#[serde(flatten)]
|
||||
inner: (u32, u32),
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for ThemeColors {
|
||||
type Error = ChorusError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() % 4 != 0 || value.len() > 8 {
|
||||
return Err(ChorusError::InvalidArguments {
|
||||
error: "Value has incorrect length to be decodeable from Vec<u8>".to_string(),
|
||||
});
|
||||
}
|
||||
let first: Result<[u8; 4], TryFromSliceError> = value[0..3].try_into();
|
||||
let second: Result<[u8; 4], TryFromSliceError> = {
|
||||
if value.len() == 8 {
|
||||
value[0..3].try_into()
|
||||
} else {
|
||||
[0; 4][0..3].try_into()
|
||||
}
|
||||
};
|
||||
|
||||
match (first, second) {
|
||||
(Ok(first), Ok(second)) => Ok(Self {
|
||||
inner: (u32::from_be_bytes(first), u32::from_be_bytes(second)),
|
||||
}),
|
||||
_ => Err(ChorusError::InvalidArguments {
|
||||
error: "ThemeColors cannot be built from this Vec<u8>".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
// TODO: Add tests for Encode and Decode.
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for ThemeColors {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
||||
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut vec_u8 = Vec::new();
|
||||
vec_u8.extend_from_slice(&self.inner.0.to_be_bytes());
|
||||
vec_u8.extend_from_slice(&self.inner.1.to_be_bytes());
|
||||
<Vec<u8> as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&vec_u8, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'d> sqlx::Decode<'d, sqlx::Postgres> for ThemeColors {
|
||||
fn decode(
|
||||
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'d>,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::Postgres>>::decode(value)?;
|
||||
value_vec.try_into().map_err(|e: ChorusError| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::Postgres> for ThemeColors {
|
||||
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
|
||||
<String as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub struct PublicUser {
|
||||
pub id: Snowflake,
|
||||
pub username: Option<String>,
|
||||
pub discriminator: Option<String>,
|
||||
pub avatar: Option<String>,
|
||||
pub accent_color: Option<u8>,
|
||||
pub accent_color: Option<UInt32>,
|
||||
pub banner: Option<String>,
|
||||
pub theme_colors: Option<Vec<u8>>,
|
||||
pub theme_colors: Option<ThemeColors>,
|
||||
pub pronouns: Option<String>,
|
||||
pub bot: Option<bool>,
|
||||
pub bio: Option<String>,
|
||||
pub premium_type: Option<u8>,
|
||||
pub premium_type: Option<UInt8>,
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub public_flags: Option<u32>,
|
||||
pub public_flags: Option<UserFlags>,
|
||||
}
|
||||
|
||||
impl From<User> for PublicUser {
|
||||
|
@ -111,8 +180,8 @@ impl From<User> for PublicUser {
|
|||
const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
pub struct UserFlags: u64 {
|
||||
const DISCORD_EMPLOYEE = 1 << 0;
|
||||
const PARTNERED_SERVER_OWNER = 1 << 1;
|
||||
|
@ -144,7 +213,7 @@ pub struct UserProfileMetadata {
|
|||
pub bio: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub accent_color: Option<i32>,
|
||||
pub theme_colors: Option<Vec<i32>>,
|
||||
pub theme_colors: Option<ThemeColors>,
|
||||
pub popout_animation_particle_type: Option<Snowflake>,
|
||||
pub emoji: Option<Emoji>,
|
||||
}
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
// 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;
|
||||
use crate::types::Shared;
|
||||
use crate::{UInt16, UInt32, UInt8};
|
||||
use serde_aux::field_attributes::deserialize_option_number_from_string;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UserStatus {
|
||||
|
@ -27,7 +29,9 @@ impl std::fmt::Display for UserStatus {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UserTheme {
|
||||
|
@ -39,36 +43,23 @@ pub enum UserTheme {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct UserSettings {
|
||||
pub afk_timeout: u16,
|
||||
pub afk_timeout: Option<UInt16>,
|
||||
pub allow_accessibility_detection: bool,
|
||||
pub animate_emoji: bool,
|
||||
pub animate_stickers: u8,
|
||||
pub animate_stickers: UInt8,
|
||||
pub contact_sync_enabled: bool,
|
||||
pub convert_emoticons: bool,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub custom_status: Option<sqlx::types::Json<CustomStatus>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub custom_status: Option<CustomStatus>,
|
||||
pub default_guilds_restricted: bool,
|
||||
pub detect_platform_accounts: bool,
|
||||
pub developer_mode: bool,
|
||||
pub disable_games_tab: bool,
|
||||
pub enable_tts_command: bool,
|
||||
pub explicit_content_filter: u8,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub friend_source_flags: sqlx::types::Json<FriendSourceFlags>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub explicit_content_filter: UInt8,
|
||||
pub friend_source_flags: FriendSourceFlags,
|
||||
pub gateway_connected: Option<bool>,
|
||||
pub gif_auto_play: bool,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub guild_folders: sqlx::types::Json<Vec<GuildFolder>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub guild_folders: Vec<GuildFolder>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
#[serde(default)]
|
||||
pub guild_positions: sqlx::types::Json<Vec<String>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
#[serde(default)]
|
||||
pub guild_positions: Vec<String>,
|
||||
pub inline_attachment_media: bool,
|
||||
|
@ -78,9 +69,6 @@ pub struct UserSettings {
|
|||
pub native_phone_integration_enabled: bool,
|
||||
pub render_embeds: bool,
|
||||
pub render_reactions: bool,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub restricted_guilds: sqlx::types::Json<Vec<String>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub restricted_guilds: Vec<String>,
|
||||
pub show_current_game: bool,
|
||||
pub status: Shared<UserStatus>,
|
||||
|
@ -92,10 +80,14 @@ pub struct UserSettings {
|
|||
impl Default for UserSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
afk_timeout: 3600,
|
||||
#[allow(clippy::useless_conversion)]
|
||||
afk_timeout: Some(3600u16.into()),
|
||||
allow_accessibility_detection: true,
|
||||
animate_emoji: true,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
animate_stickers: 0,
|
||||
#[cfg(feature = "sqlx")]
|
||||
animate_stickers: 0.into(),
|
||||
contact_sync_enabled: false,
|
||||
convert_emoticons: false,
|
||||
custom_status: None,
|
||||
|
@ -104,7 +96,10 @@ impl Default for UserSettings {
|
|||
developer_mode: true,
|
||||
disable_games_tab: true,
|
||||
enable_tts_command: false,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
explicit_content_filter: 0,
|
||||
#[cfg(feature = "sqlx")]
|
||||
explicit_content_filter: 0.into(),
|
||||
friend_source_flags: Default::default(),
|
||||
gateway_connected: Some(false),
|
||||
gif_auto_play: false,
|
||||
|
@ -119,7 +114,7 @@ impl Default for UserSettings {
|
|||
render_reactions: true,
|
||||
restricted_guilds: Default::default(),
|
||||
show_current_game: true,
|
||||
status: Arc::new(RwLock::new(UserStatus::Online)),
|
||||
status: Default::default(),
|
||||
stream_notifications_enabled: false,
|
||||
theme: UserTheme::Dark,
|
||||
timezone_offset: 0,
|
||||
|
@ -128,7 +123,8 @@ impl Default for UserSettings {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
|
||||
pub struct CustomStatus {
|
||||
pub emoji_id: Option<String>,
|
||||
pub emoji_name: Option<String>,
|
||||
|
@ -137,7 +133,8 @@ pub struct CustomStatus {
|
|||
pub text: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
|
||||
pub struct FriendSourceFlags {
|
||||
pub all: bool,
|
||||
}
|
||||
|
@ -149,11 +146,20 @@ impl Default for FriendSourceFlags {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
|
||||
pub struct GuildFolder {
|
||||
pub color: u32,
|
||||
pub color: Option<UInt32>,
|
||||
pub guild_ids: Vec<String>,
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
// FIXME: What is this thing?
|
||||
// It's not a snowflake, and it's sometimes a string and sometimes an integer.
|
||||
//
|
||||
// Ex: 1249181105
|
||||
//
|
||||
// It can also be negative somehow? Ex: -1176643795
|
||||
#[serde(deserialize_with = "deserialize_option_number_from_string")]
|
||||
pub id: Option<i64>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
#[cfg(feature = "client")]
|
||||
use chorus_macros::Composite;
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::Shared;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::types::Composite;
|
||||
|
||||
|
@ -24,6 +25,8 @@ use crate::types::{
|
|||
utils::Snowflake,
|
||||
};
|
||||
|
||||
use super::option_arc_rwlock_ptr_eq;
|
||||
|
||||
/// The VoiceState struct. Note, that Discord does not have an `id` field for this, whereas Spacebar
|
||||
/// does.
|
||||
///
|
||||
|
@ -33,9 +36,11 @@ use crate::types::{
|
|||
#[cfg_attr(feature = "client", derive(Composite))]
|
||||
pub struct VoiceState {
|
||||
pub guild_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub guild: Option<Guild>,
|
||||
pub channel_id: Option<Snowflake>,
|
||||
pub user_id: Snowflake,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub member: Option<Shared<GuildMember>>,
|
||||
/// Includes alphanumeric characters, not a snowflake
|
||||
pub session_id: String,
|
||||
|
@ -51,6 +56,29 @@ pub struct VoiceState {
|
|||
pub id: Option<Snowflake>, // Only exists on Spacebar
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for VoiceState {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.guild_id == other.guild_id
|
||||
&& self.guild == other.guild
|
||||
&& self.channel_id == other.channel_id
|
||||
&& self.user_id == other.user_id
|
||||
&& option_arc_rwlock_ptr_eq(&self.member, &other.member)
|
||||
&& self.session_id == other.session_id
|
||||
&& self.token == other.token
|
||||
&& self.deaf == other.deaf
|
||||
&& self.mute == other.mute
|
||||
&& self.self_deaf == other.self_deaf
|
||||
&& self.self_mute == other.self_mute
|
||||
&& self.self_stream == other.self_stream
|
||||
&& self.self_video == other.self_video
|
||||
&& self.suppress == other.suppress
|
||||
&& self.request_to_speak_timestamp == other.request_to_speak_timestamp
|
||||
&& self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl Updateable for VoiceState {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn id(&self) -> Snowflake {
|
||||
|
|
|
@ -6,7 +6,8 @@ use std::fmt::Debug;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::Shared;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Updateable;
|
||||
|
||||
|
@ -24,6 +25,8 @@ use crate::types::{
|
|||
utils::Snowflake,
|
||||
};
|
||||
|
||||
use super::option_arc_rwlock_ptr_eq;
|
||||
|
||||
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook>
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
|
||||
|
@ -31,13 +34,13 @@ use crate::types::{
|
|||
pub struct Webhook {
|
||||
pub id: Snowflake,
|
||||
#[serde(rename = "type")]
|
||||
pub webhook_type: i32,
|
||||
pub webhook_type: WebhookType,
|
||||
pub name: String,
|
||||
pub avatar: String,
|
||||
pub token: String,
|
||||
pub guild_id: Snowflake,
|
||||
pub channel_id: Snowflake,
|
||||
pub application_id: Snowflake,
|
||||
pub application_id: Option<Snowflake>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<User>>,
|
||||
|
@ -47,3 +50,33 @@ pub struct Webhook {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl PartialEq for Webhook {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.webhook_type == other.webhook_type
|
||||
&& self.name == other.name
|
||||
&& self.avatar == other.avatar
|
||||
&& self.token == other.token
|
||||
&& self.guild_id == other.guild_id
|
||||
&& self.channel_id == other.channel_id
|
||||
&& self.application_id == other.application_id
|
||||
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
|
||||
&& option_arc_rwlock_ptr_eq(&self.source_guild, &other.source_guild)
|
||||
&& self.url == other.url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
|
||||
)]
|
||||
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
|
||||
#[cfg_attr(feature = "sqlx", repr(i16))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
pub enum WebhookType {
|
||||
#[default]
|
||||
Incoming = 1,
|
||||
ChannelFollower = 2,
|
||||
Application = 3,
|
||||
}
|
||||
|
|
|
@ -21,15 +21,18 @@ pub enum Error {
|
|||
|
||||
#[error(transparent)]
|
||||
Guild(#[from] GuildError),
|
||||
|
||||
#[error("Invalid flags value: {0}")]
|
||||
InvalidFlags(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error, Copy, Clone)]
|
||||
pub enum GuildError {
|
||||
#[error("Invalid Guild Feature")]
|
||||
InvalidGuildFeature,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error, Copy, Clone)]
|
||||
pub enum FieldFormatError {
|
||||
#[error("Password must be between 1 and 72 characters.")]
|
||||
PasswordError,
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
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 <https://discord.com/developers/docs/topics/gateway-events#application-command-permissions-update>
|
||||
pub struct ApplicationCommandPermissionsUpdate {
|
||||
#[serde(flatten)]
|
||||
pub permissions: GuildApplicationCommandPermissions,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for ApplicationCommandPermissionsUpdate {}
|
||||
|
|
|
@ -2,28 +2,27 @@
|
|||
// 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};
|
||||
use chorus_macros::{JsonField, SourceUrlField};
|
||||
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 <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-create>
|
||||
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 <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-update>
|
||||
pub struct AutoModerationRuleUpdate {
|
||||
#[serde(flatten)]
|
||||
|
@ -43,18 +42,14 @@ impl UpdateMessage<AutoModerationRule> for AutoModerationRuleUpdate {
|
|||
}
|
||||
}
|
||||
|
||||
impl WebSocketEvent for AutoModerationRuleUpdate {}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-delete>
|
||||
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 <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-action-execution>
|
||||
pub struct AutoModerationActionExecution {
|
||||
pub guild_id: Snowflake,
|
||||
|
@ -69,5 +64,3 @@ pub struct AutoModerationActionExecution {
|
|||
pub matched_keyword: Option<String>,
|
||||
pub matched_content: Option<String>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for AutoModerationActionExecution {}
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
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;
|
||||
///
|
||||
|
@ -23,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?;
|
||||
///
|
||||
|
@ -40,9 +39,19 @@ 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,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
/// Officially Undocumented;
|
||||
/// Deletes a ringing call;
|
||||
/// Ex: {"t":"CALL_DELETE","s":8,"op":0,"d":{"channel_id":"837609115475771392"}}
|
||||
|
@ -50,9 +59,19 @@ 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,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
/// Officially Undocumented;
|
||||
/// See <https://unofficial-discord-docs.vercel.app/gateway/op13>;
|
||||
///
|
||||
|
@ -60,5 +79,3 @@ impl WebSocketEvent for CallDelete {}
|
|||
pub struct CallSync {
|
||||
pub channel_id: Snowflake,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for CallSync {}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// 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};
|
||||
|
@ -13,12 +12,15 @@ use serde::{Deserialize, Serialize};
|
|||
use super::UpdateMessage;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::Shared;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::types::IntoShared;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::types::Guild;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, WebSocketEvent, Copy, PartialEq, Clone, Eq, Hash, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update>
|
||||
pub struct ChannelPinsUpdate {
|
||||
pub guild_id: Option<Snowflake>,
|
||||
|
@ -26,9 +28,7 @@ pub struct ChannelPinsUpdate {
|
|||
pub last_pin_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for ChannelPinsUpdate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-create>
|
||||
pub struct ChannelCreate {
|
||||
#[serde(flatten)]
|
||||
|
@ -39,8 +39,6 @@ pub struct ChannelCreate {
|
|||
pub source_url: String,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for ChannelCreate {}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl UpdateMessage<Guild> for ChannelCreate {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
@ -51,15 +49,11 @@ impl UpdateMessage<Guild> for ChannelCreate {
|
|||
fn update(&mut self, object_to_update: Shared<Guild>) {
|
||||
let mut write = object_to_update.write().unwrap();
|
||||
let update = self.channel.clone().into_shared();
|
||||
if write.channels.is_some() {
|
||||
write.channels.as_mut().unwrap().push(update);
|
||||
} else {
|
||||
write.channels = Some(Vec::from([update]));
|
||||
}
|
||||
write.channels.push(update);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-update>
|
||||
pub struct ChannelUpdate {
|
||||
#[serde(flatten)]
|
||||
|
@ -70,8 +64,6 @@ pub struct ChannelUpdate {
|
|||
pub source_url: String,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for ChannelUpdate {}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl UpdateMessage<Channel> for ChannelUpdate {
|
||||
fn update(&mut self, object_to_update: Shared<Channel>) {
|
||||
|
@ -85,7 +77,7 @@ impl UpdateMessage<Channel> for ChannelUpdate {
|
|||
}
|
||||
}
|
||||
|
||||
#[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"}}
|
||||
|
@ -94,18 +86,16 @@ pub struct ChannelUnreadUpdate {
|
|||
pub guild_id: Snowflake,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Contains very few fields from [Channel]
|
||||
/// See also [ChannelUnreadUpdate]
|
||||
pub struct ChannelUnreadUpdateObject {
|
||||
pub id: Snowflake,
|
||||
pub last_message_id: Snowflake,
|
||||
pub last_pin_timestamp: Option<String>,
|
||||
pub last_pin_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for ChannelUnreadUpdate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-delete>
|
||||
pub struct ChannelDelete {
|
||||
#[serde(flatten)]
|
||||
|
@ -128,16 +118,15 @@ impl UpdateMessage<Guild> for ChannelDelete {
|
|||
return;
|
||||
}
|
||||
let mut write = object_to_update.write().unwrap();
|
||||
if write.channels.is_none() {
|
||||
if write.channels.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (iteration, item) in (0_u32..).zip(write.channels.as_mut().unwrap().iter()) {
|
||||
for (iteration, item) in (0_u32..).zip(write.channels.iter()) {
|
||||
if item.read().unwrap().id == self.id().unwrap() {
|
||||
write.channels.as_mut().unwrap().remove(iteration as usize);
|
||||
write.channels.remove(iteration as usize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocketEvent for ChannelDelete {}
|
||||
|
|
|
@ -9,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, IntoShared, JsonField, RoleObject,
|
||||
Snowflake, SourceUrlField, Sticker,
|
||||
AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake,
|
||||
SourceUrlField, Sticker,
|
||||
};
|
||||
|
||||
use super::PresenceUpdate;
|
||||
|
@ -18,9 +18,21 @@ use super::PresenceUpdate;
|
|||
#[cfg(feature = "client")]
|
||||
use super::UpdateMessage;
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::IntoShared;
|
||||
#[cfg(feature = "client")]
|
||||
use crate::types::Shared;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
Default,
|
||||
Clone,
|
||||
SourceUrlField,
|
||||
JsonField,
|
||||
WebSocketEvent,
|
||||
PartialEq,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-create>;
|
||||
/// 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
|
||||
|
@ -47,7 +59,7 @@ impl UpdateMessage<Guild> for GuildCreate {
|
|||
fn update(&mut self, _: Shared<Guild>) {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum GuildCreateDataOption {
|
||||
UnavailableGuild(UnavailableGuild),
|
||||
|
@ -60,9 +72,31 @@ impl Default for GuildCreateDataOption {
|
|||
}
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildCreate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum GuildEvents {
|
||||
Create(GuildCreate),
|
||||
Update(GuildUpdate),
|
||||
Delete(GuildDelete),
|
||||
BanAdd(GuildBanAdd),
|
||||
BanRemove(GuildBanRemove),
|
||||
EmojisUpdate(GuildEmojisUpdate),
|
||||
StickersUpdate(GuildStickersUpdate),
|
||||
IntegrationsUpdate(GuildIntegrationsUpdate),
|
||||
MemberAdd(GuildMemberAdd),
|
||||
MemberRemove(GuildMemberRemove),
|
||||
MemberUpdate(GuildMemberUpdate),
|
||||
MembersChunk(GuildMembersChunk),
|
||||
RoleCreate(GuildRoleCreate),
|
||||
RoleUpdate(GuildRoleUpdate),
|
||||
RoleDelete(GuildRoleDelete),
|
||||
ScheduledEventCreate(GuildScheduledEventCreate),
|
||||
ScheduledEventUpdate(GuildScheduledEventUpdate),
|
||||
ScheduledEventDelete(GuildScheduledEventDelete),
|
||||
ScheduledEventUserAdd(GuildScheduledEventUserAdd),
|
||||
ScheduledEventUserRemove(GuildScheduledEventUserRemove),
|
||||
AuditLogEntryCreate(GuildAuditLogEntryCreate),
|
||||
}
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-ban-add-guild-ban-add-event-fields>;
|
||||
/// Received to give info about a user being banned from a guild;
|
||||
pub struct GuildBanAdd {
|
||||
|
@ -70,9 +104,7 @@ pub struct GuildBanAdd {
|
|||
pub user: PublicUser,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildBanAdd {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove>;
|
||||
/// Received to give info about a user being unbanned from a guild;
|
||||
pub struct GuildBanRemove {
|
||||
|
@ -80,9 +112,17 @@ 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,
|
||||
PartialEq,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-update>;
|
||||
/// Received to give info about a guild being updated;
|
||||
pub struct GuildUpdate {
|
||||
|
@ -94,8 +134,6 @@ pub struct GuildUpdate {
|
|||
pub json: String,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildUpdate {}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl UpdateMessage<Guild> for GuildUpdate {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
@ -104,7 +142,17 @@ impl UpdateMessage<Guild> for GuildUpdate {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
Clone,
|
||||
SourceUrlField,
|
||||
JsonField,
|
||||
WebSocketEvent,
|
||||
PartialEq,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-delete>;
|
||||
/// Received to tell the client about a guild being deleted;
|
||||
pub struct GuildDelete {
|
||||
|
@ -125,9 +173,7 @@ impl UpdateMessage<Guild> for GuildDelete {
|
|||
fn update(&mut self, _: Shared<Guild>) {}
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildDelete {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-audit-log-entry-create>;
|
||||
/// Received to the client about an audit log entry being added;
|
||||
pub struct GuildAuditLogEntryCreate {
|
||||
|
@ -135,9 +181,7 @@ pub struct GuildAuditLogEntryCreate {
|
|||
pub entry: AuditLogEntry,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildAuditLogEntryCreate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update>;
|
||||
/// Received to tell the client about a change to a guild's emoji list;
|
||||
pub struct GuildEmojisUpdate {
|
||||
|
@ -145,9 +189,7 @@ pub struct GuildEmojisUpdate {
|
|||
pub emojis: Vec<Emoji>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildEmojisUpdate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-stickers-update>;
|
||||
/// Received to tell the client about a change to a guild's sticker list;
|
||||
pub struct GuildStickersUpdate {
|
||||
|
@ -155,17 +197,13 @@ pub struct GuildStickersUpdate {
|
|||
pub stickers: Vec<Sticker>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildStickersUpdate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-integrations-update>
|
||||
pub struct GuildIntegrationsUpdate {
|
||||
pub guild_id: Snowflake,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildIntegrationsUpdate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-add>;
|
||||
/// Received to tell the client about a user joining a guild;
|
||||
pub struct GuildMemberAdd {
|
||||
|
@ -174,9 +212,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, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-remove>;
|
||||
/// Received to tell the client about a user leaving a guild;
|
||||
pub struct GuildMemberRemove {
|
||||
|
@ -184,9 +220,7 @@ pub struct GuildMemberRemove {
|
|||
pub user: PublicUser,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildMemberRemove {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-update>
|
||||
pub struct GuildMemberUpdate {
|
||||
pub guild_id: Snowflake,
|
||||
|
@ -202,9 +236,7 @@ pub struct GuildMemberUpdate {
|
|||
pub communication_disabled_until: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildMemberUpdate {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-members-chunk>
|
||||
pub struct GuildMembersChunk {
|
||||
pub guild_id: Snowflake,
|
||||
|
@ -216,9 +248,17 @@ pub struct GuildMembersChunk {
|
|||
pub nonce: Option<String>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildMembersChunk {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
Clone,
|
||||
JsonField,
|
||||
SourceUrlField,
|
||||
WebSocketEvent,
|
||||
PartialEq,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-create>
|
||||
pub struct GuildRoleCreate {
|
||||
pub guild_id: Snowflake,
|
||||
|
@ -229,8 +269,6 @@ pub struct GuildRoleCreate {
|
|||
pub source_url: String,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildRoleCreate {}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl UpdateMessage<Guild> for GuildRoleCreate {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
@ -240,19 +278,21 @@ impl UpdateMessage<Guild> for GuildRoleCreate {
|
|||
|
||||
fn update(&mut self, object_to_update: Shared<Guild>) {
|
||||
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(self.role.clone().into_shared());
|
||||
} else {
|
||||
object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()]));
|
||||
}
|
||||
object_to_update.roles.push(self.role.clone().into_shared());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
Clone,
|
||||
JsonField,
|
||||
SourceUrlField,
|
||||
WebSocketEvent,
|
||||
PartialEq,
|
||||
)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-update>
|
||||
pub struct GuildRoleUpdate {
|
||||
pub guild_id: Snowflake,
|
||||
|
@ -263,8 +303,6 @@ pub struct GuildRoleUpdate {
|
|||
pub source_url: String,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildRoleUpdate {}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl UpdateMessage<RoleObject> for GuildRoleUpdate {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
@ -278,43 +316,35 @@ impl UpdateMessage<RoleObject> for GuildRoleUpdate {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-delete>
|
||||
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, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create>
|
||||
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, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-update>
|
||||
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, PartialEq)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-delete>
|
||||
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, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-user-add>
|
||||
pub struct GuildScheduledEventUserAdd {
|
||||
pub guild_scheduled_event_id: Snowflake,
|
||||
|
@ -322,14 +352,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, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-user-remove>
|
||||
pub struct GuildScheduledEventUserRemove {
|
||||
pub guild_scheduled_event_id: Snowflake,
|
||||
pub user_id: Snowflake,
|
||||
pub guild_id: Snowflake,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GuildScheduledEventUserRemove {}
|
||||
|
|
|
@ -5,17 +5,52 @@
|
|||
use crate::types::events::WebSocketEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(
|
||||
Debug, Deserialize, Serialize, WebSocketEvent, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
pub struct GatewayHeartbeat {
|
||||
pub op: u8,
|
||||
pub d: Option<u64>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GatewayHeartbeat {}
|
||||
impl GatewayHeartbeat {
|
||||
/// The Heartbeat packet a server would receive from a new or fresh Gateway connection.
|
||||
pub fn first() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||
/// Quickly create a [GatewayHeartbeat] with the correct `opcode` and the given `sequence_number`.
|
||||
///
|
||||
/// Shorthand for
|
||||
/// ```rs
|
||||
/// Self {
|
||||
/// op: 1,
|
||||
/// d: Some(sequence_number)
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new(sequence_number: u64) -> Self {
|
||||
Self {
|
||||
op: 1,
|
||||
d: Some(sequence_number),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for GatewayHeartbeat {
|
||||
fn default() -> Self {
|
||||
Self { op: 1, d: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Deserialize, Serialize, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
pub struct GatewayHeartbeatAck {
|
||||
pub op: i32,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GatewayHeartbeatAck {}
|
||||
impl std::default::Default for GatewayHeartbeatAck {
|
||||
fn default() -> Self {
|
||||
Self { op: 11 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,42 @@
|
|||
// 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, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent, Copy, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
pub struct GatewayHello {
|
||||
pub op: i32,
|
||||
pub d: HelloData,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GatewayHello {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy)]
|
||||
#[derive(
|
||||
Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, WebSocketEvent, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
/// 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 {}
|
||||
impl std::default::Default for GatewayHello {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// "HELLO" opcode is 10
|
||||
op: 10,
|
||||
d: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for HelloData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Discord docs mention 45000 seconds - discord.sex mentions 41250. Defaulting to 45s
|
||||
heartbeat_interval: 45000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ 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,
|
||||
|
@ -70,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
|
||||
|
@ -159,7 +157,7 @@ impl GatewayIdentifyConnectionProps {
|
|||
// 25% of the web
|
||||
//default.browser_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36".to_string();
|
||||
browser: String::from("Chrome"),
|
||||
browser_version: String::from("113.0.0.0"),
|
||||
browser_version: String::from("126.0.0.0"),
|
||||
system_locale: String::from("en-US"),
|
||||
os: String::from("Windows"),
|
||||
os_version: Some(String::from("10")),
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
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 <https://discord.com/developers/docs/topics/gateway-events#integration-create>
|
||||
pub struct IntegrationCreate {
|
||||
#[serde(flatten)]
|
||||
|
@ -14,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 <https://discord.com/developers/docs/topics/gateway-events#integration-update>
|
||||
pub struct IntegrationUpdate {
|
||||
#[serde(flatten)]
|
||||
|
@ -24,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, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#integration-delete>
|
||||
pub struct IntegrationDelete {
|
||||
pub id: Snowflake,
|
||||
|
@ -34,4 +31,3 @@ pub struct IntegrationDelete {
|
|||
pub application_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for IntegrationDelete {}
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
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 <https://discord.com/developers/docs/topics/gateway-events#interaction-create>
|
||||
pub struct InteractionCreate {
|
||||
#[serde(flatten)]
|
||||
pub interaction: Interaction,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for InteractionCreate {}
|
||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use super::WebSocketEvent;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
||||
/// Your session is now invalid.
|
||||
///
|
||||
/// Either reauthenticate and reidentify or resume if possible.
|
||||
|
@ -14,4 +14,3 @@ pub struct GatewayInvalidSession {
|
|||
pub resumable: bool,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GatewayInvalidSession {}
|
||||
|
|
|
@ -5,17 +5,16 @@
|
|||
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 <https://discord.com/developers/docs/topics/gateway-events#invite-create>
|
||||
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 <https://discord.com/developers/docs/topics/gateway-events#invite-delete>
|
||||
pub struct InviteDelete {
|
||||
pub channel_id: Snowflake,
|
||||
|
@ -23,4 +22,3 @@ pub struct InviteDelete {
|
|||
pub code: String,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for InviteDelete {}
|
||||
|
|
|
@ -7,10 +7,9 @@ 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;
|
||||
|
@ -31,4 +30,3 @@ pub struct LazyRequest {
|
|||
pub channels: Option<HashMap<String, Vec<Vec<u64>>>>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for LazyRequest {}
|
||||
|
|
|
@ -6,12 +6,12 @@ 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 <https://discord.com/developers/docs/topics/gateway-events#typing-start>
|
||||
pub struct TypingStartEvent {
|
||||
|
@ -22,9 +22,7 @@ pub struct TypingStartEvent {
|
|||
pub member: Option<GuildMember>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for TypingStartEvent {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-create>
|
||||
pub struct MessageCreate {
|
||||
#[serde(flatten)]
|
||||
|
@ -34,7 +32,7 @@ pub struct MessageCreate {
|
|||
pub mentions: Option<Vec<MessageCreateUser>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields>
|
||||
pub struct MessageCreateUser {
|
||||
#[serde(flatten)]
|
||||
|
@ -42,9 +40,7 @@ pub struct MessageCreateUser {
|
|||
pub member: Option<GuildMember>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageCreate {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-update>
|
||||
pub struct MessageUpdate {
|
||||
|
@ -55,9 +51,20 @@ pub struct MessageUpdate {
|
|||
pub mentions: Option<Vec<MessageCreateUser>>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageUpdate {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
Clone,
|
||||
WebSocketEvent,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-delete>
|
||||
pub struct MessageDelete {
|
||||
|
@ -66,9 +73,19 @@ pub struct MessageDelete {
|
|||
pub guild_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageDelete {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
Clone,
|
||||
WebSocketEvent,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-delete-bulk>
|
||||
pub struct MessageDeleteBulk {
|
||||
|
@ -77,9 +94,7 @@ pub struct MessageDeleteBulk {
|
|||
pub guild_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageDeleteBulk {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-add>
|
||||
pub struct MessageReactionAdd {
|
||||
|
@ -91,9 +106,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 <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove>
|
||||
pub struct MessageReactionRemove {
|
||||
|
@ -104,9 +117,20 @@ pub struct MessageReactionRemove {
|
|||
pub emoji: Emoji,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageReactionRemove {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
Clone,
|
||||
WebSocketEvent,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-all>
|
||||
pub struct MessageReactionRemoveAll {
|
||||
|
@ -115,9 +139,7 @@ pub struct MessageReactionRemoveAll {
|
|||
pub guild_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageReactionRemoveAll {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
|
||||
/// # Reference
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-emoji>
|
||||
pub struct MessageReactionRemoveEmoji {
|
||||
|
@ -127,9 +149,7 @@ 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
|
||||
|
@ -139,8 +159,8 @@ impl WebSocketEvent for MessageReactionRemoveEmoji {}
|
|||
///
|
||||
/// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}}
|
||||
pub struct MessageACK {
|
||||
/// ?
|
||||
pub version: u16,
|
||||
// No ideas. See 206933
|
||||
pub version: u32,
|
||||
pub message_id: Snowflake,
|
||||
/// This is an integer???
|
||||
/// Not even unix, see '3070'???
|
||||
|
@ -149,5 +169,3 @@ pub struct MessageACK {
|
|||
pub flags: Option<serde_json::Value>,
|
||||
pub channel_id: Snowflake,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for MessageACK {}
|
||||
|
|
|
@ -33,6 +33,8 @@ pub use voice::*;
|
|||
pub use voice_gateway::*;
|
||||
pub use webhooks::*;
|
||||
|
||||
use chorus_macros::WebSocketEvent;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use super::Snowflake;
|
||||
|
||||
|
@ -46,7 +48,7 @@ use serde_json::{from_str, from_value, to_value, Value};
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Shared;
|
||||
use crate::types::Shared;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
|
@ -84,7 +86,7 @@ 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]
|
||||
|
@ -102,8 +104,6 @@ pub struct GatewaySendPayload {
|
|||
pub sequence_number: Option<u64>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for GatewaySendPayload {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Clone)]
|
||||
/// The payload used for receiving events from the gateway
|
||||
pub struct GatewayReceivePayload<'a> {
|
||||
|
|
|
@ -7,15 +7,17 @@ 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?)
|
||||
pub struct PassiveUpdateV1 {
|
||||
#[serde(default)]
|
||||
pub voice_states: Vec<VoiceState>,
|
||||
pub members: Option<Vec<GuildMember>>,
|
||||
#[serde(default)]
|
||||
pub members: Vec<GuildMember>,
|
||||
pub guild_id: Snowflake,
|
||||
#[serde(default)]
|
||||
pub channels: Vec<ChannelUnreadUpdateObject>,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for PassiveUpdateV1 {}
|
||||
|
|
|
@ -6,7 +6,7 @@ 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 <https://discord.com/developers/docs/topics/gateway-events#update-presence>
|
||||
pub struct UpdatePresence {
|
||||
|
@ -18,16 +18,18 @@ 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 <https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields>
|
||||
/// (Same structure as <https://docs.discord.sex/resources/presence#presence-object>)
|
||||
pub struct PresenceUpdate {
|
||||
pub user: PublicUser,
|
||||
#[serde(default)]
|
||||
pub guild_id: Option<Snowflake>,
|
||||
pub status: UserStatus,
|
||||
#[serde(default)]
|
||||
pub activities: Vec<Activity>,
|
||||
pub client_status: ClientStatusObject,
|
||||
}
|
||||
|
||||
impl WebSocketEvent for PresenceUpdate {}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue