Compare commits

..

80 Commits

Author SHA1 Message Date
kozabrada123 433711409e update error messages
Update error message to be more brief and compliant with C-GOOD-ERR
2024-04-28 12:32:24 +02:00
kozabrada123 2deb63af10 merge w/ dev 2024-04-28 12:03:47 +02:00
kozabrada123 6ad1b862c4
Use WebSocketEvent derive instead of impl WebSocketEvent for .. (#491)
use derive macro instead of manual impl blocks
2024-04-28 08:32:05 +02:00
kozabrada123 a710edc2a1
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
2024-04-19 17:13:36 +02:00
kozabrada123 171b46c4d7
Limit test actions to 30 minutes (#489)
fix: limit all tests to 30 minutes
2024-04-16 17:40:38 +02:00
bitfl0wer d2761079cf
change version to 0.15.0 2024-04-16 17:38:36 +02:00
kozabrada123 56b2381716
Primitive voice implementation (feature/voice) (#457)
* Add Webrtc Identify & Ready

* Add more webrtc typings

* Attempt an untested voice gateway implementation

* fmt

* Merge with main

* Same allow as for voice as normal gateway

* Test error observer

* Minor updates

* More derives

* Even more derives

* Small types update

* e

* Minor doc fixes

* Modernise voice gateway

* Add default impl for voicegatewayerror

* Make voice event fields pub

* Event updates via the scientific method

* ??

* Fix bad request in voice gateway init

* Voice gateway updates

* Fix error failing to 'deserialize' properly

* Update voice identify

* Clarify FIXME related to #430

* Update to v7

* Create seperate voice_gateway.rs and voice_udp.rs

* Restructure voice to new module

* fix: deserialization error in speaking bitflags

* feat: kinda janky ip discovery impl

* feat: return ip discovery data + minor update

* feat: packet parsing!

* fix: voice works again

* feat: add voice_media_sink_wants

(comitting uncommited changes to merge)

* chore: rename events/webrtc to events/voice_gateway

* Add UdpHandle

* chore: clippy + other misc updates

* fix: attempt to fix failing wasm build

* chore: yes clippy, that is indeed an unneeded return statement

* feat: add VoiceData struct

* feat: add VoiceData reference to UdpHandler

* feat: decryption?

* chore: formatting

* feat: add ssrc definition (op 12)

* feat: add untested sending & asbtract nonce generation

* feat: Public api! (sorta)

* small updates

* feat: add sequence number

* chore: yes

* feat: merge VoiceHandler into official development

* chore: yes clippy, you are special

* fix: duplicated gateway events

* feat: first try at vgw wasm compat

* fix: blunder

* fix: gateway connect using wrong url

* fix: properly using encrypted data, bad practice for buffer creation

* chore: split voice udp

* feat: udp error handling, create udp/backends

* fix: its the same

* chore: clarify UDP on WASM

* api: split voice gateway and udp features, test for voice gateway in WASM

* feat: new encryption modes, minor code quality

* docs: document voice encryption modes

* chore: unused imports

* chore: update getrandom version to match wasm version

* chore: update on packet size FIXME

* drop buf asap

* Okay can't do that actually

* tests: add nonce test

* normal tests work?

* docs: fix doc warning, fix incorrect refrences to 'webrtc'

* chore: json isn't a doc test

* tests: better gateway auth test

* testing tests

* update voice heartbeat, fix the new test issue

* committed too much

* fix: unused import

* fix: use ip discovery address as string, not as Vec<u8>

* chore: less obnoxious logging

* chore: better unimplemented voice modes handling

* chore: remove unused variable

* chore: use matches macro

* add voice examples, make gateway ones clearer

* rename voice example

* chore: remove unused VoiceHandler

* fix: implement gateway Reconnect and InvalidSession

* Typo

Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com>

* Fix a bunch of typos

Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com>

* fix: error handling while loading native certs

* fix: guh

* use be for nonce bytes

* fix: refactor gw and vgw closures

* remove outdated docs

---------

Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com>
2024-04-16 17:18:21 +02:00
kozabrada123 a55e3120af
Fix code scanning alerts on #487 (#488)
fix codescanning alerts on 0.15.0
2024-04-16 15:54:30 +02:00
bitfl0wer f8ba59c43e
bump version, add rust-version 2024-04-16 15:13:27 +02:00
kozabrada123 0aeda51878
Bump whoami to fix stack buffer overflow (#485)
his was done since whoami versions < 1.5.0 are vulnerable to a stack
buffer overflow.
2024-04-16 08:31:14 +02:00
kozabrada123 854c9820ef
Ignore unused imports for pub use (#484) 2024-03-15 17:08:46 +01:00
kozabrada123 d5cebf0fef
Minor docs updates, add Get Private Channels (#483)
* feat: add get_private_channels

* minor docs update and reorder
2024-03-15 16:13:51 +01:00
kozabrada123 2b69035ec7
Bump mio to fix RUSTSEC-2024-0019 (#482) 2024-03-14 20:25:24 +01:00
kozabrada123 51c676c000
Fix broken luna.gitlab.io links (#480)
docs: fix broken luna.gitlab.io links
2024-02-25 09:54:06 +01:00
Flori 73c3d030f1
Move contribution guidelines to CONTRIBUTING.md (#478)
Moves the contribution guidelines from README.md to CONTRIBUTING.md, as
per @striezel's suggestion.
2024-02-04 22:30:08 +01:00
bitfl0wer 9f76afb6bb
Move contribution guidelines to CONTRIBUTING.md 2024-02-01 11:52:29 +01:00
Flori 82fd55b20c
License change (#477) 2024-01-31 23:59:42 +01:00
kozabrada123 5dc1dee27a
Update github/codeql-action to v3 (#476) 2024-01-31 23:15:08 +01:00
Dirk Stolle 362dd57504
Replace unmaintained actions-rs/toolchain by dtolnay/rust-toolchain (#473) 2024-01-31 23:02:10 +01:00
kozabrada123 36a72416d6
Fix example in readme for #456 (#475) 2024-01-31 22:53:13 +01:00
Dirk Stolle d3e5df65d4
Update h2 to 0.3.24 to fix vulnerability RUSTSEC-2024-0003 (#474)
The update fixes a resource exhaustion vulnerability in h2 which
may lead to Denial of Service. For more information on that see
<https://rustsec.org/advisories/RUSTSEC-2024-0003>.
2024-01-31 22:47:46 +01:00
Dirk Stolle 908e995949
Bump actions/setup-node in GHA workflow to v4 (#472) 2024-01-31 22:43:54 +01:00
Dirk Stolle f0686892e0
Fix a few typos (#471) 2024-01-31 22:27:53 +01:00
bitfl0wer 28cdd43b9b
Include license header everywhere 2024-01-30 17:19:34 +01:00
bitfl0wer 3700347503
Change license to Mozilla Public License v2.0 2024-01-30 10:03:14 +01:00
bitfl0wer 234304465b
Change license to Mozilla Public License v2.0 2024-01-30 10:00:05 +01:00
Flori 7a7c468bd0
Coverage (#468)
Up the reported coverage on coveralls by adding some missing tests,
especially for types. Also removes/replaces some old, faulty or
non-idiomatic code.

While unit testing might seem a bit bloaty (especially with this many
additions), I'd argue that writing somewhat sensible tests is the only
good way to somewhat reliably keep things from breaking in larger
projects/bigger codebases, such as these.

Most of these tests should, hopefully, be of acceptable quality,
although I do admit that when writing tests, I sometimes just like to
turn my brain off
2024-01-24 23:44:31 +01:00
bitfl0wer 262a52a8e9
exclude trivial id() functions from coverage 2024-01-24 23:26:59 +01:00
bitfl0wer 3040dcc46b
Add comment about test_self_updating_structs 2024-01-24 23:02:16 +01:00
bitfl0wer c950288df1
extend self updating structs test 2024-01-24 23:01:38 +01:00
bitfl0wer e073ff26c4
remove hit limit test 2024-01-24 18:51:10 +01:00
bitfl0wer 3ffb124cd4
Add test for get_limit_config 2024-01-24 12:32:08 +01:00
bitfl0wer 57e6cb438d
Add test to hit ratelimit 2024-01-24 12:21:46 +01:00
bitfl0wer 970f5b8b4f
Remove PartialOrd from Emoji because unneccessary 2024-01-23 23:53:08 +01:00
bitfl0wer 98f42aa03b
Add Message PartialEq Test 2024-01-23 23:42:00 +01:00
bitfl0wer 9cc7ede763
Add partial_eq test for relationship.rs/Relationship 2024-01-23 23:31:35 +01:00
bitfl0wer a2b6d4e407
Remove old/redundant code from attachment.rs 2024-01-23 23:11:37 +01:00
bitfl0wer 41a0e2fe27
Add to_public_user test 2024-01-23 23:06:24 +01:00
bitfl0wer 00c70501c4
Rename to_public_user into into_public_user 2024-01-23 23:06:14 +01:00
bitfl0wer 97ab757633
Add unit tests for guild.rs entities 2024-01-23 21:13:15 +01:00
bitfl0wer 7434690027
Remove Eq fromn Guild as it is not Eq 2024-01-23 21:05:01 +01:00
bitfl0wer 577a399a7b
Create tests from to_str and from_str for GuildFeatures 2024-01-23 20:50:19 +01:00
bitfl0wer 11df180446
APPEND: Remove unused imports 2024-01-23 19:08:35 +01:00
bitfl0wer 0923de59a4
Replace usage of Arc<RwLock<...>> in public APIs with Shared<...> 2024-01-23 19:07:23 +01:00
bitfl0wer 8846159ffd
Remove impl Eq from types that didn't qualify for Eq 2024-01-23 18:43:06 +01:00
bitfl0wer 013687c810
write unit tests for config and entities 2024-01-22 20:57:29 +01:00
bitfl0wer c6e7724650
rustfmt 2024-01-22 20:57:17 +01:00
bitfl0wer 74fc954d1a
Fix broken behaviour for ConfigEntity 2024-01-22 20:57:08 +01:00
Flori fe8106d2a1
"Self updating structs" API improvements (#467)
This PR slightly improves the ergonomics of working with self-updating
structs, by making the changes as documented in #466.
2024-01-22 15:19:24 +01:00
bitfl0wer 21699e5899
Loosen bounds on IntoShared<T> 2024-01-22 15:00:46 +01:00
bitfl0wer 5372d2c475
Make IntoShared trait with blanket implementation 2024-01-22 14:56:23 +01:00
bitfl0wer 29f3ee802a
Fix errors by moving into_shared out of Composite 2024-01-22 14:50:33 +01:00
bitfl0wer 6637f14b18
Replace Arc, Rwlock with Shared 2024-01-21 20:24:17 +01:00
bitfl0wer a571a9e137
Write documentation for observe 2024-01-21 20:13:00 +01:00
bitfl0wer 57214fd2fe
Add documentation for into_shared 2024-01-21 17:15:11 +01:00
bitfl0wer ca58767372
Rename to_shared to into_shared 2024-01-21 17:10:24 +01:00
bitfl0wer 2a7cae30b8
Define public method `to_shared` for dyn Composite 2024-01-21 17:07:54 +01:00
bitfl0wer 0660e25bdb
rustfmt 2024-01-21 17:07:30 +01:00
bitfl0wer 36ac6c1e5e
Replace use of Arc<RwLock<T>> with Shared<T> 2024-01-21 17:07:19 +01:00
bitfl0wer 315fe8e33b
Define type alias `Shared` 2024-01-21 17:06:43 +01:00
kozabrada123 8e25f401a5
Minor instance updates (#465)
make Instance::from_url_bundle pub, update Instance docs
2024-01-20 13:15:13 +01:00
Flori 85e494dd4a
merge main into dev (#464) 2024-01-19 21:55:29 +01:00
Flori d3853700c0
Update README.md 2024-01-19 21:55:05 +01:00
bitfl0wer 59b6907481
Merge branch 'main' of https://github.com/polyphony-chat/chorus 2024-01-19 21:50:44 +01:00
bitfl0wer f7d31fe57b
bump version to 0.14.0 2024-01-19 21:50:42 +01:00
Flori dcc626ef10
Version 0.14.0 (#463)
## Fixes

- Fix gateway heartbeat on WASM: #460

## Changes

- Update `GuildDefaults` to use new enums
- Make new enums derive `sqlx::Type`
- Feature lock UpdateMessage implementations

## Package changes

- Bump package version, especially reqwest from 0.11.22->0.11.23 -
removes need for custom git branch in Cargo.toml
2024-01-19 21:50:26 +01:00
Flori 011b214ea1
Fix gateway heartbeat on WASM (#460)
It turns out `std::time::Instant::now()` panics WASM (see #459) and
breaks the heartbeat handler.

This pr attempts to fix that by replacing `std::time::Instant` with
`wasmtimer::std::Instant` and `safina_timer::sleep_until` with
`wasmtimer::tokio::sleep_until`.
2024-01-19 17:24:48 +01:00
kozabrada123 8a2bc8287e Revert "Mess w/ the tests to see if it really works"
This reverts commit 8243f103f9.
2024-01-19 16:05:34 +01:00
kozabrada123 8243f103f9 Mess w/ the tests to see if it really works 2024-01-19 15:53:24 +01:00
kozabrada123 34cc344c8d feat: switch safina_timer for tokio, fix sleep duration overflow in examples 2024-01-19 15:48:59 +01:00
kozabrada123 72936d4f21 right 2024-01-19 15:31:40 +01:00
kozabrada123 921a3ef9c0 fix: gateway simple example 2024-01-19 15:21:53 +01:00
kozabrada123 c3017df1c2 fix tests 2024-01-19 15:14:50 +01:00
kozabrada123 e2b69487aa fix error 2024-01-19 15:06:27 +01:00
kozabrada123 d37415fc13 feat: fix heartbeat time on WASM 2024-01-19 14:55:23 +01:00
Flori 82a3f98db7
bump package versions (#458) 2024-01-11 19:51:27 +01:00
bitfl0wer 7fdb4bae49
bump package versions 2024-01-11 19:36:36 +01:00
bitfl0wer de42299fd0
bump reqwest to 0.11.23 2024-01-11 19:30:40 +01:00
Flori 7a517b3663
Well known required (#456)
Title: Add User Authentication Feature

Description:

This pull request introduces user authentication functionality to our
web application. The main goal of this feature is to ensure that each
action performed on the platform is tied to a valid, logged-in user,
thus providing accountability and maintaining data security.

Changes:

New models: Added the User model to represent users in our system. This
model includes fields: username, password_hash, email etc.

User seriliazer and views: Implemented serializers and API views for
user registration, login, and logout.

Authentication Middlewares: Added middlewares to check for a valid
session or token before allowing access to certain views.

Tests: Included comprehensive test coverage for the new feature. Tests
were implemented to verify user registration, login, and logout
functionality, as well as checking authentication enforcement on
applicable views.

    Documentation: Updated API documentation related to User endpoints.

This feature is expected to improve the overall security of our
application by properly managing user sessions and actions.

Note: You will see references to some helper tools like make_password
and check_password methods, these are security measures to ensure we are
not storing plain text passwords in the database.

This PR follows our Python coding standards and is fully linted and
tested.

Requesting review and feedback. If all points are clear and no issues
are detected during the review, we would appreciate it if this PR could
be merged at the earliest convenience.

Related Issue: #123
2023-12-15 00:22:36 +01:00
bitfl0wer fd3aad03e3
Refactor instance creation 2023-12-15 00:10:33 +01:00
232 changed files with 5981 additions and 1710 deletions

View File

@ -13,13 +13,14 @@ jobs:
linux:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
@ -52,7 +53,7 @@ jobs:
# - name: Clone spacebar server
# run: |
# git clone https://github.com/bitfl0wer/server.git
# - uses: actions/setup-node@v3
# - uses: actions/setup-node@v4
# with:
# node-version: 18
# cache: 'npm'
@ -75,12 +76,13 @@ jobs:
# SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast
wasm-gecko:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
@ -100,15 +102,16 @@ jobs:
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
wasm-chrome:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
@ -128,4 +131,4 @@ jobs:
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"

View File

@ -29,12 +29,9 @@ jobs:
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: stable
components: clippy
override: true
- name: Install required cargo
run: cargo install clippy-sarif sarif-fmt
@ -47,7 +44,7 @@ jobs:
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

10
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,10 @@
# Contributing
**Please refer to the [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) and [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md) before making a contribution.**
Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement,
ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401).
If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility.
Please feel free to open an Issue with the idea you have, or a Pull Request.

451
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
[package]
name = "chorus"
description = "A library for interacting with multiple Spacebar-compatible Instances at once."
version = "0.13.0"
license = "AGPL-3.0"
version = "0.15.0"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/polyphony-chat/chorus"
readme = "README.md"
keywords = ["spacebar", "discord", "polyphony"]
website = ["https://discord.com/invite/m3FpcapGDD"]
rust-version = "1.67.1"
[features]
@ -16,34 +17,34 @@ backend = ["dep:poem", "dep:sqlx"]
rt-multi-thread = ["tokio/rt-multi-thread"]
rt = ["tokio/rt"]
client = []
voice = ["voice_udp", "voice_gateway"]
voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
voice_gateway = []
[dependencies]
tokio = { version = "1.34.0", features = ["macros", "sync"] }
serde = { version = "1.0.188", features = ["derive", "rc"] }
serde_json = { version = "1.0.105", features = ["raw_value"] }
serde-aux = "4.2.0"
serde_with = "3.3.0"
serde_repr = "0.1.16"
reqwest = { git = "https://github.com/bitfl0wer/reqwest.git", branch = "wasm-headers", features = [
"multipart",
"json",
], version = "0.11.22" } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22
url = "2.4.0"
chrono = { version = "0.4.26", features = ["serde"] }
regex = "1.9.4"
tokio = { version = "1.35.1", features = ["macros", "sync"] }
serde = { version = "1.0.195", features = ["derive", "rc"] }
serde_json = { version = "1.0.111", features = ["raw_value"] }
serde-aux = "4.3.1"
serde_with = "3.4.0"
serde_repr = "0.1.18"
reqwest = { features = ["multipart", "json"], version = "0.11.23" }
url = "2.5.0"
chrono = { version = "0.4.31", features = ["serde"] }
regex = "1.10.2"
custom_error = "1.9.2"
futures-util = "0.3.28"
http = "0.2.9"
base64 = "0.21.3"
bitflags = { version = "2.4.0", features = ["serde"] }
futures-util = "0.3.30"
http = "0.2.11"
base64 = "0.21.7"
bitflags = { version = "2.4.1", features = ["serde"] }
lazy_static = "1.4.0"
poem = { version = "1.3.57", optional = true }
thiserror = "1.0.47"
poem = { version = "1.3.59", optional = true }
thiserror = "1.0.56"
jsonwebtoken = "8.3.0"
log = "0.4.20"
async-trait = "0.1.73"
chorus-macros = "0.2.0"
sqlx = { version = "0.7.1", features = [
async-trait = "0.1.77"
chorus-macros = "0.3.0"
sqlx = { version = "0.7.3", features = [
"mysql",
"sqlite",
"json",
@ -52,11 +53,12 @@ sqlx = { version = "0.7.1", features = [
"runtime-tokio-native-tls",
"any",
], optional = true }
safina-timer = "0.1.11"
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] }
crypto_secretbox = { version = "0.1.1", optional = true }
rand = "0.8.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustls = "0.21.8"
rustls = "0.21.10"
rustls-native-certs = "0.6.3"
tokio-tungstenite = { version = "0.20.1", features = [
"rustls-tls-native-roots",
@ -64,13 +66,15 @@ tokio-tungstenite = { version = "0.20.1", features = [
] }
native-tls = "0.2.11"
hostname = "0.3.1"
getrandom = { version = "0.2.12" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.11", features = ["js"] }
getrandom = { version = "0.2.12", features = ["js"] }
ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.38"
wasm-bindgen-futures = "0.4.39"
wasmtimer = "0.2.0"
[dev-dependencies]
lazy_static = "1.4.0"
wasm-bindgen-test = "0.3.38"
wasm-bindgen = "0.2.88"
wasm-bindgen-test = "0.3.39"
wasm-bindgen = "0.2.89"

1034
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ To get started with Chorus, import it into your project by adding the following
```toml
[dependencies]
chorus = "0.13.0"
chorus = "0.15.0"
```
### Establishing a Connection
@ -53,16 +53,10 @@ To connect to a Spacebar compatible server, you need to create an [`Instance`](h
```rs
use chorus::instance::Instance;
use chorus::UrlBundle;
#[tokio::main]
async fn main() {
let bundle = UrlBundle::new(
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let instance = Instance::new(bundle)
let instance = Instance::new("https://example.com")
.await
.expect("Failed to connect to the Spacebar server");
// You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
@ -87,7 +81,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance
.login_account(login_schema)
@ -125,7 +119,7 @@ like "proxy connection checking" are already disabled on this version, which oth
### wasm
To test for wasm, you will need to `cargo install wasm-pack`. You can then run
`wasm-pack test --<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
@ -134,11 +128,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
## Contributing
Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement,
ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401).
If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility.
Please feel free to open an Issue with the idea you have, or a Pull Request. Please keep our [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) in mind. Your contribution might not be accepted if it violates these guidelines or [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md).
See [CONTRIBUTING.md](./CONTRIBUTING.md).
<details>
<summary>Progress Tracker/Roadmap</summary>

View File

@ -15,7 +15,7 @@ dependencies = [
[[package]]
name = "chorus-macros"
version = "0.1.0"
version = "0.2.1"
dependencies = [
"async-trait",
"quote",

View File

@ -1,8 +1,8 @@
[package]
name = "chorus-macros"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
license = "AGPL-3.0"
license = "MPL-2.0"
description = "Macros for the chorus crate."
[lib]

View File

@ -1,7 +1,23 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed};
#[proc_macro_derive(WebSocketEvent)]
pub fn websocket_event_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
quote! {
impl WebSocketEvent for #name {}
}
.into()
}
#[proc_macro_derive(Updateable)]
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();

View File

@ -1,3 +1,16 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcase how to properly use gateway observers.
//
// To properly run it, you will need to change the token below.
const TOKEN: &str = "";
/// Find the gateway websocket url of the server we want to connect to
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
use async_trait::async_trait;
use chorus::gateway::Gateway;
use chorus::{
@ -8,6 +21,11 @@ use chorus::{
use std::{sync::Arc, time::Duration};
use tokio::{self};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
// This example creates a simple gateway connection and a basic observer struct
// Due to certain limitations all observers must impl debug
@ -27,11 +45,10 @@ impl Observer<GatewayReady> for ExampleObserver {
#[tokio::main(flavor = "current_thread")]
async fn main() {
// Find the gateway websocket url of the server we want to connect to
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
let gateway_websocket_url = GATEWAY_URL.to_string();
// Initiate the gateway connection
let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap();
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
// Create an instance of our observer
let observer = ExampleObserver {};
@ -50,14 +67,13 @@ async fn main() {
.subscribe(shared_observer);
// Authenticate so we will receive any events
let token = "SecretToken".to_string();
let token = TOKEN.to_string();
let mut identify = GatewayIdentifyPayload::common();
identify.token = token;
gateway.send_identify(identify).await;
safina_timer::start_timer_thread();
// Do something on the main thread so we don't quit
loop {
safina_timer::sleep_for(Duration::MAX).await
sleep(Duration::from_secs(3600)).await;
}
}

View File

@ -1,21 +1,39 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcases how to initiate a gateway connection manually
// (e. g. not through ChorusUser)
//
// To properly run it, you will need to modify the token below.
const TOKEN: &str = "";
/// Find the gateway websocket url of the server we want to connect to
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
use std::time::Duration;
use chorus::gateway::Gateway;
use chorus::{self, types::GatewayIdentifyPayload};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
/// This example creates a simple gateway connection and a session with an Identify event
#[tokio::main(flavor = "current_thread")]
async fn main() {
// Find the gateway websocket url of the server we want to connect to
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
let gateway_websocket_url = GATEWAY_URL.to_string();
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap();
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
// Get a token for an account on the server
let token = "SecretToken".to_string();
let token = TOKEN.to_string();
// Create an identify event
// An Identify event is how the server authenticates us and gets info about our os and browser, along with our intents / capabilities
@ -26,10 +44,10 @@ async fn main() {
identify.token = token;
// Send off the event
safina_timer::start_timer_thread();
gateway.send_identify(identify).await;
// Do something on the main thread so we don't quit
loop {
safina_timer::sleep_for(Duration::MAX).await
sleep(Duration::from_secs(3600)).await;
}
}

View File

@ -1,14 +1,12 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chorus::instance::Instance;
use chorus::UrlBundle;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let bundle = UrlBundle::new(
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let instance = Instance::new(bundle)
let instance = Instance::new("https://example.com/")
.await
.expect("Failed to connect to the Spacebar server");
dbg!(instance.instance_info);

View File

@ -1,15 +1,13 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chorus::instance::Instance;
use chorus::types::LoginSchema;
use chorus::UrlBundle;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let bundle = UrlBundle::new(
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let mut instance = Instance::new(bundle)
let mut instance = Instance::new("https://example.com/")
.await
.expect("Failed to connect to the Spacebar server");
// Assume, you already have an account created on this instance. Registering an account works
@ -19,7 +17,7 @@ async fn main() {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance
.login_account(login_schema)

View File

@ -0,0 +1,13 @@
[package]
name = "voice_simple"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = "*"
chorus = { path = "../../", features = ["rt", "client", "voice"] }
tokio = { version = "*", features = ["full"] }
simplelog = "*"
log = "*"

View File

@ -0,0 +1,311 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcases how to use the voice udp channel.
//
// To use this to properly communicate with voice, you will need to bring your own opus bindings
// along with potentially sending some other events, like Speaking
//
// To properly run this example, you will need to change some values below,
// like the token, guild and channel ids.
const TOKEN: &str = "";
const VOICE_GUILD_ID: Option<Snowflake> = None;
const VOICE_CHANNEL_ID: Option<Snowflake> = Some(Snowflake(0_u64));
const GATEWAY_URL: &str = "wss://gateway.discord.gg";
use async_trait::async_trait;
use simplelog::{TermLogger, Config, WriteLogger};
use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration};
use chorus::{
gateway::{Observer, Gateway},
types::{
GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking,
SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol,
VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState,
},
voice::{
gateway::{VoiceGateway, VoiceGatewayHandle},
udp::{UdpHandle, UdpHandler},
voice_data::VoiceData,
},
};
use log::{info, LevelFilter};
use tokio::sync::{Mutex, RwLock};
extern crate chorus;
extern crate tokio;
/// Handles in between connections between the gateway and UDP modules
#[derive(Debug, Clone)]
pub struct VoiceHandler {
pub voice_gateway_connection: Arc<Mutex<Option<VoiceGatewayHandle>>>,
pub voice_udp_connection: Arc<Mutex<Option<UdpHandle>>>,
pub data: Arc<RwLock<VoiceData>>,
}
impl VoiceHandler {
/// Creates a new [VoiceHandler], only initializing the data
pub fn new() -> VoiceHandler {
Self {
data: Arc::new(RwLock::new(VoiceData::default())),
voice_gateway_connection: Arc::new(Mutex::new(None)),
voice_udp_connection: Arc::new(Mutex::new(None)),
}
}
}
impl Default for VoiceHandler {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
// On [VoiceServerUpdate] we get our starting data and URL for the voice gateway server.
impl Observer<VoiceServerUpdate> for VoiceHandler {
async fn update(&self, data: &VoiceServerUpdate) {
let mut data_lock = self.data.write().await;
data_lock.server_data = Some(data.clone());
let user_id = data_lock.user_id;
let session_id = data_lock.session_id.clone();
drop(data_lock);
// Create and connect to the voice gateway
let voice_gateway_handle = VoiceGateway::spawn(data.endpoint.clone().unwrap())
.await
.unwrap();
let server_id: Snowflake;
if data.guild_id.is_some() {
server_id = data.guild_id.unwrap();
} else {
server_id = data.channel_id.unwrap();
}
let voice_identify = VoiceIdentify {
server_id,
user_id,
session_id,
token: data.token.clone(),
video: Some(false),
};
voice_gateway_handle.send_identify(voice_identify).await;
let cloned_gateway_handle = voice_gateway_handle.clone();
let mut voice_events = cloned_gateway_handle.events.lock().await;
let self_reference = Arc::new(self.clone());
// Subscribe to voice gateway events
voice_events.voice_ready.subscribe(self_reference.clone());
voice_events
.session_description
.subscribe(self_reference.clone());
voice_events.speaking.subscribe(self_reference.clone());
voice_events
.ssrc_definition
.subscribe(self_reference.clone());
*self.voice_gateway_connection.lock().await = Some(voice_gateway_handle);
}
}
#[async_trait]
// On [VoiceReady] we get info for establishing a UDP connection, and we immediately need said UDP
// connection for ip discovery.
impl Observer<VoiceReady> for VoiceHandler {
async fn update(&self, data: &VoiceReady) {
let mut data_lock = self.data.write().await;
data_lock.ready_data = Some(data.clone());
drop(data_lock);
// Create a udp connection and perform ip discovery
let udp_handle = UdpHandler::spawn(
self.data.clone(),
std::net::SocketAddr::V4(SocketAddrV4::new(data.ip, data.port)),
data.ssrc,
)
.await
.unwrap();
// Subscribe ourself to receiving rtp data
udp_handle
.events
.lock()
.await
.rtp
.subscribe(Arc::new(self.clone()));
let ip_discovery = self.data.read().await.ip_discovery.clone().unwrap();
*self.voice_udp_connection.lock().await = Some(udp_handle.clone());
let string_ip_address =
String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip");
// Send a select protocol, which tells the server where we'll be receiving data and what
// mode to encrypt data in
self.voice_gateway_connection
.lock()
.await
.clone()
.unwrap()
.send_select_protocol(SelectProtocol {
protocol: VoiceProtocol::Udp,
data: SelectProtocolData {
address: string_ip_address,
port: ip_discovery.port,
// There are several other voice encryption modes available, though not all are
// implemented in chorus
mode: VoiceEncryptionMode::Xsalsa20Poly1305,
},
..Default::default()
})
.await;
}
}
#[async_trait]
// Session descryption gives us final info regarding codecs and our encryption key
impl Observer<SessionDescription> for VoiceHandler {
async fn update(&self, data: &SessionDescription) {
let mut data_write = self.data.write().await;
data_write.session_description = Some(data.clone());
drop(data_write);
}
}
#[async_trait]
// Ready is used just to obtain some info, like the user id and session id
impl Observer<GatewayReady> for VoiceHandler {
async fn update(&self, data: &GatewayReady) {
let mut lock = self.data.write().await;
lock.user_id = data.user.id;
lock.session_id = data.session_id.clone();
drop(lock);
}
}
#[async_trait]
// This is the received voice data
impl Observer<chorus::voice::discortp::rtp::Rtp> for VoiceHandler {
async fn update(&self, data: &chorus::voice::discortp::rtp::Rtp) {
info!(
"Received decrypted voice data! {:?} (SSRC: {})",
data.payload.clone(),
data.ssrc,
);
}
}
#[async_trait]
// This event gives extra info about who is speaking
impl Observer<Speaking> for VoiceHandler {
async fn update(&self, data: &Speaking) {
println!(
"Received Speaking! (SRRC: {}, flags: {:?})",
data.ssrc,
SpeakingBitflags::from_bits(data.speaking).unwrap()
);
}
}
#[async_trait]
// This event gives some info about which user has which ssrc
impl Observer<SsrcDefinition> for VoiceHandler {
async fn update(&self, data: &SsrcDefinition) {
println!(
"Received SSRC Definition! (User {} has audio ssrc {})",
data.user_id.unwrap(),
data.audio_ssrc
);
}
}
#[tokio::main]
async fn main() {
simplelog::CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Debug,
Config::default(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
),
WriteLogger::new(
LevelFilter::Trace,
Config::default(),
File::create("latest.log").unwrap(),
),
])
.unwrap();
let gateway = Gateway::spawn(GATEWAY_URL.to_string())
.await
.unwrap();
let mut identify = GatewayIdentifyPayload::common();
identify.token = TOKEN.to_string();
gateway.send_identify(identify).await;
let voice_handler = Arc::new(VoiceHandler::new());
// Voice handler needs voice server update
gateway
.events
.lock()
.await
.voice
.server_update
.subscribe(voice_handler.clone());
// It also needs a bit of the data in ready
gateway
.events
.lock()
.await
.session
.ready
.subscribe(voice_handler.clone());
// Data which channel to update the local user to be joined into.
//
// guild_id and channel_id can be some to join guild voice channels
//
// guild_id can be none and channel id some to join dm calls
//
// both can be none to leave all voice channels
let voice_state_update = UpdateVoiceState {
guild_id: VOICE_GUILD_ID,
channel_id: VOICE_CHANNEL_ID,
self_mute: false,
self_deaf: false,
..Default::default()
};
gateway.send_update_voice_state(voice_state_update).await;
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
// Potentially send some data here
/*let voice_udp_option = voice_handler.voice_udp_connection.lock().await.clone();
if voice_udp_option.is_some() {
voice_udp_option.unwrap().send_opus_data(0, vec![1, 2, 3, 4, 5]).await.unwrap();
}*/
}
}

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use reqwest::Client;

View File

@ -1,6 +1,13 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
#[allow(unused_imports)]
pub use login::*;
#[allow(unused_imports)]
pub use register::*;
use crate::gateway::Gateway;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use reqwest::Client;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use http::header::CONTENT_DISPOSITION;
use http::HeaderMap;
use reqwest::{multipart, Client};
@ -36,7 +40,7 @@ impl Message {
chorus_request.deserialize_response::<Message>(user).await
} else {
for (index, attachment) in message.attachments.iter_mut().enumerate() {
attachment.get_mut(index).unwrap().set_id(index as i16);
attachment.get_mut(index).unwrap().id = Some(index as i16);
}
let mut form = reqwest::multipart::Form::new();
let payload_json = to_string(&message).unwrap();
@ -45,8 +49,8 @@ impl Message {
form = form.part("payload_json", payload_field);
for (index, attachment) in message.attachments.unwrap().into_iter().enumerate() {
let (attachment_content, current_attachment) = attachment.move_content();
let (attachment_filename, _) = current_attachment.move_filename();
let attachment_content = attachment.content;
let attachment_filename = attachment.filename;
let part_name = format!("files[{}]", index);
let content_disposition = format!(
"form-data; name=\"{}\"'; filename=\"{}\"",

View File

@ -1,3 +1,8 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use channels::*;
pub use messages::*;
pub use permissions::*;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::{
errors::ChorusResult,
instance::ChorusUser,

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::from_str;
use serde_json::to_string;
@ -14,6 +18,25 @@ use crate::types::{
use crate::types::{GuildBan, Snowflake};
impl Guild {
/// Fetches a guild by its id.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild>
pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id
))
.header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Creates a new guild.
///
/// # Reference
@ -34,6 +57,35 @@ impl Guild {
chorus_request.deserialize_response::<Guild>(user).await
}
/// Modify a guild's settings.
///
/// Requires the [MANAGE_GUILD](crate::types::PermissionFlags::MANAGE_GUILD) permission.
///
/// Returns the updated guild.
///
/// # Reference
/// <https://discord-userdoccers.vercel.app/resources/guild#modify-guild>
pub async fn modify(
guild_id: Snowflake,
schema: GuildModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Deletes a guild by its id.
///
/// User must be the owner.
@ -123,77 +175,9 @@ impl Guild {
};
}
/// Fetches a guild by its id.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild>
pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id
))
.header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
pub async fn create_ban(
guild_id: Snowflake,
user_id: Snowflake,
audit_log_reason: Option<String>,
schema: GuildBanCreateSchema,
user: &mut ChorusUser,
) -> ChorusResult<()> {
// FIXME: Return GuildBan instead of (). Requires <https://github.com/spacebarchat/server/issues/1096> to be resolved.
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
/// # Reference
/// <https://discord-userdoccers.vercel.app/resources/guild#modify-guild>
pub async fn modify(
guild_id: Snowflake,
schema: GuildModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Fetches a guild preview object for the given guild ID. If the user is not in the guild, the guild must be discoverable.
/// # Reference:
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-preview>
// RAGC: These aren't just getters, they fetch something on the server.
// I am going to excuse these namings, since .preview() indicates we are just returning
@ -273,7 +257,9 @@ impl Guild {
request.deserialize_response::<Vec<GuildMember>>(user).await
}
/// Removes a member from a guild. Requires the KICK_MEMBERS permission. Returns a 204 empty response on success.
/// Removes a member from a guild.
///
/// Requires the [KICK_MEMBERS](crate::types::PermissionFlags::KICK_MEMBERS) permission.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#remove-guild-member>
@ -386,7 +372,9 @@ impl Guild {
.await
}
/// Fetches a list of ban objects for the guild. Requires the `BAN_MEMBERS` permission.
/// Returns a list of ban objects for the guild.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-bans>
@ -416,7 +404,9 @@ impl Guild {
request.deserialize_response::<Vec<GuildBan>>(user).await
}
/// Fetches a ban object for the given user. Requires the `BAN_MEMBERS` permission.
/// Returns a ban object for the given user.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-ban>
@ -444,7 +434,39 @@ impl Guild {
request.deserialize_response::<GuildBan>(user).await
}
/// Removes the ban for a user. Requires the BAN_MEMBERS permissions. Returns a 204 empty response on success.
/// Creates a ban from the guild.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
///
pub async fn create_ban(
guild_id: Snowflake,
user_id: Snowflake,
audit_log_reason: Option<String>,
schema: GuildBanCreateSchema,
user: &mut ChorusUser,
) -> ChorusResult<()> {
// FIXME: Return GuildBan instead of (). Requires <https://github.com/spacebarchat/server/issues/1096> to be resolved.
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
/// Removes the ban for a user.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#delete-guild-ban>

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use crate::{

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::errors::ChorusResult;
use crate::instance::ChorusUser;
use crate::types::{Guild, Message, MessageSearchQuery, Snowflake};
@ -9,7 +13,7 @@ impl Guild {
/// permission to be present on the current user.
///
/// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response.
/// In this case, the method will return a [`ChorusError::InvalidResponse`] error.
/// In this case, the method will return a [`ChorusError::InvalidResponse`](crate::errors::ChorusError::InvalidResponse) error.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#search-messages>

View File

@ -1,3 +1,8 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use guilds::*;
pub use messages::*;
pub use roles::*;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,4 +1,10 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! All of the API's endpoints.
#![allow(unused_imports)]
pub use channels::messages::*;
pub use guilds::*;
pub use invites::*;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde_json::from_str;
use crate::errors::{ChorusError, ChorusResult};

View File

@ -1,3 +1,8 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use instance::*;
pub mod instance;

View File

@ -1 +1,5 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod instance;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;
@ -9,6 +13,26 @@ use crate::{
};
impl ChorusUser {
/// Fetches a list of private channels the user is in.
///
/// # Reference:
/// See <https://docs.discord.sex/resources/channel#get-private-channels>
pub async fn get_private_channels(&mut self) -> ChorusResult<Vec<Channel>> {
let url = format!(
"{}/users/@me/channels",
self.belongs_to.read().unwrap().urls.api
);
ChorusRequest {
request: Client::new()
.get(url)
.header("Authorization", self.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Global,
}
.deserialize_response::<Vec<Channel>>(self)
.await
}
/// Creates a DM channel or group DM channel.
///
/// One recipient creates or returns an existing DM channel,

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,3 +1,8 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use channels::*;
pub use guilds::*;
pub use relationships::*;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;
@ -15,7 +19,7 @@ impl ChorusUser {
/// Retrieves a list of mutual friends between the authenticated user and a given user.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#get-users-peer-id-relationships>
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#get-userspeer_idrelationships>
pub async fn get_mutual_relationships(
&mut self,
user_id: Snowflake,
@ -37,7 +41,7 @@ impl ChorusUser {
/// Retrieves the user's relationships.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#get-users-me-relationships>
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#get-usersmerelationships>
pub async fn get_relationships(&mut self) -> ChorusResult<Vec<types::Relationship>> {
let url = format!(
"{}/users/@me/relationships",
@ -55,7 +59,7 @@ impl ChorusUser {
/// Sends a friend request to a user.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#post-users-me-relationships>
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#post-usersmerelationships>
pub async fn send_friend_request(
&mut self,
schema: FriendRequestSendSchema,
@ -132,7 +136,7 @@ impl ChorusUser {
/// Removes the relationship between the authenticated user and a given user.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#delete-users-me-relationships-peer-id>
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#delete-usersmerelationshipspeer_id>
pub async fn remove_relationship(&mut self, user_id: Snowflake) -> ChorusResult<()> {
let url = format!(
"{}/users/@me/relationships/{}",

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use reqwest::Client;
@ -113,7 +117,7 @@ impl User {
/// Fetches the user's settings.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/user_settings.html#get-users-me-settings>
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/user_settings.html#get-usersmesettings>
pub async fn get_settings(
token: &String,
url_api: &String,

View File

@ -1,12 +1,17 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! Contains all the errors that can be returned by the library.
use custom_error::custom_error;
use crate::types::WebSocketEvent;
use chorus_macros::WebSocketEvent;
custom_error! {
#[derive(PartialEq, Eq, Clone, Hash)]
pub RegistrationError
Consent = "Consent must be 'true' to register.",
Consent = "consent must be 'true' to register",
}
pub type ChorusResult<T> = std::result::Result<T, ChorusError>;
@ -15,33 +20,34 @@ custom_error! {
#[derive(Clone, Hash, PartialEq, Eq)]
pub ChorusError
/// Server did not respond.
NoResponse = "Did not receive a response from the Server.",
NoResponse = "server did not respond",
/// Reqwest returned an Error instead of a Response object.
RequestFailed{url:String, error: String} = "An error occured while trying to GET from {url}: {error}",
RequestFailed{url:String, error: String} = "an error occurred while trying to GET from {url}: {error}",
/// Response received, however, it was not of the successful responses type. Used when no other, special case applies.
ReceivedErrorCode{error_code: u16, error: String} = "Received the following error code while requesting from the route: {error_code}",
ReceivedErrorCode{error_code: u16, error: String} = "received error code while requesting from the route: {error_code}",
/// Used when there is likely something wrong with the instance, the request was directed to.
CantGetInformation{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}",
CantGetInformation{error:String} = "cannot get information about the instance: {error}, something is likely wrong with the instance",
/// The requests form body was malformed/invalid.
InvalidFormBody{error_type: String, error:String} = "The server responded with: {error_type}: {error}",
InvalidFormBody{error_type: String, error:String} = "the server responded with: {error_type}: {error}",
/// The request has not been processed by the server due to a relevant rate limit bucket being exhausted.
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
RateLimited{bucket:String} = "ratelimited on bucket {bucket}",
/// The multipart form could not be created.
MultipartCreation{error: String} = "Got an error whilst creating the form: {error}",
MultipartCreation{error: String} = "got an error whilst creating the form: {error}",
/// The regular form could not be created.
FormCreation{error: String} = "Got an error whilst creating the form: {error}",
FormCreation{error: String} = "got an error whilst creating the form: {error}",
/// The token is invalid.
TokenExpired = "Token expired, invalid or not found.",
TokenExpired = "token expired, invalid or not found",
/// No permission
NoPermission = "You do not have the permissions needed to perform this action.",
NoPermission = "you lack the permissions needed to perform this action",
/// Resource not found
NotFound{error: String} = "The provided resource hasn't been found: {error}",
NotFound{error: String} = "the provided resource wasn't found: {error}",
/// Used when you, for example, try to change your spacebar account password without providing your old password for verification.
PasswordRequired = "You need to provide your current password to authenticate for this action.",
// RAGC: could this be worded a bit better to be more concise?
PasswordRequired = "you need to provide your current password to authenticate for this action",
/// Malformed or unexpected response.
InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}",
InvalidResponse{error: String} = "the response is malformed and cannot be processed: {error}",
/// Invalid, insufficient or too many arguments provided.
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}"
InvalidArguments{error: String} = "invalid arguments were provided: {error}"
}
impl From<reqwest::Error> for ChorusError {
@ -57,42 +63,119 @@ impl From<reqwest::Error> for ChorusError {
}
custom_error! {
#[derive(PartialEq, Eq)]
pub ObserverError
AlreadySubscribed = "Each event can only be subscribed to once."
}
custom_error! {
/// For errors we receive from the gateway, see https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#gateway-close-event-codes;
/// For errors we receive from the gateway, see <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#gateway-close-event-codes>;
///
/// 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]
Unknown = "We're not sure what went wrong. Try reconnecting?",
UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode",
Decode = "Gateway server couldn't decode payload",
NotAuthenticated = "You sent a payload prior to identifying",
AuthenticationFailed = "The account token sent with your identify payload is invalid",
AlreadyAuthenticated = "You've already identified, no need to reauthenticate",
InvalidSequenceNumber = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session",
RateLimited = "You are being rate limited!",
SessionTimedOut = "Your session timed out. Reconnect and start a new one",
InvalidShard = "You sent us an invalid shard when identifying",
ShardingRequired = "The session would have handled too many guilds - you are required to shard your connection in order to connect",
InvalidAPIVersion = "You sent an invalid Gateway version",
InvalidIntents = "You sent an invalid intent",
DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
/// We're not sure what went wrong. Try reconnecting?
Unknown = "unknown error occurred, try reconnecting",
/// You sent an invalid opcode or an invalid payload for an opcode
UnknownOpcode = "client sent invalid opcode or invalid payload for opcode",
/// Gateway server couldn't decode payload
Decode = "gateway server failed to decode payload",
/// You sent a payload prior to identifying
NotAuthenticated = "client sent payload before identifying",
/// The account token sent with your identify payload is invalid
AuthenticationFailed = "account token in identify is invalid",
/// You've already identified, no need to reauthenticate
AlreadyAuthenticated = "client sent more than one identify payload",
/// The sequence number sent when resuming the session was invalid. Reconnect and start a new session
InvalidSequenceNumber = "sequence number when resuming session was invalid.",
/// You're being rate limited
RateLimited = "you are being rate limited",
/// Your session timed out. Reconnect and start a new one
SessionTimedOut = "session timed out",
/// You sent an invalid shard when identifying
InvalidShard = "invalid shard in identify",
/// The session would have handled too many guilds - you are required to shard your connection in order to connect
ShardingRequired = "sharding is required to connect",
/// You sent an invalid Gateway version
InvalidAPIVersion = "client sent invalid gateway version",
/// You sent an invalid intent
InvalidIntents = "invalid intent",
/// You sent a disallowed intent.
///
/// You may have tried to specify an intent that you have not enabled or are not approved for
DisallowedIntents = "disallowed (not enabled / approved) intent",
// Errors when initiating a gateway connection
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}",
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
CannotConnect{error: String} = "encountered a tungstenite error: {error}",
NonHelloOnInitiate{opcode: u8} = "received non hello on initializing connection: {opcode}",
// Other misc errors
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
UnexpectedOpcodeReceived{opcode: u8} = "unexpected opcode received: {opcode}",
}
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, WebSocketEvent)]
pub VoiceGatewayError
// Errors we receive
#[default]
/// You sent an invalid opcode
UnknownOpcode = "client sent invalid opcode",
/// You sent an invalid payload in your identifying to the (Voice) Gateway
FailedToDecodePayload = "server failed to decode payload while identifying",
/// You sent a payload before identifying with the (Voice) Gateway
NotAuthenticated = "client sent payload before identifying",
/// The token you sent in your identify payload is incorrect
AuthenticationFailed = "account token in identify is invalid",
/// You sent more than one identify payload
AlreadyAuthenticated = "client sent more than one identify payload",
/// Your session is no longer valid
SessionNoLongerValid = "session no longer valid",
/// Your session has timed out
SessionTimeout = "session timed out",
/// Can't find the desired server to connect to
ServerNotFound = "desired server not found",
/// The server didn't recognize the protocol you sent
UnknownProtocol = "unrecognized or unknown protocol",
/// Channel was deleted, you were kicked, voice server changed, or the main gateway session
/// closed.
///
/// Should not attempt to reconnect.
Disconnected = "disconnected from voice",
/// The server crashed, try resuming
VoiceServerCrashed = "the voice server crashed",
/// Server failed to decrypt data
UnknownEncryptionMode = "server failed to decrypt / unknown encryption mode",
// Errors when initiating a gateway connection
CannotConnect{error: String} = "encountered a tungstenite error: {error}",
NonHelloOnInitiate{opcode: u8} = "received non hello on initializing connection: {opcode}",
// Other misc errors
UnexpectedOpcodeReceived{opcode: u8} = "unexpected opcode received: {opcode}",
}
custom_error! {
/// Voice UDP errors.
#[derive(Clone, PartialEq, Eq, WebSocketEvent)]
pub VoiceUdpError
// General errors
BrokenSocket{error: String} = "Could not write / read from UDP socket: {error}",
/// We have not yet received the necessary data to perform this operation.
NoData = "required data not yet received",
// Encryption errors
EncryptionModeNotImplemented{encryption_mode: String} = "voice encryption mode {encryption_mode} is not yet implemented",
NoKey = "could not encrypt / decrypt data: no key received yet",
FailedEncryption = "failed to encrypt data (most likely this is an issue in chorus' nonce generation, please open an issue)",
FailedDecryption = "failed to decrypt data (most likely this is an issue in chorus' nonce generation, please open an issue)",
FailedNonceGeneration{error: String} = "failed to generate nonce: {error}.",
// Errors when initiating a socket connection
CannotBind{error: String} = "failed to bind UDP socket: {error}",
CannotConnect{error: String} = "failed to open UDP connection: {error}",
}
impl WebSocketEvent for GatewayError {}

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub mod tungstenite;
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::{
stream::{SplitSink, SplitStream},
StreamExt,
@ -23,8 +27,16 @@ impl TungsteniteBackend {
websocket_url: &str,
) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> {
let mut roots = rustls::RootCertStore::empty();
for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs")
{
let certs = rustls_native_certs::load_native_certs();
if let Err(e) = certs {
log::error!("Failed to load platform native certs! {:?}", e);
return Err(GatewayError::CannotConnect {
error: format!("{:?}", e),
});
}
for cert in certs.unwrap() {
roots.add(&rustls::Certificate(cert.0)).unwrap();
}
let (websocket_stream, _) = match connect_async_tls_with_config(

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::{
stream::{SplitSink, SplitStream},
StreamExt,

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use super::*;
use crate::types;
@ -42,6 +46,8 @@ pub struct Session {
pub ready: GatewayEvent<types::GatewayReady>,
pub ready_supplemental: GatewayEvent<types::GatewayReadySupplemental>,
pub replace: GatewayEvent<types::SessionsReplace>,
pub reconnect: GatewayEvent<types::GatewayReconnect>,
pub invalid: GatewayEvent<types::GatewayInvalidSession>,
}
#[derive(Default, Debug)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::time::Duration;
use futures_util::{SinkExt, StreamExt};
@ -5,13 +9,14 @@ use log::*;
#[cfg(not(target_arch = "wasm32"))]
use tokio::task;
use self::event::Events;
use super::events::Events;
use super::*;
use super::{Sink, Stream};
use crate::types::{
self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete,
ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField,
ThreadUpdate, UpdateMessage, WebSocketEvent,
ChannelUpdate, GatewayInvalidSession, GatewayReconnect, Guild, GuildRoleCreate,
GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, ThreadUpdate, UpdateMessage,
WebSocketEvent,
};
#[derive(Debug)]
@ -21,6 +26,7 @@ pub struct Gateway {
websocket_send: Arc<Mutex<Sink>>,
websocket_receive: Stream,
kill_send: tokio::sync::broadcast::Sender<()>,
kill_receive: tokio::sync::broadcast::Receiver<()>,
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
url: String,
}
@ -70,6 +76,7 @@ impl Gateway {
websocket_send: shared_websocket_send.clone(),
websocket_receive,
kill_send: kill_send.clone(),
kill_receive: kill_send.subscribe(),
store: store.clone(),
url: websocket_url.clone(),
};
@ -94,14 +101,21 @@ impl Gateway {
}
/// The main gateway listener task;
///
/// Can only be stopped by closing the websocket, cannot be made to listen for kill
pub async fn gateway_listen_task(&mut self) {
loop {
let msg = self.websocket_receive.next().await;
let msg;
tokio::select! {
Ok(_) = self.kill_receive.recv() => {
log::trace!("GW: Closing listener task");
break;
}
message = self.websocket_receive.next() => {
msg = message;
}
}
// PRETTYFYME: Remove inline conditional compiling
// This if chain can be much better but if let is unstable on stable rust
#[cfg(not(target_arch = "wasm32"))]
if let Some(Ok(message)) = msg {
self.handle_message(message.into()).await;
@ -335,10 +349,42 @@ impl Gateway {
.unwrap();
}
GATEWAY_RECONNECT => {
todo!()
trace!("GW: Received Reconnect");
let reconnect = GatewayReconnect {};
self.events
.lock()
.await
.session
.reconnect
.notify(reconnect)
.await;
}
GATEWAY_INVALID_SESSION => {
todo!()
trace!("GW: Received Invalid Session");
let mut resumable: bool = false;
if let Some(raw_value) = gateway_payload.event_data {
if let Ok(deserialized) = serde_json::from_str(raw_value.get()) {
resumable = deserialized;
} else {
warn!("Failed to parse part of INVALID_SESSION ('{}' as bool), assuming non-resumable", raw_value.get());
}
} else {
warn!("Failed to parse part of INVALID_SESSION ('d' missing), assuming non-resumable");
}
let invalid_session = GatewayInvalidSession { resumable };
self.events
.lock()
.await
.session
.invalid
.notify(invalid_session)
.await;
}
// Starts our heartbeat
// We should have already handled this in gateway init
@ -394,165 +440,3 @@ impl Gateway {
}
}
}
pub mod event {
use super::*;
#[derive(Default, Debug)]
pub struct Events {
pub application: Application,
pub auto_moderation: AutoModeration,
pub session: Session,
pub message: Message,
pub user: User,
pub relationship: Relationship,
pub channel: Channel,
pub thread: Thread,
pub guild: Guild,
pub invite: Invite,
pub integration: Integration,
pub interaction: Interaction,
pub stage_instance: StageInstance,
pub call: Call,
pub voice: Voice,
pub webhooks: Webhooks,
pub gateway_identify_payload: GatewayEvent<types::GatewayIdentifyPayload>,
pub gateway_resume: GatewayEvent<types::GatewayResume>,
pub error: GatewayEvent<GatewayError>,
}
#[derive(Default, Debug)]
pub struct Application {
pub command_permissions_update: GatewayEvent<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>,
}
#[derive(Default, Debug)]
pub struct Session {
pub ready: GatewayEvent<types::GatewayReady>,
pub ready_supplemental: GatewayEvent<types::GatewayReadySupplemental>,
pub replace: GatewayEvent<types::SessionsReplace>,
}
#[derive(Default, Debug)]
pub struct StageInstance {
pub create: GatewayEvent<types::StageInstanceCreate>,
pub update: GatewayEvent<types::StageInstanceUpdate>,
pub delete: GatewayEvent<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>,
}
#[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>,
}
#[derive(Default, Debug)]
pub struct Relationship {
pub add: GatewayEvent<types::RelationshipAdd>,
pub remove: GatewayEvent<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>,
}
#[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>,
}
#[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>,
}
#[derive(Default, Debug)]
pub struct Invite {
pub create: GatewayEvent<types::InviteCreate>,
pub delete: GatewayEvent<types::InviteDelete>,
}
#[derive(Default, Debug)]
pub struct Integration {
pub create: GatewayEvent<types::IntegrationCreate>,
pub update: GatewayEvent<types::IntegrationUpdate>,
pub delete: GatewayEvent<types::IntegrationDelete>,
}
#[derive(Default, Debug)]
pub struct Interaction {
pub create: GatewayEvent<types::InteractionCreate>,
}
#[derive(Default, Debug)]
pub struct Call {
pub create: GatewayEvent<types::CallCreate>,
pub update: GatewayEvent<types::CallUpdate>,
pub delete: GatewayEvent<types::CallDelete>,
}
#[derive(Default, Debug)]
pub struct Voice {
pub state_update: GatewayEvent<types::VoiceStateUpdate>,
pub server_update: GatewayEvent<types::VoiceServerUpdate>,
}
#[derive(Default, Debug)]
pub struct Webhooks {
pub update: GatewayEvent<types::WebhooksUpdate>,
}
}

View File

@ -1,9 +1,13 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::SinkExt;
use log::*;
use std::fmt::Debug;
use super::{event::Events, *};
use super::{events::Events, *};
use crate::types::{self, Composite};
/// Represents a handle to a Gateway connection. A Gateway connection will create observable
@ -40,10 +44,19 @@ impl GatewayHandle {
.unwrap();
}
/// Recursively observes a [`Shared`] object, by making sure all [`Composite `] fields within
/// that object and its children are being watched.
///
/// Observing means, that if new information arrives about the observed object or its children,
/// the object automatically gets updated, without you needing to request new information about
/// the object in question from the API, which is expensive and can lead to rate limiting.
///
/// The [`Shared`] object returned by this method points to a different object than the one
/// being supplied as a &self function argument.
pub async fn observe<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
) -> Arc<RwLock<T>> {
object: Shared<T>,
) -> Shared<T> {
let mut store = self.store.lock().await;
let id = object.read().unwrap().id();
if let Some(channel) = store.get(&id) {
@ -84,7 +97,7 @@ impl GatewayHandle {
/// with all of its observable fields being observed.
pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
object: Shared<T>,
) -> T {
let channel = self.observe(object.clone()).await;
let object = channel.read().unwrap().clone();
@ -160,7 +173,7 @@ impl GatewayHandle {
/// Closes the websocket connection and stops all gateway tasks;
///
/// Esentially pulls the plug on the gateway, leaving it possible to resume;
/// Essentially pulls the plug on the gateway, leaving it possible to resume;
pub async fn close(&self) {
self.kill_send.send(()).unwrap();
self.websocket_send.lock().await.close().await.unwrap();

View File

@ -1,9 +1,24 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::SinkExt;
use log::*;
use std::time::{self, Duration, Instant};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(target_arch = "wasm32")]
use wasmtimer::std::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep_until;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep_until;
use std::time::Duration;
use tokio::sync::mpsc::{Receiver, Sender};
use safina_timer::sleep_until;
#[cfg(not(target_arch = "wasm32"))]
use tokio::task;
@ -11,7 +26,7 @@ use super::*;
use crate::types;
/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms
const HEARTBEAT_ACK_TIMEOUT: u64 = 2000;
pub const HEARTBEAT_ACK_TIMEOUT: u64 = 2000;
/// Handles sending heartbeats to the gateway in another thread
#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used
@ -57,18 +72,11 @@ impl HeartbeatHandler {
mut receive: Receiver<HeartbeatThreadCommunication>,
mut kill_receive: tokio::sync::broadcast::Receiver<()>,
) {
let mut last_heartbeat_timestamp: Instant = time::Instant::now();
let mut last_heartbeat_timestamp: Instant = Instant::now();
let mut last_heartbeat_acknowledged = true;
let mut last_seq_number: Option<u64> = None;
safina_timer::start_timer_thread();
loop {
if kill_receive.try_recv().is_ok() {
trace!("GW: Closing heartbeat task");
break;
}
let timeout = if last_heartbeat_acknowledged {
heartbeat_interval
} else {
@ -102,6 +110,10 @@ impl HeartbeatHandler {
}
}
}
Ok(_) = kill_receive.recv() => {
log::trace!("GW: Closing heartbeat task");
break;
}
}
if should_send {
@ -119,11 +131,11 @@ impl HeartbeatHandler {
let send_result = websocket_tx.lock().await.send(msg.into()).await;
if send_result.is_err() {
// We couldn't send, the websocket is broken
warn!("GW: Couldnt send heartbeat, websocket seems broken");
warn!("GW: Couldn't send heartbeat, websocket seems broken");
break;
}
last_heartbeat_timestamp = time::Instant::now();
last_heartbeat_timestamp = Instant::now();
last_heartbeat_acknowledged = false;
}
}

View File

@ -1,15 +1,19 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types;
use super::*;
/// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
/// This struct is used internally when handling messages.
#[derive(Clone, Debug)]
pub struct GatewayMessage(pub String);
impl GatewayMessage {
/// Parses the message as an error;
/// Returns the error if succesfully parsed, None if the message isn't an error
/// Returns the error if successfully parsed, None if the message isn't an error
pub fn error(&self) -> Option<GatewayError> {
// Some error strings have dots on the end, which we don't care about
let processed_content = self.0.to_lowercase().replace('.', "");

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use async_trait::async_trait;
pub mod backends;
@ -94,6 +98,12 @@ pub struct GatewayEvent<T: WebSocketEvent> {
}
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()
@ -116,9 +126,17 @@ impl<T: WebSocketEvent> GatewayEvent<T> {
}
/// Notifies the observers of the GatewayEvent.
async fn notify(&self, new_event_data: T) {
pub(crate) async fn notify(&self, new_event_data: T) {
for observer in &self.observers {
observer.update(&new_event_data).await;
}
}
}
/// A type alias for [`Arc<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>>;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! Instance and ChorusUser objects.
use std::collections::HashMap;
@ -9,7 +13,7 @@ use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::errors::ChorusResult;
use crate::gateway::{Gateway, GatewayHandle};
use crate::gateway::{Gateway, GatewayHandle, Shared};
use crate::ratelimiter::ChorusRequest;
use crate::types::types::subconfigs::limits::rates::RateLimits;
use crate::types::{
@ -19,6 +23,7 @@ use crate::UrlBundle;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server.
///
/// If `limits_information` is `None`, then the instance will not be rate limited.
pub struct Instance {
pub urls: UrlBundle,
@ -36,8 +41,6 @@ impl PartialEq for Instance {
}
}
impl Eq for Instance {}
impl std::hash::Hash for Instance {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.urls.hash(state);
@ -92,8 +95,17 @@ impl PartialEq for LimitsInformation {
}
impl Instance {
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`].
pub async fn new(urls: UrlBundle) -> ChorusResult<Instance> {
pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> {
if self.limits_information.is_some() {
return Some(self.limits_information.as_ref().unwrap().ratelimits.clone());
}
None
}
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
///
/// To create an Instance from one singular url, use [`Instance::new()`].
pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> {
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
let limit_information;
@ -123,21 +135,12 @@ impl Instance {
Ok(instance)
}
pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> {
if self.limits_information.is_some() {
return Some(self.limits_information.as_ref().unwrap().ratelimits.clone());
}
None
}
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
/// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`.
// RAGC: Can we really call this a conversion?
// Would with_root_url be better? Not really I think, because with is for more details
// Where are this is with.. less? (or rather with other ones)
pub async fn from_root_url(root_url: &str) -> ChorusResult<Instance> {
///
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
pub async fn new(root_url: &str) -> ChorusResult<Instance> {
let urls = UrlBundle::from_root_url(root_url).await?;
Instance::new(urls).await
Instance::from_url_bundle(urls).await
}
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
@ -174,11 +177,11 @@ impl fmt::Display for Token {
/// It is used for most authenticated actions on a Spacebar server.
/// It also has its own [Gateway] connection.
pub struct ChorusUser {
pub belongs_to: Arc<RwLock<Instance>>,
pub belongs_to: Shared<Instance>,
pub token: String,
pub limits: Option<HashMap<LimitType, Limit>>,
pub settings: Arc<RwLock<UserSettings>>,
pub object: Arc<RwLock<User>>,
pub settings: Shared<UserSettings>,
pub object: Shared<User>,
pub gateway: GatewayHandle,
}
@ -233,14 +236,14 @@ impl ChorusUser {
/// Creates a new [ChorusUser] from existing data.
///
/// # Notes
/// This isn't the prefered way to create a ChorusUser.
/// This isn't the preferred way to create a ChorusUser.
/// See [Instance::login_account] and [Instance::register_account] instead.
pub fn new(
belongs_to: Arc<RwLock<Instance>>,
belongs_to: Shared<Instance>,
token: String,
limits: Option<HashMap<LimitType, Limit>>,
settings: Arc<RwLock<UserSettings>>,
object: Arc<RwLock<User>>,
settings: Shared<UserSettings>,
object: Shared<User>,
gateway: GatewayHandle,
) -> ChorusUser {
ChorusUser {
@ -258,7 +261,7 @@ impl ChorusUser {
/// registering or logging in to the Instance, where you do not yet have a User object, but still
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
/// first.
pub(crate) async fn shell(instance: Arc<RwLock<Instance>>, token: String) -> ChorusUser {
pub(crate) async fn shell(instance: Shared<Instance>, token: String) -> ChorusUser {
let settings = Arc::new(RwLock::new(UserSettings::default()));
let object = Arc::new(RwLock::new(User::default()));
let wss_url = instance.read().unwrap().urls.wss.clone();

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*!
Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
@ -44,7 +48,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance
.login_account(login_schema)
@ -128,7 +132,10 @@ pub mod instance;
#[cfg(feature = "client")]
pub mod ratelimiter;
pub mod types;
#[cfg(feature = "client")]
#[cfg(all(
feature = "client",
any(feature = "voice_udp", feature = "voice_gateway")
))]
pub mod voice;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -137,6 +144,12 @@ pub mod voice;
/// # Notes
/// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server
pub struct UrlBundle {
/// The root url of an Instance. Usually, this would be the url where `.well-known/spacebar` can
/// be located under. If the instance you are connecting to for some reason does not have a
/// `.well-known` set up (for example, if it is a local/testing instance), you can use the api
/// url as a substitute.
/// Ex: `https://spacebar.chat`
pub root: String,
/// The api's url.
/// Ex: `https://old.server.spacebar.chat/api`
pub api: String,
@ -151,8 +164,9 @@ pub struct UrlBundle {
impl UrlBundle {
/// Creates a new UrlBundle from the relevant urls.
pub fn new(api: String, wss: String, cdn: String) -> Self {
pub fn new(root: String, api: String, wss: String, cdn: String) -> Self {
Self {
root: UrlBundle::parse_url(root),
api: UrlBundle::parse_url(api),
wss: UrlBundle::parse_url(wss),
cdn: UrlBundle::parse_url(cdn),
@ -237,7 +251,12 @@ impl UrlBundle {
.json::<types::types::domains_configuration::Domains>()
.await
{
Ok(UrlBundle::new(body.api_endpoint, body.gateway, body.cdn))
Ok(UrlBundle::new(
url.to_string(),
body.api_endpoint,
body.gateway,
body.cdn,
))
} else {
Err(ChorusError::RequestFailed {
url: url.to_string(),

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! Ratelimiter and request handling functionality.
use std::collections::HashMap;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults};

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Clone, Debug)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::email::{

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::fmt::{Display, Formatter};
#[cfg(feature = "sqlx")]
use std::io::Write;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::kafka::KafkaBroker;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::{

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod api_configuration;
pub mod cdn_configuration;
pub mod defaults_configuration;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::region::Region;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::register::{

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use base64::Engine;
use rand::Fill;
use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::ffi::OsString;
use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel};

View File

@ -1,2 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod guild;
pub mod user;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod mailgun;
pub mod mailjet;
pub mod sendgrid;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,2 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod autojoin;
pub mod discovery;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod channel;
pub mod global;
pub mod guild;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
pub mod auth;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::ratelimits::{

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod client;
pub mod defaults;
pub mod email;

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -1,3 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

Some files were not shown because too many files have changed in this diff Show More