Compare commits
8 Commits
f0dbd3410f
...
411db01786
Author | SHA1 | Date |
---|---|---|
bitfl0wer | 411db01786 | |
Flori | 76186a08f0 | |
bitfl0wer | d846ce9948 | |
bitfl0wer | e316372631 | |
bitfl0wer | 72c5d13eaf | |
bitfl0wer | 05b9f1c801 | |
Flori | 9926f8ab94 | |
Flori | 5bee907733 |
|
@ -83,7 +83,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -215,9 +215,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.13"
|
version = "1.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
|
checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
@ -286,7 +286,7 @@ version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -457,7 +457,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -468,7 +468,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -584,15 +584,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.32"
|
version = "1.0.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666"
|
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide 0.8.0",
|
"miniz_oxide 0.8.0",
|
||||||
|
@ -691,7 +691,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1601,7 +1601,7 @@ dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1659,9 +1659,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.36"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -2004,9 +2004,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.208"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
@ -2024,20 +2024,20 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.208"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.125"
|
version = "1.0.127"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
|
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -2053,7 +2053,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2095,7 +2095,7 @@ dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2289,7 +2289,7 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-macros-core",
|
"sqlx-macros-core",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2312,7 +2312,7 @@ dependencies = [
|
||||||
"sqlx-mysql",
|
"sqlx-mysql",
|
||||||
"sqlx-postgres",
|
"sqlx-postgres",
|
||||||
"sqlx-sqlite",
|
"sqlx-sqlite",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
@ -2382,7 +2382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ae3447aced07f8bc71d73dc8dd1c6d25c2f4d10ea62a22ceabc12af8410d7e2"
|
checksum = "0ae3447aced07f8bc71d73dc8dd1c6d25c2f4d10ea62a22ceabc12af8410d7e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2487,9 +2487,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.75"
|
version = "2.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
|
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2562,7 +2562,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2635,7 +2635,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2730,7 +2730,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2929,7 +2929,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2963,7 +2963,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -2997,7 +2997,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3299,7 +3299,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -23,9 +23,9 @@ voice_gateway = []
|
||||||
sqlx-pg-uint = ["dep:sqlx-pg-uint", "sqlx-pg-uint/serde"]
|
sqlx-pg-uint = ["dep:sqlx-pg-uint", "sqlx-pg-uint/serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.38.1", features = ["macros", "sync"] }
|
tokio = { version = "1.39.3", features = ["macros", "sync"] }
|
||||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
serde = { version = "1.0.209", features = ["derive", "rc"] }
|
||||||
serde_json = { version = "1.0.120", features = ["raw_value"] }
|
serde_json = { version = "1.0.127", features = ["raw_value"] }
|
||||||
serde-aux = "4.5.0"
|
serde-aux = "4.5.0"
|
||||||
serde_with = "3.9.0"
|
serde_with = "3.9.0"
|
||||||
serde_repr = "0.1.19"
|
serde_repr = "0.1.19"
|
||||||
|
@ -36,7 +36,7 @@ reqwest = { features = [
|
||||||
], version = "=0.11.26", default-features = false }
|
], version = "=0.11.26", default-features = false }
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
regex = "1.10.5"
|
regex = "1.10.6"
|
||||||
custom_error = "1.9.2"
|
custom_error = "1.9.2"
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
http = "0.2.12"
|
http = "0.2.12"
|
||||||
|
@ -64,7 +64,7 @@ discortp = { version = "0.5.0", optional = true, features = [
|
||||||
] }
|
] }
|
||||||
crypto_secretbox = { version = "0.1.1", optional = true }
|
crypto_secretbox = { version = "0.1.1", optional = true }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
flate2 = { version = "1.0.30", optional = true }
|
flate2 = { version = "1.0.33", optional = true }
|
||||||
webpki-roots = "0.26.3"
|
webpki-roots = "0.26.3"
|
||||||
pubserve = { version = "1.1.0", features = ["async", "send"] }
|
pubserve = { version = "1.1.0", features = ["async", "send"] }
|
||||||
sqlx-pg-uint = { version = "0.5.0", features = ["serde"], optional = true }
|
sqlx-pg-uint = { version = "0.5.0", features = ["serde"], optional = true }
|
||||||
|
|
90
README.md
90
README.md
|
@ -28,14 +28,15 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/)
|
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
|
||||||
and Discord. It is designed to be easy to use, and to be compatible with both Discord and Spacebar Chat.
|
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
|
||||||
|
flexibility one would expect from a library like this.
|
||||||
|
|
||||||
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
|
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
|
||||||
|
|
||||||
## A Tour of Chorus
|
## A Tour of Chorus
|
||||||
|
|
||||||
Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
|
Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
|
||||||
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
|
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
|
||||||
a WebSocket connection to the Gateway. This means that you can focus on building your application,
|
a WebSocket connection to the Gateway. This means that you can focus on building your application,
|
||||||
instead of worrying about the underlying implementation details.
|
instead of worrying about the underlying implementation details.
|
||||||
|
@ -44,19 +45,19 @@ To get started with Chorus, import it into your project by adding the following
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chorus = "0.15.0"
|
chorus = "0.16.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Establishing a Connection
|
### Establishing a Connection
|
||||||
|
|
||||||
To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
||||||
|
|
||||||
```rs
|
```rs
|
||||||
use chorus::instance::Instance;
|
use chorus::instance::Instance;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let instance = Instance::new("https://example.com")
|
let instance = Instance::new("https://example.com", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect to the Spacebar server");
|
.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.
|
// You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
|
||||||
|
@ -81,7 +82,7 @@ let login_schema = LoginSchema {
|
||||||
password: "Correct-Horse-Battery-Staple".to_string(),
|
password: "Correct-Horse-Battery-Staple".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
|
// Each user connects to the Gateway. Each users' Gateway connection lives on a separate thread. Depending on
|
||||||
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
|
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
|
||||||
let user = instance
|
let user = instance
|
||||||
.login_account(login_schema)
|
.login_account(login_schema)
|
||||||
|
@ -148,80 +149,6 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
|
||||||
|
|
||||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Progress Tracker/Roadmap</summary>
|
|
||||||
|
|
||||||
### Core Functionality
|
|
||||||
- [x] Rate Limiter (hint: couldn't be fully tested due to [an Issue with the Spacebar Server](https://github.com/spacebarchat/server/issues/1022))
|
|
||||||
- [x] [Login (the conventional way)](https://github.com/polyphony-chat/chorus/issues/1)
|
|
||||||
- [ ] [2FA](https://github.com/polyphony-chat/chorus/issues/40)
|
|
||||||
- [x] [Registration](https://github.com/polyphony-chat/chorus/issues/1)
|
|
||||||
|
|
||||||
### Messaging
|
|
||||||
- [x] [Sending messages](https://github.com/polyphony-chat/chorus/issues/23)
|
|
||||||
- [x] [Events (Message, User, Channel, etc.)](https://github.com/polyphony-chat/chorus/issues/51)
|
|
||||||
- [x] Channel creation
|
|
||||||
- [x] Channel deletion
|
|
||||||
- [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48)
|
|
||||||
- [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45)
|
|
||||||
- [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45)
|
|
||||||
- [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89)
|
|
||||||
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
|
|
||||||
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
|
|
||||||
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
|
|
||||||
- [ ] Message Search
|
|
||||||
- [ ] Message history
|
|
||||||
- [ ] Emoji
|
|
||||||
- [ ] Stickers
|
|
||||||
- [ ] [Forum channels](https://github.com/polyphony-chat/chorus/issues/90)
|
|
||||||
|
|
||||||
### User Management
|
|
||||||
- [ ] [User profile customization](https://github.com/polyphony-chat/chorus/issues/41)
|
|
||||||
- [x] Gettings users and user profiles
|
|
||||||
- [x] [Friend requests](https://github.com/polyphony-chat/chorus/issues/92)
|
|
||||||
- [x] [Blocking users](https://github.com/polyphony-chat/chorus/issues/92)
|
|
||||||
- [ ] User presence (online, offline, idle, etc.)
|
|
||||||
- [ ] User status (custom status, etc.)
|
|
||||||
- [x] Account deletion
|
|
||||||
|
|
||||||
### Additional Features
|
|
||||||
- [ ] Server discovery
|
|
||||||
- [ ] Server templates
|
|
||||||
|
|
||||||
### Voice and Video
|
|
||||||
- [ ] [Voice chat support](https://github.com/polyphony-chat/chorus/issues/49)
|
|
||||||
- [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49)
|
|
||||||
|
|
||||||
### Permissions and Roles
|
|
||||||
- [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification)
|
|
||||||
- [x] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions)
|
|
||||||
- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88)
|
|
||||||
- [x] Role-based access control
|
|
||||||
|
|
||||||
### Guild Management
|
|
||||||
- [x] Guild creation
|
|
||||||
- [x] Guild deletion
|
|
||||||
- [ ] [Guild settings (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/43)
|
|
||||||
- [ ] Guild invites
|
|
||||||
|
|
||||||
### Moderation
|
|
||||||
- [ ] Channel moderation (slow mode, etc.)
|
|
||||||
- [ ] User sanctions (mute, kick, ban)
|
|
||||||
- [ ] Audit logs
|
|
||||||
|
|
||||||
### Embeds and Rich Content
|
|
||||||
- [x] Sending rich content in messages (links, images, videos)
|
|
||||||
- [ ] Customizing embed appearance (title, description, color, fields)
|
|
||||||
|
|
||||||
### Webhooks
|
|
||||||
- [ ] Webhook creation and management
|
|
||||||
- [ ] Handling incoming webhook events
|
|
||||||
|
|
||||||
### Documentation and Examples
|
|
||||||
- [ ] Comprehensive documentation
|
|
||||||
- [ ] Example usage and code snippets
|
|
||||||
- [ ] Tutorials and guides
|
|
||||||
|
|
||||||
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
|
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
|
||||||
[Rust-url]: https://www.rust-lang.org/
|
[Rust-url]: https://www.rust-lang.org/
|
||||||
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
|
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
|
||||||
|
@ -242,4 +169,3 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
|
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
|
||||||
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
|
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
|
||||||
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
|
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
|
||||||
</details>
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ impl Subscriber<GatewayReady> for ExampleObserver {
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let gateway_websocket_url = GATEWAY_URL.to_string();
|
let gateway_websocket_url = GATEWAY_URL;
|
||||||
|
|
||||||
// These options specify the encoding format, compression, etc
|
// These options specify the encoding format, compression, etc
|
||||||
//
|
//
|
||||||
|
|
|
@ -25,7 +25,7 @@ use wasmtimer::tokio::sleep;
|
||||||
/// This example creates a simple gateway connection and a session with an Identify event
|
/// This example creates a simple gateway connection and a session with an Identify event
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let gateway_websocket_url = GATEWAY_URL.to_string();
|
let gateway_websocket_url = GATEWAY_URL;
|
||||||
|
|
||||||
// These options specify the encoding format, compression, etc
|
// These options specify the encoding format, compression, etc
|
||||||
//
|
//
|
||||||
|
@ -34,7 +34,9 @@ async fn main() {
|
||||||
let options = GatewayOptions::default();
|
let options = GatewayOptions::default();
|
||||||
|
|
||||||
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
|
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
|
||||||
let gateway = Gateway::spawn(gateway_websocket_url, options).await.unwrap();
|
let gateway = Gateway::spawn(gateway_websocket_url, options)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
|
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use chorus::instance::Instance;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let instance = Instance::new("https://example.com/")
|
let instance = Instance::new("https://example.com/", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect to the Spacebar server");
|
.expect("Failed to connect to the Spacebar server");
|
||||||
dbg!(instance.instance_info);
|
dbg!(instance.instance_info);
|
||||||
|
|
|
@ -7,7 +7,7 @@ use chorus::types::LoginSchema;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let mut instance = Instance::new("https://example.com/")
|
let mut instance = Instance::new("https://example.com/", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect to the Spacebar server");
|
.expect("Failed to connect to the Spacebar server");
|
||||||
// Assume, you already have an account created on this instance. Registering an account works
|
// Assume, you already have an account created on this instance. Registering an account works
|
||||||
|
|
|
@ -30,13 +30,12 @@ impl Instance {
|
||||||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||||
// request (since login is an instance wide limit), which is why we are just cloning the
|
// request (since login is an instance wide limit), which is why we are just cloning the
|
||||||
// instances' limits to pass them on as user_rate_limits later.
|
// instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut user =
|
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
|
||||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
|
||||||
|
|
||||||
let login_result = chorus_request
|
let login_result = chorus_request
|
||||||
.deserialize_response::<LoginResult>(&mut user)
|
.deserialize_response::<LoginResult>(&mut user)
|
||||||
.await?;
|
.await?;
|
||||||
user.set_token(login_result.token);
|
user.set_token(&login_result.token);
|
||||||
user.settings = login_result.settings;
|
user.settings = login_result.settings;
|
||||||
|
|
||||||
let object = User::get(&mut user, None).await?;
|
let object = User::get(&mut user, None).await?;
|
||||||
|
|
|
@ -22,9 +22,8 @@ pub mod register;
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
/// Logs into an existing account on the spacebar server, using only a token.
|
/// Logs into an existing account on the spacebar server, using only a token.
|
||||||
pub async fn login_with_token(&mut self, token: String) -> ChorusResult<ChorusUser> {
|
pub async fn login_with_token(&mut self, token: &str) -> ChorusResult<ChorusUser> {
|
||||||
let mut user =
|
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
|
||||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
|
|
||||||
|
|
||||||
let object = User::get(&mut user, None).await?;
|
let object = User::get(&mut user, None).await?;
|
||||||
let settings = User::get_settings(&mut user).await?;
|
let settings = User::get_settings(&mut user).await?;
|
||||||
|
|
|
@ -37,14 +37,13 @@ impl Instance {
|
||||||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||||
// request (since register is an instance wide limit), which is why we are just cloning
|
// request (since register is an instance wide limit), which is why we are just cloning
|
||||||
// the instances' limits to pass them on as user_rate_limits later.
|
// the instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut user =
|
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
|
||||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
|
||||||
|
|
||||||
let token = chorus_request
|
let token = chorus_request
|
||||||
.deserialize_response::<Token>(&mut user)
|
.deserialize_response::<Token>(&mut user)
|
||||||
.await?
|
.await?
|
||||||
.token;
|
.token;
|
||||||
user.set_token(token);
|
user.set_token(&token);
|
||||||
|
|
||||||
let object = User::get(&mut user, None).await?;
|
let object = User::get(&mut user, None).await?;
|
||||||
let settings = User::get_settings(&mut user).await?;
|
let settings = User::get_settings(&mut user).await?;
|
||||||
|
|
|
@ -112,7 +112,7 @@ impl Message {
|
||||||
let result = request.send_request(user).await?;
|
let result = request.send_request(user).await?;
|
||||||
let result_json = result.json::<Value>().await.unwrap();
|
let result_json = result.json::<Value>().await.unwrap();
|
||||||
if !result_json.is_object() {
|
if !result_json.is_object() {
|
||||||
return Err(search_error(result_json.to_string()));
|
return Err(search_error(result_json.to_string().as_str()));
|
||||||
}
|
}
|
||||||
let value_map = result_json.as_object().unwrap();
|
let value_map = result_json.as_object().unwrap();
|
||||||
if let Some(messages) = value_map.get("messages") {
|
if let Some(messages) = value_map.get("messages") {
|
||||||
|
@ -123,7 +123,7 @@ impl Message {
|
||||||
}
|
}
|
||||||
// The code below might be incorrect. We'll cross that bridge when we come to it
|
// The code below might be incorrect. We'll cross that bridge when we come to it
|
||||||
if !value_map.contains_key("code") || !value_map.contains_key("retry_after") {
|
if !value_map.contains_key("code") || !value_map.contains_key("retry_after") {
|
||||||
return Err(search_error(result_json.to_string()));
|
return Err(search_error(result_json.to_string().as_str()));
|
||||||
}
|
}
|
||||||
let code = value_map.get("code").unwrap().as_u64().unwrap();
|
let code = value_map.get("code").unwrap().as_u64().unwrap();
|
||||||
let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();
|
let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();
|
||||||
|
@ -482,7 +482,7 @@ impl Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_error(result_text: String) -> ChorusError {
|
fn search_error(result_text: &str) -> ChorusError {
|
||||||
ChorusError::InvalidResponse {
|
ChorusError::InvalidResponse {
|
||||||
error: format!(
|
error: format!(
|
||||||
"Got unexpected Response, or Response which is not valid JSON. Response: \n{}",
|
"Got unexpected Response, or Response which is not valid JSON. Response: \n{}",
|
||||||
|
|
|
@ -9,8 +9,10 @@ use futures_util::{
|
||||||
};
|
};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_tungstenite::{
|
use tokio_tungstenite::{
|
||||||
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream,
|
connect_async_tls_with_config, connect_async_with_config, tungstenite, Connector,
|
||||||
|
MaybeTlsStream, WebSocketStream,
|
||||||
};
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::gateway::{GatewayMessage, RawGatewayMessage};
|
use crate::gateway::{GatewayMessage, RawGatewayMessage};
|
||||||
|
|
||||||
|
@ -32,6 +34,21 @@ impl TungsteniteBackend {
|
||||||
pub async fn connect(
|
pub async fn connect(
|
||||||
websocket_url: &str,
|
websocket_url: &str,
|
||||||
) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> {
|
) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> {
|
||||||
|
let websocket_url_parsed =
|
||||||
|
Url::parse(websocket_url).map_err(|_| TungsteniteBackendError::TungsteniteError {
|
||||||
|
error: tungstenite::error::Error::Url(
|
||||||
|
tungstenite::error::UrlError::UnsupportedUrlScheme,
|
||||||
|
),
|
||||||
|
})?;
|
||||||
|
if websocket_url_parsed.scheme() == "ws" {
|
||||||
|
let (websocket_stream, _) =
|
||||||
|
match connect_async_with_config(websocket_url, None, false).await {
|
||||||
|
Ok(websocket_stream) => websocket_stream,
|
||||||
|
Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(websocket_stream.split())
|
||||||
|
} else if websocket_url_parsed.scheme() == "wss" {
|
||||||
let certs = webpki_roots::TLS_SERVER_ROOTS;
|
let certs = webpki_roots::TLS_SERVER_ROOTS;
|
||||||
let roots = rustls::RootCertStore {
|
let roots = rustls::RootCertStore {
|
||||||
roots: certs
|
roots: certs
|
||||||
|
@ -64,6 +81,13 @@ impl TungsteniteBackend {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(websocket_stream.split())
|
Ok(websocket_stream.split())
|
||||||
|
} else {
|
||||||
|
Err(TungsteniteBackendError::TungsteniteError {
|
||||||
|
error: tungstenite::error::Error::Url(
|
||||||
|
tungstenite::error::UrlError::UnsupportedUrlScheme,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl Gateway {
|
||||||
/// # Note
|
/// # Note
|
||||||
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
|
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
websocket_url: String,
|
websocket_url: &str,
|
||||||
options: GatewayOptions,
|
options: GatewayOptions,
|
||||||
) -> Result<GatewayHandle, GatewayError> {
|
) -> Result<GatewayHandle, GatewayError> {
|
||||||
let url = options.add_to_url(websocket_url);
|
let url = options.add_to_url(websocket_url);
|
||||||
|
|
|
@ -25,8 +25,8 @@ impl GatewayOptions {
|
||||||
/// Adds the options to an existing gateway url
|
/// Adds the options to an existing gateway url
|
||||||
///
|
///
|
||||||
/// Returns the new url
|
/// Returns the new url
|
||||||
pub(crate) fn add_to_url(&self, url: String) -> String {
|
pub(crate) fn add_to_url(&self, url: &str) -> String {
|
||||||
let mut url = url;
|
let mut url = url.to_string();
|
||||||
|
|
||||||
let mut parameters = Vec::with_capacity(2);
|
let mut parameters = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,13 @@ impl Instance {
|
||||||
|
|
||||||
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
|
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
|
||||||
///
|
///
|
||||||
|
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
|
||||||
|
///
|
||||||
/// To create an Instance from one singular url, use [`Instance::new()`].
|
/// To create an Instance from one singular url, use [`Instance::new()`].
|
||||||
pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> {
|
pub async fn from_url_bundle(
|
||||||
|
urls: UrlBundle,
|
||||||
|
options: Option<GatewayOptions>,
|
||||||
|
) -> ChorusResult<Instance> {
|
||||||
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
|
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
|
||||||
let limit_information;
|
let limit_information;
|
||||||
|
|
||||||
|
@ -89,7 +94,7 @@ impl Instance {
|
||||||
instance_info: GeneralConfiguration::default(),
|
instance_info: GeneralConfiguration::default(),
|
||||||
limits_information: limit_information,
|
limits_information: limit_information,
|
||||||
client: Client::new(),
|
client: Client::new(),
|
||||||
gateway_options: GatewayOptions::default(),
|
gateway_options: options.unwrap_or_default(),
|
||||||
};
|
};
|
||||||
instance.instance_info = match instance.general_configuration_schema().await {
|
instance.instance_info = match instance.general_configuration_schema().await {
|
||||||
Ok(schema) => schema,
|
Ok(schema) => schema,
|
||||||
|
@ -103,14 +108,16 @@ impl Instance {
|
||||||
|
|
||||||
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
|
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
|
||||||
///
|
///
|
||||||
|
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
|
||||||
|
///
|
||||||
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
|
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
|
||||||
pub async fn new(root_url: &str) -> ChorusResult<Instance> {
|
pub async fn new(root_url: &str, options: Option<GatewayOptions>) -> ChorusResult<Instance> {
|
||||||
let urls = UrlBundle::from_root_url(root_url).await?;
|
let urls = UrlBundle::from_root_url(root_url).await?;
|
||||||
Instance::from_url_bundle(urls).await
|
Instance::from_url_bundle(urls, options).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
|
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
|
||||||
let api_url = UrlBundle::parse_url(api_url.to_string());
|
let api_url = UrlBundle::parse_url(api_url);
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let request = client
|
let request = client
|
||||||
.get(format!("{}/policies/instance/limits", &api_url))
|
.get(format!("{}/policies/instance/limits", &api_url))
|
||||||
|
@ -163,8 +170,8 @@ impl ChorusUser {
|
||||||
self.token.clone()
|
self.token.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_token(&mut self, token: String) {
|
pub fn set_token(&mut self, token: &str) {
|
||||||
self.token = token;
|
self.token = token.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [ChorusUser] from existing data.
|
/// Creates a new [ChorusUser] from existing data.
|
||||||
|
@ -195,16 +202,15 @@ impl ChorusUser {
|
||||||
/// registering or logging in to the Instance, where you do not yet have a User object, but still
|
/// 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
|
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
|
||||||
/// first.
|
/// first.
|
||||||
pub(crate) async fn shell(instance: Shared<Instance>, token: String) -> ChorusUser {
|
pub(crate) async fn shell(instance: Shared<Instance>, token: &str) -> ChorusUser {
|
||||||
let settings = Arc::new(RwLock::new(UserSettings::default()));
|
let settings = Arc::new(RwLock::new(UserSettings::default()));
|
||||||
let object = Arc::new(RwLock::new(User::default()));
|
let object = Arc::new(RwLock::new(User::default()));
|
||||||
let wss_url = instance.read().unwrap().urls.wss.clone();
|
let wss_url = &instance.read().unwrap().urls.wss.clone();
|
||||||
|
let gateway_options = instance.read().unwrap().gateway_options;
|
||||||
// Dummy gateway object
|
// Dummy gateway object
|
||||||
let gateway = Gateway::spawn(wss_url, GatewayOptions::default())
|
let gateway = Gateway::spawn(wss_url, gateway_options).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ChorusUser {
|
ChorusUser {
|
||||||
token,
|
token: token.to_string(),
|
||||||
belongs_to: instance.clone(),
|
belongs_to: instance.clone(),
|
||||||
limits: instance
|
limits: instance
|
||||||
.read()
|
.read()
|
||||||
|
|
88
src/lib.rs
88
src/lib.rs
|
@ -3,27 +3,29 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
|
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
|
||||||
|
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
|
||||||
|
flexibility one would expect from a library like this.
|
||||||
|
|
||||||
|
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
|
||||||
|
|
||||||
|
## A Tour of Chorus
|
||||||
|
|
||||||
|
Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
|
||||||
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
|
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
|
||||||
a WebSocket connection to the Gateway. This means that you can focus on building your application,
|
a WebSocket connection to the Gateway. This means that you can focus on building your application,
|
||||||
instead of worrying about the underlying implementation details.
|
instead of worrying about the underlying implementation details.
|
||||||
|
|
||||||
### Establishing a Connection
|
### Establishing a Connection
|
||||||
|
|
||||||
To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
|
||||||
|
|
||||||
```rs
|
```rs
|
||||||
use chorus::instance::Instance;
|
use chorus::instance::Instance;
|
||||||
use chorus::UrlBundle;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let bundle = UrlBundle::new(
|
let instance = Instance::new("https://example.com")
|
||||||
"https://example.com/api".to_string(),
|
|
||||||
"wss://example.com/".to_string(),
|
|
||||||
"https://example.com/cdn".to_string(),
|
|
||||||
);
|
|
||||||
let instance = Instance::new(bundle)
|
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect to the Spacebar server");
|
.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.
|
// You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
|
||||||
|
@ -36,7 +38,7 @@ This Instance can now be used to log in, register and from there on, interact wi
|
||||||
|
|
||||||
### Logging In
|
### Logging In
|
||||||
|
|
||||||
Logging in correctly provides you with an instance of [`ChorusUser`](https://docs.rs/chorus/latest/chorus/instance/struct.ChorusUser.html), with which you can interact with the server and
|
Logging in correctly provides you with an instance of `ChorusUser`, with which you can interact with the server and
|
||||||
manipulate the account. Assuming you already have an account on the server, you can log in like this:
|
manipulate the account. Assuming you already have an account on the server, you can log in like this:
|
||||||
|
|
||||||
```rs
|
```rs
|
||||||
|
@ -48,7 +50,7 @@ let login_schema = LoginSchema {
|
||||||
password: "Correct-Horse-Battery-Staple".to_string(),
|
password: "Correct-Horse-Battery-Staple".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
|
// Each user connects to the Gateway. Each users' Gateway connection lives on a separate thread. Depending on
|
||||||
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
|
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
|
||||||
let user = instance
|
let user = instance
|
||||||
.login_account(login_schema)
|
.login_account(login_schema)
|
||||||
|
@ -64,15 +66,33 @@ All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aar
|
||||||
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use
|
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use
|
||||||
Chorus in your browser, or in any other environment that supports WebAssembly.
|
Chorus in your browser, or in any other environment that supports WebAssembly.
|
||||||
|
|
||||||
We recommend checking out the examples directory, as well as the documentation for more information.
|
To compile for `wasm32-unknown-unknown`, execute the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --target=wasm32-unknown-unknown --no-default-features
|
||||||
|
```
|
||||||
|
|
||||||
|
The following features are supported on `wasm32-unknown-unknown`:
|
||||||
|
|
||||||
|
| Feature | WASM Support |
|
||||||
|
| ----------------- | ------------ |
|
||||||
|
| `client` | ✅ |
|
||||||
|
| `rt` | ✅ |
|
||||||
|
| `rt-multi-thread` | ❌ |
|
||||||
|
| `backend` | ❌ |
|
||||||
|
| `voice` | ❌ |
|
||||||
|
| `voice_udp` | ❌ |
|
||||||
|
| `voice_gateway` | ✅ |
|
||||||
|
|
||||||
|
We recommend checking out the "examples" directory, as well as the documentation for more information.
|
||||||
|
|
||||||
## MSRV (Minimum Supported Rust Version)
|
## MSRV (Minimum Supported Rust Version)
|
||||||
|
|
||||||
Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0.
|
Rust **1.70.0**. This number might change at any point while Chorus is not yet at version 1.0.0.
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version`
|
Make sure that you have at least Rust 1.70.0 installed. You can check your Rust version by running `cargo --version`
|
||||||
in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target.
|
in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target.
|
||||||
You can do this by running `rustup target add wasm32-unknown-unknown`.
|
You can do this by running `rustup target add wasm32-unknown-unknown`.
|
||||||
|
|
||||||
|
@ -86,12 +106,16 @@ like "proxy connection checking" are already disabled on this version, which oth
|
||||||
### wasm
|
### wasm
|
||||||
|
|
||||||
To test for wasm, you will need to `cargo install wasm-pack`. You can then run
|
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.
|
to run the tests for wasm.
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html).
|
This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
!*/
|
!*/
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png"
|
html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png"
|
||||||
|
@ -186,7 +210,7 @@ pub struct UrlBundle {
|
||||||
|
|
||||||
impl UrlBundle {
|
impl UrlBundle {
|
||||||
/// Creates a new UrlBundle from the relevant urls.
|
/// Creates a new UrlBundle from the relevant urls.
|
||||||
pub fn new(root: String, api: String, wss: String, cdn: String) -> Self {
|
pub fn new(root: &str, api: &str, wss: &str, cdn: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: UrlBundle::parse_url(root),
|
root: UrlBundle::parse_url(root),
|
||||||
api: UrlBundle::parse_url(api),
|
api: UrlBundle::parse_url(api),
|
||||||
|
@ -203,17 +227,17 @@ impl UrlBundle {
|
||||||
/// let url = parse_url("localhost:3000");
|
/// let url = parse_url("localhost:3000");
|
||||||
/// ```
|
/// ```
|
||||||
/// `-> Outputs "http://localhost:3000".`
|
/// `-> Outputs "http://localhost:3000".`
|
||||||
pub fn parse_url(url: String) -> String {
|
pub fn parse_url(url: &str) -> String {
|
||||||
let url = match Url::parse(&url) {
|
let url = match Url::parse(url) {
|
||||||
Ok(url) => {
|
Ok(url) => {
|
||||||
if url.scheme() == "localhost" {
|
if url.scheme() == "localhost" {
|
||||||
return UrlBundle::parse_url(format!("http://{}", url));
|
return UrlBundle::parse_url(&format!("http://{}", url));
|
||||||
}
|
}
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
Err(ParseError::RelativeUrlWithoutBase) => {
|
Err(ParseError::RelativeUrlWithoutBase) => {
|
||||||
let url_fmt = format!("http://{}", url);
|
let url_fmt = format!("http://{}", url);
|
||||||
return UrlBundle::parse_url(url_fmt);
|
return UrlBundle::parse_url(&url_fmt);
|
||||||
}
|
}
|
||||||
Err(_) => panic!("Invalid URL"), // TODO: should not panic here
|
Err(_) => panic!("Invalid URL"), // TODO: should not panic here
|
||||||
};
|
};
|
||||||
|
@ -236,7 +260,7 @@ impl UrlBundle {
|
||||||
/// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that
|
/// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that
|
||||||
/// a wrong URL was provided.
|
/// a wrong URL was provided.
|
||||||
pub async fn from_root_url(url: &str) -> ChorusResult<UrlBundle> {
|
pub async fn from_root_url(url: &str) -> ChorusResult<UrlBundle> {
|
||||||
let parsed = UrlBundle::parse_url(url.to_string());
|
let parsed = UrlBundle::parse_url(url);
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let request_wellknown = client
|
let request_wellknown = client
|
||||||
.get(format!("{}/.well-known/spacebar", &parsed))
|
.get(format!("{}/.well-known/spacebar", &parsed))
|
||||||
|
@ -274,10 +298,10 @@ impl UrlBundle {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(UrlBundle::new(
|
Ok(UrlBundle::new(
|
||||||
url.to_string(),
|
url,
|
||||||
body.api_endpoint,
|
&body.api_endpoint,
|
||||||
body.gateway,
|
&body.gateway,
|
||||||
body.cdn,
|
&body.cdn,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Err(ChorusError::RequestFailed {
|
Err(ChorusError::RequestFailed {
|
||||||
|
@ -294,13 +318,13 @@ mod lib {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_url() {
|
fn test_parse_url() {
|
||||||
let mut result = UrlBundle::parse_url(String::from("localhost:3000/"));
|
let mut result = UrlBundle::parse_url("localhost:3000/");
|
||||||
assert_eq!(result, String::from("http://localhost:3000"));
|
assert_eq!(result, "http://localhost:3000");
|
||||||
result = UrlBundle::parse_url(String::from("https://some.url.com/"));
|
result = UrlBundle::parse_url("https://some.url.com/");
|
||||||
assert_eq!(result, String::from("https://some.url.com"));
|
|
||||||
result = UrlBundle::parse_url(String::from("https://some.url.com/"));
|
|
||||||
assert_eq!(result, String::from("https://some.url.com"));
|
|
||||||
result = UrlBundle::parse_url(String::from("https://some.url.com"));
|
|
||||||
assert_eq!(result, String::from("https://some.url.com"));
|
assert_eq!(result, String::from("https://some.url.com"));
|
||||||
|
result = UrlBundle::parse_url("https://some.url.com/");
|
||||||
|
assert_eq!(result, "https://some.url.com");
|
||||||
|
result = UrlBundle::parse_url("https://some.url.com");
|
||||||
|
assert_eq!(result, "https://some.url.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ use jsonwebtoken::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String {
|
pub fn generate_token(id: &Snowflake, email: &str, jwt_key: &str) -> String {
|
||||||
let claims = Claims::new(&email, id);
|
let claims = Claims::new(email, id);
|
||||||
|
|
||||||
build_token(&claims, jwt_key).unwrap()
|
build_token(&claims, jwt_key).unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,12 @@ impl From<u64> for Snowflake {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Snowflake> for u64 {
|
||||||
|
fn from(item: Snowflake) -> Self {
|
||||||
|
item.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl serde::Serialize for Snowflake {
|
impl serde::Serialize for Snowflake {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub struct VoiceGateway {
|
||||||
|
|
||||||
impl VoiceGateway {
|
impl VoiceGateway {
|
||||||
#[allow(clippy::new_ret_no_self)]
|
#[allow(clippy::new_ret_no_self)]
|
||||||
pub async fn spawn(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
|
pub async fn spawn(websocket_url: &str) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
|
||||||
// Append the needed things to the websocket url
|
// Append the needed things to the websocket url
|
||||||
let processed_url = format!("wss://{}/?v=7", websocket_url);
|
let processed_url = format!("wss://{}/?v=7", websocket_url);
|
||||||
trace!("VGW: Connecting to {}", processed_url.clone());
|
trace!("VGW: Connecting to {}", processed_url.clone());
|
||||||
|
@ -110,7 +110,7 @@ impl VoiceGateway {
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(VoiceGatewayHandle {
|
Ok(VoiceGatewayHandle {
|
||||||
url: websocket_url.clone(),
|
url: websocket_url.to_string(),
|
||||||
events: shared_events,
|
events: shared_events,
|
||||||
websocket_send: shared_websocket_send.clone(),
|
websocket_send: shared_websocket_send.clone(),
|
||||||
kill_send: kill_send.clone(),
|
kill_send: kill_send.clone(),
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl VoiceGatewayHandle {
|
||||||
|
|
||||||
/// Sends a speaking event to the gateway
|
/// Sends a speaking event to the gateway
|
||||||
pub async fn send_speaking(&self, to_send: Speaking) {
|
pub async fn send_speaking(&self, to_send: Speaking) {
|
||||||
let to_send_value = serde_json::to_value(&to_send).unwrap();
|
let to_send_value = serde_json::to_value(to_send).unwrap();
|
||||||
|
|
||||||
trace!("VGW: Sending Speaking");
|
trace!("VGW: Sending Speaking");
|
||||||
|
|
||||||
|
|
|
@ -79,11 +79,7 @@ async fn test_login_with_token() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
let token = &bundle.user.token;
|
let token = &bundle.user.token;
|
||||||
let other_user = bundle
|
let other_user = bundle.instance.login_with_token(token).await.unwrap();
|
||||||
.instance
|
|
||||||
.login_with_token(token.clone())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bundle.user.object.read().unwrap().id,
|
bundle.user.object.read().unwrap().id,
|
||||||
other_user.object.read().unwrap().id
|
other_user.object.read().unwrap().id
|
||||||
|
@ -98,8 +94,8 @@ async fn test_login_with_token() {
|
||||||
async fn test_login_with_invalid_token() {
|
async fn test_login_with_invalid_token() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
let token = "invalid token lalalalala".to_string();
|
let token = "invalid token lalalalala";
|
||||||
let other_user = bundle.instance.login_with_token(token.clone()).await;
|
let other_user = bundle.instance.login_with_token(token).await;
|
||||||
|
|
||||||
assert!(other_user.is_err());
|
assert!(other_user.is_err());
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use chorus::{
|
||||||
instance::{ChorusUser, Instance},
|
instance::{ChorusUser, Instance},
|
||||||
types::{
|
types::{
|
||||||
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
|
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
|
||||||
RoleCreateModifySchema, RoleObject, Shared
|
RoleCreateModifySchema, RoleObject, Shared,
|
||||||
},
|
},
|
||||||
UrlBundle,
|
UrlBundle,
|
||||||
};
|
};
|
||||||
|
@ -50,7 +50,7 @@ impl TestBundle {
|
||||||
limits: self.user.limits.clone(),
|
limits: self.user.limits.clone(),
|
||||||
settings: self.user.settings.clone(),
|
settings: self.user.settings.clone(),
|
||||||
object: self.user.object.clone(),
|
object: self.user.object.clone(),
|
||||||
gateway: Gateway::spawn(self.instance.urls.wss.clone(), GatewayOptions::default())
|
gateway: Gateway::spawn(&self.instance.urls.wss, GatewayOptions::default())
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
|
@ -59,11 +59,16 @@ impl TestBundle {
|
||||||
|
|
||||||
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
||||||
pub(crate) async fn setup() -> TestBundle {
|
pub(crate) async fn setup() -> TestBundle {
|
||||||
|
|
||||||
// So we can get logs when tests fail
|
// So we can get logs when tests fail
|
||||||
let _ = simple_logger::SimpleLogger::with_level(simple_logger::SimpleLogger::new(), log::LevelFilter::Debug).init();
|
let _ = simple_logger::SimpleLogger::with_level(
|
||||||
|
simple_logger::SimpleLogger::new(),
|
||||||
|
log::LevelFilter::Debug,
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
let instance = Instance::new("http://localhost:3001/api").await.unwrap();
|
let instance = Instance::new("http://localhost:3001/api", None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
// Requires the existence of the below user.
|
// Requires the existence of the below user.
|
||||||
let reg = RegisterSchema {
|
let reg = RegisterSchema {
|
||||||
username: "integrationtestuser".into(),
|
username: "integrationtestuser".into(),
|
||||||
|
@ -121,10 +126,10 @@ pub(crate) async fn setup() -> TestBundle {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let urls = UrlBundle::new(
|
let urls = UrlBundle::new(
|
||||||
"http://localhost:3001/api".to_string(),
|
"http://localhost:3001/api",
|
||||||
"http://localhost:3001/api".to_string(),
|
"http://localhost:3001/api",
|
||||||
"ws://localhost:3001/".to_string(),
|
"ws://localhost:3001/",
|
||||||
"http://localhost:3001".to_string(),
|
"http://localhost:3001",
|
||||||
);
|
);
|
||||||
TestBundle {
|
TestBundle {
|
||||||
urls,
|
urls,
|
||||||
|
|
|
@ -31,7 +31,7 @@ use wasmtimer::tokio::sleep;
|
||||||
async fn test_gateway_establish() {
|
async fn test_gateway_establish() {
|
||||||
let bundle = common::setup().await;
|
let bundle = common::setup().await;
|
||||||
|
|
||||||
let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default())
|
let _: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
common::teardown(bundle).await
|
common::teardown(bundle).await
|
||||||
|
@ -55,7 +55,7 @@ impl Subscriber<GatewayReady> for GatewayReadyObserver {
|
||||||
async fn test_gateway_authenticate() {
|
async fn test_gateway_authenticate() {
|
||||||
let bundle = common::setup().await;
|
let bundle = common::setup().await;
|
||||||
|
|
||||||
let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default())
|
let gateway: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue