Compare commits
11 Commits
5e9359e9f9
...
413d26f015
Author | SHA1 | Date |
---|---|---|
kozabrada123 | 413d26f015 | |
kozabrada123 | 926f89e1cf | |
kozabrada123 | 80c99753c4 | |
kozabrada123 | 1fa84b4b63 | |
kozabrada123 | e6a4cc30a6 | |
Flori | e4dd31ef78 | |
kozabrada123 | 0a1c51dddd | |
kozabrada123 | 5d0a65a9a9 | |
kozabrada123 | 62d48d61fe | |
kozabrada123 | 20bdb3247a | |
kozabrada123 | 7db5015c07 |
|
@ -34,7 +34,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
|
@ -292,6 +291,15 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
|
@ -541,9 +549,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
version = "5.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
|
@ -773,9 +786,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
@ -806,12 +819,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
|
@ -1141,9 +1151,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
|||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
|
||||
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
@ -1367,6 +1377,12 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
|
@ -2059,6 +2075,9 @@ name = "smallvec"
|
|||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
|
@ -2107,9 +2126,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
|
||||
checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
|
@ -2120,11 +2139,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
|
||||
checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"atoi",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
@ -2138,6 +2156,7 @@ dependencies = [
|
|||
"futures-intrusive",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hashbrown 0.14.5",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"indexmap 2.2.6",
|
||||
|
@ -2164,22 +2183,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
|
||||
checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sqlx-core",
|
||||
"sqlx-macros-core",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
|
||||
checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
|
@ -2195,7 +2214,7 @@ dependencies = [
|
|||
"sqlx-mysql",
|
||||
"sqlx-postgres",
|
||||
"sqlx-sqlite",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.70",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"url",
|
||||
|
@ -2203,12 +2222,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
|
||||
checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
@ -2246,12 +2265,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
|
||||
checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
|
@ -2286,9 +2305,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
|
||||
checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
|
@ -2302,10 +2321,10 @@ dependencies = [
|
|||
"log",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"tracing",
|
||||
"url",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2675,12 +2694,6 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
|
@ -2720,12 +2733,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
|
|
|
@ -48,7 +48,7 @@ jsonwebtoken = "8.3.0"
|
|||
log = "0.4.22"
|
||||
async-trait = "0.1.81"
|
||||
chorus-macros = { path = "./chorus-macros", version = "0" } # Note: version here is used when releasing. This will use the latest release. Make sure to republish the crate when code in macros is changed!
|
||||
sqlx = { version = "0.7.4", features = [
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
"mysql",
|
||||
"sqlite",
|
||||
"json",
|
||||
|
@ -87,3 +87,6 @@ lazy_static = "1.5.0"
|
|||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.92"
|
||||
simple_logger = { version = "5.0.0", default-features = false }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
|
|
@ -156,7 +156,6 @@ pub fn composite_derive(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_derive(SqlxBitFlags)]
|
||||
pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
|
@ -165,25 +164,46 @@ pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream {
|
|||
|
||||
quote!{
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::MySql> for #name {
|
||||
fn type_info() -> sqlx::mysql::MySqlTypeInfo {
|
||||
u64::type_info()
|
||||
impl sqlx::Type<sqlx::Any> for #name {
|
||||
fn type_info() -> sqlx::any::AnyTypeInfo {
|
||||
<Vec<u8> as sqlx::Type<sqlx::Any>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Encode<'q, sqlx::MySql> for #name {
|
||||
fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
|
||||
u64::encode_by_ref(&self.bits(), buf)
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Any> for #name {
|
||||
fn encode_by_ref(&self, buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||
<Vec<u8> as sqlx::Encode<sqlx::Any>>::encode_by_ref(&self.bits().to_be_bytes().into(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Decode<'q, sqlx::MySql> for #name {
|
||||
fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'q>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
u64::decode(value).map(|d| #name::from_bits(d).unwrap())
|
||||
impl<'q> sqlx::Decode<'q, sqlx::Any> for #name {
|
||||
fn decode(value: <sqlx::Any as sqlx::Database>::ValueRef<'q>) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let vec = <Vec<u8> as sqlx::Decode<sqlx::Any>>::decode(value)?;
|
||||
Ok(Self::from_bits(vec_u8_to_u64(vec)).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [Vec<u8>] to an unsigned, 64 bit integer. The [u64] is created using [u64::from_be_bytes].
|
||||
///
|
||||
/// Empty vectors will result in an output of `0_u64`. Only the first 8 values from the vector are
|
||||
/// being processed. Any additional values will be skipped.
|
||||
///
|
||||
/// Vectors holding less than 8 values will be treated as a vector holding 8 values, where the
|
||||
/// missing values are padded with `0_u8`.
|
||||
fn vec_u8_to_u64(vec: Vec<u8>) -> u64 {
|
||||
let mut buf: [u8; 8] = [0; 8];
|
||||
let mut position = 0;
|
||||
for read in vec.iter() {
|
||||
buf[position] = *read;
|
||||
position += 1;
|
||||
if position > 7 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
u64::from_be_bytes(buf)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ impl Instance {
|
|||
user.set_token(login_result.token);
|
||||
user.settings = login_result.settings;
|
||||
|
||||
let object = User::get(&mut user, None).await?;
|
||||
let object = User::get_current(&mut user).await?;
|
||||
*user.object.write().unwrap() = object;
|
||||
|
||||
let mut identify = GatewayIdentifyPayload::common();
|
||||
|
|
|
@ -26,7 +26,7 @@ impl Instance {
|
|||
let mut user =
|
||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
|
||||
|
||||
let object = User::get(&mut user, None).await?;
|
||||
let object = User::get_current(&mut user).await?;
|
||||
let settings = User::get_settings(&mut user).await?;
|
||||
|
||||
*user.object.write().unwrap() = object;
|
||||
|
|
|
@ -46,7 +46,7 @@ impl Instance {
|
|||
.token;
|
||||
user.set_token(token);
|
||||
|
||||
let object = User::get(&mut user, None).await?;
|
||||
let object = User::get_current(&mut user).await?;
|
||||
let settings = User::get_settings(&mut user).await?;
|
||||
|
||||
*user.object.write().unwrap() = object;
|
||||
|
|
|
@ -11,22 +11,63 @@ use crate::{
|
|||
errors::{ChorusError, ChorusResult},
|
||||
instance::{ChorusUser, Instance},
|
||||
ratelimiter::ChorusRequest,
|
||||
types::{LimitType, User, UserModifySchema, UserSettings},
|
||||
types::{
|
||||
DeleteDisableUserSchema, GetPomeloEligibilityReturn, GetPomeloSuggestionsReturn, GetUserProfileSchema, LimitType, PublicUser, Snowflake, User, UserModifyProfileSchema, UserModifySchema, UserProfile, UserProfileMetadata, UserSettings, VerifyUserEmailChangeResponse, VerifyUserEmailChangeSchema
|
||||
},
|
||||
};
|
||||
|
||||
impl ChorusUser {
|
||||
/// Gets a user by id, or if the id is None, gets the current user.
|
||||
/// Gets the local / current user.
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get_current`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-current-user>
|
||||
pub async fn get_current_user(&mut self) -> ChorusResult<User> {
|
||||
User::get_current(self).await
|
||||
}
|
||||
|
||||
/// Gets a non-local user by their id
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#get-user> and
|
||||
/// <https://discord-userdoccers.vercel.app/resources/user#get-current-user>
|
||||
pub async fn get_user(&mut self, id: Option<&String>) -> ChorusResult<User> {
|
||||
/// See <https://docs.discord.sex/resources/user#get-user>
|
||||
pub async fn get_user(&mut self, id: Snowflake) -> ChorusResult<PublicUser> {
|
||||
User::get(self, id).await
|
||||
}
|
||||
|
||||
/// Gets a non-local user by their unique username.
|
||||
///
|
||||
/// As of 2024/07/28, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// If fetching with a pomelo username, discriminator should be set to None.
|
||||
///
|
||||
/// This route also permits fetching users with their old pre-pomelo username#discriminator
|
||||
/// combo.
|
||||
///
|
||||
/// Note:
|
||||
///
|
||||
/// "Unless the target user is a bot, you must be able to add
|
||||
/// the user as a friend to resolve them by username.
|
||||
///
|
||||
/// Due to this restriction, you are not able to resolve your own username."
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get_by_username`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-by-username>
|
||||
pub async fn get_user_by_username(
|
||||
&mut self,
|
||||
username: &String,
|
||||
discriminator: Option<&String>,
|
||||
) -> ChorusResult<PublicUser> {
|
||||
User::get_by_username(self, username, discriminator).await
|
||||
}
|
||||
|
||||
/// Gets the user's settings.
|
||||
///
|
||||
/// # Notes
|
||||
|
@ -40,7 +81,6 @@ impl ChorusUser {
|
|||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#modify-current-user>
|
||||
pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult<User> {
|
||||
|
||||
// See <https://docs.discord.sex/resources/user#json-params>, note 1
|
||||
let requires_current_password = modify_schema.username.is_some()
|
||||
|| modify_schema.discriminator.is_some()
|
||||
|
@ -67,39 +107,211 @@ impl ChorusUser {
|
|||
chorus_request.deserialize_response::<User>(self).await
|
||||
}
|
||||
|
||||
/// Deletes the user from the Instance.
|
||||
/// Disables the current user's account.
|
||||
///
|
||||
/// Invalidates all active tokens.
|
||||
///
|
||||
/// Requires the user's current password (if any)
|
||||
///
|
||||
/// # Notes
|
||||
/// Requires MFA
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#disable-user>
|
||||
pub async fn delete(mut self) -> ChorusResult<()> {
|
||||
/// See <https://docs.discord.sex/resources/user#disable-user>
|
||||
pub async fn disable(&mut self, schema: DeleteDisableUserSchema) -> ChorusResult<()> {
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/disable",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.json(&schema);
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
chorus_request.handle_request_as_result(self).await
|
||||
}
|
||||
|
||||
/// Deletes the current user from the Instance.
|
||||
///
|
||||
/// Requires the user's current password (if any)
|
||||
///
|
||||
/// # Notes
|
||||
/// Requires MFA
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#delete-user>
|
||||
pub async fn delete(&mut self, schema: DeleteDisableUserSchema) -> ChorusResult<()> {
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/delete",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.header("Content-Type", "application/json");
|
||||
.json(&schema);
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
chorus_request.handle_request_as_result(&mut self).await
|
||||
chorus_request.handle_request_as_result(self).await
|
||||
}
|
||||
|
||||
/// Gets a user's profile object by their id.
|
||||
///
|
||||
/// This endpoint requires one of the following:
|
||||
///
|
||||
/// - The other user is a bot
|
||||
/// - The other user shares a mutual guild with the current user
|
||||
/// - The other user is a friend of the current user
|
||||
/// - The other user is a friend suggestion of the current user
|
||||
/// - The other user has an outgoing friend request to the current user
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get_profile`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-profile>
|
||||
pub async fn get_user_profile(
|
||||
&mut self,
|
||||
id: Snowflake,
|
||||
query_parameters: GetUserProfileSchema,
|
||||
) -> ChorusResult<UserProfile> {
|
||||
User::get_profile(self, id, query_parameters).await
|
||||
}
|
||||
|
||||
/// Modifies the current user's profile.
|
||||
///
|
||||
/// Returns the updated [UserProfileMetadata].
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::modify_profile`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-profile>
|
||||
pub async fn modify_profile(
|
||||
&mut self,
|
||||
schema: UserModifyProfileSchema,
|
||||
) -> ChorusResult<UserProfileMetadata> {
|
||||
User::modify_profile(self, schema).await
|
||||
}
|
||||
|
||||
/// Initiates the email change process.
|
||||
///
|
||||
/// Sends a verification code to the current user's email.
|
||||
///
|
||||
/// Should be followed up with [Self::verify_email_change]
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-email>
|
||||
pub async fn initiate_email_change(&mut self) -> ChorusResult<()> {
|
||||
let request = Client::new()
|
||||
.put(format!(
|
||||
"{}/users/@me/email",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
chorus_request.handle_request_as_result(self).await
|
||||
}
|
||||
|
||||
/// Verifies a code sent to change the current user's email.
|
||||
///
|
||||
/// Should be the follow-up to [Self::initiate_email_change]
|
||||
///
|
||||
/// This endpoint returns a token which can be used with [Self::modify]
|
||||
/// to set a new email address (email_token).
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-email>
|
||||
pub async fn verify_email_change(
|
||||
&mut self,
|
||||
schema: VerifyUserEmailChangeSchema,
|
||||
) -> ChorusResult<VerifyUserEmailChangeResponse> {
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/email/verify-code",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.json(&schema);
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<VerifyUserEmailChangeResponse>(self)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns a suggested unique username based on the current user's username.
|
||||
///
|
||||
/// Note:
|
||||
///
|
||||
/// "This endpoint is used during the pomelo migration flow.
|
||||
///
|
||||
/// The user must be in the rollout to use this endpoint."
|
||||
///
|
||||
/// If a user has already migrated, this endpoint will likely return a 401 Unauthorized
|
||||
/// ([ChorusError::NoPermission])
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-pomelo-suggestions>
|
||||
pub async fn get_pomelo_suggestions(&mut self) -> ChorusResult<String> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/pomelo-suggestions",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<GetPomeloSuggestionsReturn>(self)
|
||||
.await
|
||||
.map(|returned| returned.username)
|
||||
}
|
||||
|
||||
/// Checks whether a unique username is available.
|
||||
///
|
||||
/// Returns whether the username is not taken yet.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-pomelo-eligibility>
|
||||
pub async fn get_pomelo_eligibility(&mut self, username: &String) -> ChorusResult<bool> {
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/pomelo-attempt",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
// FIXME: should we create a type for this?
|
||||
.body(format!(r#"{{ "username": {:?} }}"#, username))
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<GetPomeloEligibilityReturn>(self)
|
||||
.await
|
||||
.map(|returned| !returned.taken)
|
||||
}
|
||||
}
|
||||
|
||||
impl User {
|
||||
/// Gets a user by id, or if the id is None, gets the current user.
|
||||
/// Gets the local / current user.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#get-user> and
|
||||
/// <https://discord-userdoccers.vercel.app/resources/user#get-current-user>
|
||||
pub async fn get(user: &mut ChorusUser, id: Option<&String>) -> ChorusResult<User> {
|
||||
/// See <https://docs.discord.sex/resources/user#get-current-user>
|
||||
pub async fn get_current(user: &mut ChorusUser) -> ChorusResult<User> {
|
||||
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
|
||||
let url = if id.is_none() {
|
||||
format!("{}/users/@me", url_api)
|
||||
} else {
|
||||
format!("{}/users/{}", url_api, id.unwrap())
|
||||
};
|
||||
let url = format!("{}/users/@me", url_api);
|
||||
let request = reqwest::Client::new()
|
||||
.get(url)
|
||||
.header("Authorization", user.token());
|
||||
|
@ -107,16 +319,71 @@ impl User {
|
|||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
match chorus_request.send_request(user).await {
|
||||
Ok(result) => {
|
||||
let result_text = result.text().await.unwrap();
|
||||
Ok(serde_json::from_str::<User>(&result_text).unwrap())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
chorus_request.deserialize_response::<User>(user).await
|
||||
}
|
||||
|
||||
/// Gets the user's settings.
|
||||
/// Gets a non-local user by their id
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#get-user>
|
||||
pub async fn get(user: &mut ChorusUser, id: Snowflake) -> ChorusResult<PublicUser> {
|
||||
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
|
||||
let url = format!("{}/users/{}", url_api, id);
|
||||
let request = reqwest::Client::new()
|
||||
.get(url)
|
||||
.header("Authorization", user.token());
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<PublicUser>(user)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets a user by their unique username.
|
||||
///
|
||||
/// As of 2024/07/28, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// If fetching with a pomelo username, discriminator should be set to None.
|
||||
///
|
||||
/// This route also permits fetching users with their old pre-pomelo username#discriminator
|
||||
/// combo.
|
||||
///
|
||||
/// Note:
|
||||
///
|
||||
/// "Unless the target user is a bot, you must be able to add
|
||||
/// the user as a friend to resolve them by username.
|
||||
///
|
||||
/// Due to this restriction, you are not able to resolve your own username."
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-by-username>
|
||||
pub async fn get_by_username(
|
||||
user: &mut ChorusUser,
|
||||
username: &String,
|
||||
discriminator: Option<&String>,
|
||||
) -> ChorusResult<PublicUser> {
|
||||
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
|
||||
let url = format!("{}/users/username/{username}", url_api);
|
||||
let mut request = reqwest::Client::new()
|
||||
.get(url)
|
||||
.header("Authorization", user.token());
|
||||
|
||||
if let Some(some_discriminator) = discriminator {
|
||||
request = request.query(&[("discriminator", some_discriminator)]);
|
||||
}
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<PublicUser>(user)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets the current user's settings.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/user_settings.html#get-usersmesettings>
|
||||
|
@ -129,12 +396,64 @@ impl User {
|
|||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
match chorus_request.send_request(user).await {
|
||||
Ok(result) => {
|
||||
let result_text = result.text().await.unwrap();
|
||||
Ok(serde_json::from_str(&result_text).unwrap())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
chorus_request
|
||||
.deserialize_response::<UserSettings>(user)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets a user's profile object by their id.
|
||||
///
|
||||
/// This endpoint requires one of the following:
|
||||
///
|
||||
/// - The other user is a bot
|
||||
/// - The other user shares a mutual guild with the current user
|
||||
/// - The other user is a friend of the current user
|
||||
/// - The other user is a friend suggestion of the current user
|
||||
/// - The other user has an outgoing friend request to the current user
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-profile>
|
||||
pub async fn get_profile(
|
||||
user: &mut ChorusUser,
|
||||
id: Snowflake,
|
||||
query_parameters: GetUserProfileSchema,
|
||||
) -> ChorusResult<UserProfile> {
|
||||
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
|
||||
let request: reqwest::RequestBuilder = Client::new()
|
||||
.get(format!("{}/users/{}/profile", url_api, id))
|
||||
.header("Authorization", user.token())
|
||||
.query(&query_parameters);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<UserProfile>(user)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Modifies the current user's profile.
|
||||
///
|
||||
/// Returns the updated [UserProfileMetadata].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-profile>
|
||||
pub async fn modify_profile(
|
||||
user: &mut ChorusUser,
|
||||
schema: UserModifyProfileSchema,
|
||||
) -> ChorusResult<UserProfileMetadata> {
|
||||
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
|
||||
let request: reqwest::RequestBuilder = Client::new()
|
||||
.patch(format!("{}/users/@me/profile", url_api))
|
||||
.header("Authorization", user.token())
|
||||
.json(&schema);
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
chorus_request
|
||||
.deserialize_response::<UserProfileMetadata>(user)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,9 +162,11 @@ impl Display for GuildFeaturesList {
|
|||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
|
||||
fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'r>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let v = <String as sqlx::Decode<sqlx::MySql>>::decode(value)?;
|
||||
impl<'r> sqlx::Decode<'r, sqlx::Any> for GuildFeaturesList {
|
||||
fn decode(
|
||||
value: <sqlx::Any as sqlx::Database>::ValueRef<'r>,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let v = <String as sqlx::Decode<sqlx::Any>>::decode(value)?;
|
||||
Ok(Self(
|
||||
v.split(',')
|
||||
.filter(|f| !f.is_empty())
|
||||
|
@ -175,10 +177,13 @@ impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
|
|||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
|
||||
fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Any> for GuildFeaturesList {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>,
|
||||
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
|
||||
if self.is_empty() {
|
||||
return sqlx::encode::IsNull::Yes;
|
||||
return Ok(sqlx::encode::IsNull::Yes);
|
||||
}
|
||||
let features = self
|
||||
.iter()
|
||||
|
@ -186,18 +191,18 @@ impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
|
|||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
<String as sqlx::Encode<sqlx::MySql>>::encode_by_ref(&features, buf)
|
||||
<String as sqlx::Encode<sqlx::Any>>::encode_by_ref(&features, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::MySql> for GuildFeaturesList {
|
||||
fn type_info() -> sqlx::mysql::MySqlTypeInfo {
|
||||
<String as sqlx::Type<sqlx::MySql>>::type_info()
|
||||
impl sqlx::Type<sqlx::Any> for GuildFeaturesList {
|
||||
fn type_info() -> sqlx::any::AnyTypeInfo {
|
||||
<String as sqlx::Type<sqlx::Any>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool {
|
||||
<String as sqlx::Type<sqlx::MySql>>::compatible(ty)
|
||||
fn compatible(ty: &sqlx::any::AnyTypeInfo) -> bool {
|
||||
<String as sqlx::Type<sqlx::Any>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,7 +102,10 @@ fn compare_install_params(
|
|||
b: &Option<sqlx::types::Json<InstallParams>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(),
|
||||
(Some(a), Some(b)) => match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
@ -51,7 +51,10 @@ fn compare_options(
|
|||
b: &Option<sqlx::types::Json<AuditEntryInfo>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(),
|
||||
(Some(a), Some(b)) => match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
@ -69,7 +72,10 @@ fn compare_changes(
|
|||
a: &sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
|
||||
b: &sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
|
||||
) -> bool {
|
||||
a.encode_to_string() == b.encode_to_string()
|
||||
match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
|
|
@ -156,7 +156,10 @@ fn compare_permission_overwrites(
|
|||
b: &Option<Json<Vec<PermissionOverwrite>>>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(),
|
||||
(Some(a), Some(b)) => match (a.encode_to_string(), b.encode_to_string()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
@ -444,7 +444,7 @@ pub enum VerificationLevel {
|
|||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
/// See <https://docs.discord.sex/resources/guild#mfa-level>
|
||||
pub enum MFALevel {
|
||||
#[default]
|
||||
None = 0,
|
||||
|
@ -467,7 +467,7 @@ pub enum MFALevel {
|
|||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
/// See <https://docs.discord.sex/resources/guild#nsfw-level>
|
||||
pub enum NSFWLevel {
|
||||
#[default]
|
||||
Default = 0,
|
||||
|
@ -492,12 +492,19 @@ pub enum NSFWLevel {
|
|||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
|
||||
// Note: Maybe rename this to GuildPremiumTier?
|
||||
/// **Guild** premium (Boosting) tier
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/guild#premium-tier>
|
||||
pub enum PremiumTier {
|
||||
#[default]
|
||||
/// No server boost perks
|
||||
None = 0,
|
||||
/// Level 1 server boost perks
|
||||
Tier1 = 1,
|
||||
/// Level 2 server boost perks
|
||||
Tier2 = 2,
|
||||
/// Level 3 server boost perks
|
||||
Tier3 = 3,
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::types::{
|
||||
entities::{Application, User},
|
||||
|
@ -23,7 +24,7 @@ pub struct Integration {
|
|||
pub syncing: Option<bool>,
|
||||
pub role_id: Option<String>,
|
||||
pub enabled_emoticons: Option<bool>,
|
||||
pub expire_behaviour: Option<u8>,
|
||||
pub expire_behaviour: Option<IntegrationExpireBehaviour>,
|
||||
pub expire_grace_period: Option<u16>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<User>>,
|
||||
|
@ -50,6 +51,7 @@ pub struct IntegrationAccount {
|
|||
#[serde(rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(rename_all = "snake_case"))]
|
||||
/// See <https://docs.discord.sex/resources/integration#integration-type>
|
||||
pub enum IntegrationType {
|
||||
#[default]
|
||||
Twitch,
|
||||
|
@ -57,3 +59,32 @@ pub enum IntegrationType {
|
|||
Discord,
|
||||
GuildSubscription,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// Defines the behaviour that is executed when a user's subscription to the integration expires.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/integration#integration-expire-behavior>
|
||||
pub enum IntegrationExpireBehaviour {
|
||||
#[default]
|
||||
/// Remove the subscriber role from the user
|
||||
RemoveRole = 0,
|
||||
/// Kick the user from the guild
|
||||
Kick = 1,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::types::utils::Snowflake;
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::array::TryFromSliceError;
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -22,7 +23,7 @@ use crate::gateway::GatewayHandle;
|
|||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{Composite, Updateable};
|
||||
|
||||
use super::Emoji;
|
||||
use super::{Emoji, GuildMember};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
|
@ -39,6 +40,8 @@ impl User {
|
|||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#user-structure>
|
||||
pub struct User {
|
||||
pub id: Snowflake,
|
||||
pub username: String,
|
||||
|
@ -57,8 +60,10 @@ pub struct User {
|
|||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_option_number_from_string")]
|
||||
pub flags: Option<UserFlags>,
|
||||
pub premium: Option<bool>,
|
||||
/// The type of premium (Nitro) a user has
|
||||
pub premium_type: Option<PremiumType>,
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub premium_type: Option<u8>,
|
||||
pub pronouns: Option<String>,
|
||||
pub public_flags: Option<UserFlags>,
|
||||
pub banner: Option<String>,
|
||||
|
@ -66,13 +71,15 @@ pub struct User {
|
|||
pub theme_colors: Option<ThemeColors>,
|
||||
pub phone: Option<String>,
|
||||
pub nsfw_allowed: Option<bool>,
|
||||
pub premium: Option<bool>,
|
||||
pub purchased_flags: Option<i32>,
|
||||
pub premium_usage_flags: Option<i32>,
|
||||
pub disabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)]
|
||||
/// A user's theme colors, as u32s representing hex color codes
|
||||
///
|
||||
/// found in [UserProfileMetadata]
|
||||
pub struct ThemeColors {
|
||||
#[serde(flatten)]
|
||||
inner: (u32, u32),
|
||||
|
@ -109,36 +116,38 @@ impl TryFrom<Vec<u8>> for ThemeColors {
|
|||
|
||||
#[cfg(feature = "sqlx")]
|
||||
// TODO: Add tests for Encode and Decode.
|
||||
impl<'q> sqlx::Encode<'q, sqlx::MySql> for ThemeColors {
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Any> for ThemeColors {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
||||
) -> sqlx::encode::IsNull {
|
||||
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>,
|
||||
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut vec_u8 = Vec::new();
|
||||
vec_u8.extend_from_slice(&self.inner.0.to_be_bytes());
|
||||
vec_u8.extend_from_slice(&self.inner.1.to_be_bytes());
|
||||
<Vec<u8> as sqlx::Encode<'q, sqlx::MySql>>::encode_by_ref(&vec_u8, buf)
|
||||
<Vec<u8> as sqlx::Encode<sqlx::Any>>::encode_by_ref(&vec_u8, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'d> sqlx::Decode<'d, sqlx::MySql> for ThemeColors {
|
||||
impl<'d> sqlx::Decode<'d, sqlx::Any> for ThemeColors {
|
||||
fn decode(
|
||||
value: <sqlx::MySql as sqlx::database::HasValueRef<'d>>::ValueRef,
|
||||
value: <sqlx::Any as sqlx::Database>::ValueRef<'d>,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::MySql>>::decode(value)?;
|
||||
let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::Any>>::decode(value)?;
|
||||
value_vec.try_into().map_err(|e: ChorusError| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::MySql> for ThemeColors {
|
||||
fn type_info() -> <sqlx::MySql as sqlx::Database>::TypeInfo {
|
||||
<String as sqlx::Type<sqlx::MySql>>::type_info()
|
||||
impl sqlx::Type<sqlx::Any> for ThemeColors {
|
||||
fn type_info() -> <sqlx::Any as sqlx::Database>::TypeInfo {
|
||||
<String as sqlx::Type<sqlx::Any>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#partial-user-structure>
|
||||
pub struct PublicUser {
|
||||
pub id: Snowflake,
|
||||
pub username: Option<String>,
|
||||
|
@ -150,7 +159,9 @@ pub struct PublicUser {
|
|||
pub pronouns: Option<String>,
|
||||
pub bot: Option<bool>,
|
||||
pub bio: Option<String>,
|
||||
pub premium_type: Option<u8>,
|
||||
/// The type of premium (Nitro) a user has
|
||||
pub premium_type: Option<PremiumType>,
|
||||
/// The date the user's premium (Nitro) subscribtion started
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub public_flags: Option<UserFlags>,
|
||||
}
|
||||
|
@ -181,6 +192,8 @@ const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
|
|||
bitflags::bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#user-flags>
|
||||
pub struct UserFlags: u64 {
|
||||
const DISCORD_EMPLOYEE = 1 << 0;
|
||||
const PARTNERED_SERVER_OWNER = 1 << 1;
|
||||
|
@ -194,6 +207,7 @@ bitflags::bitflags! {
|
|||
const EARLY_SUPPORTER = 1 << 9;
|
||||
const TEAM_USER = 1 << 10;
|
||||
const TRUST_AND_SAFETY = 1 << 11;
|
||||
/// Note: deprecated by Discord
|
||||
const SYSTEM = 1 << 12;
|
||||
const HAS_UNREAD_URGENT_MESSAGES = 1 << 13;
|
||||
const BUGHUNTER_LEVEL_2 = 1 << 14;
|
||||
|
@ -205,14 +219,549 @@ bitflags::bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// **User** premium (Nitro) type
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#premium-type>
|
||||
pub enum PremiumType {
|
||||
#[default]
|
||||
/// No Nitro
|
||||
None = 0,
|
||||
/// Nitro Classic
|
||||
Tier1 = 1,
|
||||
/// Nitro
|
||||
Tier2 = 2,
|
||||
/// Nitro Basic
|
||||
Tier3 = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#profile-metadata-object>
|
||||
pub struct UserProfileMetadata {
|
||||
/// The guild ID this profile applies to, if it is a guild profile.
|
||||
pub guild_id: Option<Snowflake>,
|
||||
/// The user's pronouns, up to 40 characters
|
||||
pub pronouns: String,
|
||||
/// The user's bio / description, up to 190 characters
|
||||
pub bio: Option<String>,
|
||||
/// The hash used to retrieve the user's banned from the CDN
|
||||
pub banner: Option<String>,
|
||||
/// Banner color encoded as an i32 representation of a hex color code
|
||||
pub accent_color: Option<i32>,
|
||||
/// See [ThemeColors]
|
||||
pub theme_colors: Option<ThemeColors>,
|
||||
pub popout_animation_particle_type: Option<Snowflake>,
|
||||
pub emoji: Option<Emoji>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// A user's publically facing profile
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#profile-metadata-object>
|
||||
pub struct UserProfile {
|
||||
// TODO: add profile application object
|
||||
pub user: PublicUser,
|
||||
|
||||
#[serde(rename = "user_profile")]
|
||||
pub profile_metadata: UserProfileMetadata,
|
||||
|
||||
#[serde(default)]
|
||||
pub badges: Vec<ProfileBadge>,
|
||||
|
||||
pub guild_member: Option<GuildMember>,
|
||||
|
||||
#[serde(rename = "guild_member_profile")]
|
||||
pub guild_member_profile_metadata: Option<UserProfileMetadata>,
|
||||
|
||||
#[serde(default)]
|
||||
pub guild_badges: Vec<ProfileBadge>,
|
||||
|
||||
/// The user's legacy username#discriminator, if existing and shown
|
||||
pub legacy_username: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub mutual_guilds: Vec<MutualGuild>,
|
||||
|
||||
#[serde(default)]
|
||||
pub mutual_friends: Vec<PublicUser>,
|
||||
|
||||
pub mutual_friends_count: Option<u32>,
|
||||
|
||||
// TODO: Add connections!
|
||||
// TODO: And application role connections!
|
||||
/// The type of premium (Nitro) a user has
|
||||
pub premium_type: Option<PremiumType>,
|
||||
/// The date the user's premium (Nitro) subscribtion started
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
/// The date the user's premium guild (Boosting) subscribtion started
|
||||
pub premium_guild_since: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
/// Info about a badge on a user's profile ([UserProfile])
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#profile-badge-structure>
|
||||
///
|
||||
/// For a list of know badges, see <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub struct ProfileBadge {
|
||||
/// The badge's unique id, e.g. "staff", "partner", "premium", ...
|
||||
pub id: String,
|
||||
/// Description of what the badge represents, e.g. "Discord Staff"
|
||||
pub description: String,
|
||||
/// An icon hash, to get the badge's icon from the CDN
|
||||
pub icon: String,
|
||||
/// A link (potentially used for href) for the badge.
|
||||
///
|
||||
/// e.g.:
|
||||
/// "staff" badge links to "https://discord.com/company"
|
||||
/// "certified_moderator" links to "https://discord.com/safety"
|
||||
pub link: Option<String>,
|
||||
}
|
||||
|
||||
impl PartialEq for ProfileBadge {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Note: does not include description, since it changes for some badges
|
||||
//
|
||||
// Think nitro "Subscriber since ...", "Server boosting since ..."
|
||||
self.id.eq(&other.id) && self.icon.eq(&other.icon) && self.link.eq(&other.link)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfileBadge {
|
||||
/// Returns a badge representing the "staff" badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_staff() -> Self {
|
||||
Self {
|
||||
id: "staff".to_string(),
|
||||
description: "Discord Staff".to_string(),
|
||||
icon: "5e74e9b61934fc1f67c65515d1f7e60d".to_string(),
|
||||
link: Some("https://discord.com/company".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the partnered server owner badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_partner() -> Self {
|
||||
Self {
|
||||
id: "partner".to_string(),
|
||||
description: "Partnered Server Owner".to_string(),
|
||||
icon: "3f9748e53446a137a052f3454e2de41e".to_string(),
|
||||
link: Some("https://discord.com/partners".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the certified moderator badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_certified_moderator() -> Self {
|
||||
Self {
|
||||
id: "certified_moderator".to_string(),
|
||||
description: "Moderator Programs Alumni".to_string(),
|
||||
icon: "fee1624003e2fee35cb398e125dc479b".to_string(),
|
||||
link: Some("https://discord.com/safety".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the hypesquad events badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_hypesquad() -> Self {
|
||||
Self {
|
||||
id: "hypesquad".to_string(),
|
||||
description: "HypeSquad Events".to_string(),
|
||||
icon: "bf01d1073931f921909045f3a39fd264".to_string(),
|
||||
link: Some("https://support.discord.com/hc/en-us/articles/360035962891-Profile-Badges-101#h_01GM67K5EJ16ZHYZQ5MPRW3JT3".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the hypesquad bravery badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_hypesquad_bravery() -> Self {
|
||||
Self {
|
||||
id: "hypesquad_house_1".to_string(),
|
||||
description: "HypeSquad Bravery".to_string(),
|
||||
icon: "8a88d63823d8a71cd5e390baa45efa02".to_string(),
|
||||
link: Some("https://discord.com/settings/hypesquad-online".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the hypesquad brilliance badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_hypesquad_brilliance() -> Self {
|
||||
Self {
|
||||
id: "hypesquad_house_2".to_string(),
|
||||
description: "HypeSquad Brilliance".to_string(),
|
||||
icon: "011940fd013da3f7fb926e4a1cd2e618".to_string(),
|
||||
link: Some("https://discord.com/settings/hypesquad-online".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the hypesquad balance badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_hypesquad_balance() -> Self {
|
||||
Self {
|
||||
id: "hypesquad_house_3".to_string(),
|
||||
description: "HypeSquad Balance".to_string(),
|
||||
icon: "3aa41de486fa12454c3761e8e223442e".to_string(),
|
||||
link: Some("https://discord.com/settings/hypesquad-online".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the bug hunter level 1 badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_bug_hunter_1() -> Self {
|
||||
Self {
|
||||
id: "bug_hunter_level_1".to_string(),
|
||||
description: "Discord Bug Hunter".to_string(),
|
||||
icon: "2717692c7dca7289b35297368a940dd0".to_string(),
|
||||
link: Some(
|
||||
"https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the bug hunter level 2 badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_bug_hunter_2() -> Self {
|
||||
Self {
|
||||
id: "bug_hunter_level_2".to_string(),
|
||||
description: "Discord Bug Hunter".to_string(),
|
||||
icon: "848f79194d4be5ff5f81505cbd0ce1e6".to_string(),
|
||||
link: Some(
|
||||
"https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the active developer badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_active_developer() -> Self {
|
||||
Self {
|
||||
id: "active_developer".to_string(),
|
||||
description: "Active Developer".to_string(),
|
||||
icon: "6bdc42827a38498929a4920da12695d9".to_string(),
|
||||
link: Some(
|
||||
"https://support-dev.discord.com/hc/en-us/articles/10113997751447?ref=badge"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the early verified bot developer badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_early_verified_developer() -> Self {
|
||||
Self {
|
||||
id: "verified_developer".to_string(),
|
||||
description: "Early Verified Bot Developer".to_string(),
|
||||
icon: "6df5892e0f35b051f8b61eace34f4967".to_string(),
|
||||
link: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the early supporter badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_early_supporter() -> Self {
|
||||
Self {
|
||||
id: "early_supporter".to_string(),
|
||||
description: "Early Supporter".to_string(),
|
||||
icon: "7060786766c9c840eb3019e725d2b358".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the nitro subscriber badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_nitro() -> Self {
|
||||
Self {
|
||||
id: "premium".to_string(),
|
||||
description: "Subscriber since 1 Jan 2015".to_string(),
|
||||
icon: "2ba85e8026a8614b640c2837bcdfe21b".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 1 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_1() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl1".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "51040c70d4f20a921ad6674ff86fc95c".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 2 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_2() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl2".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "0e4080d1d333bc7ad29ef6528b6f2fb7".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 3 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_3() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl3".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "72bed924410c304dbe3d00a6e593ff59".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 4 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_4() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl4".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "df199d2050d3ed4ebf84d64ae83989f8".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 5 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_5() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl5".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "996b3e870e8a22ce519b3a50e6bdd52f".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 6 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_6() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl6".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "991c9f39ee33d7537d9f408c3e53141e".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 7 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_7() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl7".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "cb3ae83c15e970e8f3d410bc62cb8b99".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 8 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_8() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl8".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "7142225d31238f6387d9f09efaa02759".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the level 9 server boosting badge on Discord.com
|
||||
///
|
||||
/// Note: The description updates for the start date
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_server_boosting_9() -> Self {
|
||||
Self {
|
||||
id: "guild_booster_lvl9".to_string(),
|
||||
description: "Server boosting since 1 Jan 2015".to_string(),
|
||||
icon: "ec92202290b48d0879b7413d2dde3bab".to_string(),
|
||||
link: Some("https://discord.com/settings/premium".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the legacy username badge on Discord.com
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_legacy_username() -> Self {
|
||||
Self {
|
||||
id: "legacy_username".to_string(),
|
||||
description: "Originally known as USERNAME".to_string(),
|
||||
icon: "6de6d34650760ba5551a79732e98ed60".to_string(),
|
||||
link: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the legacy username badge on Discord.com,
|
||||
/// with the provided username (which should already contain the #DISCRIM part)
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_legacy_username_with_username(username: String) -> Self {
|
||||
Self {
|
||||
id: "legacy_username".to_string(),
|
||||
description: format!("Originally known as {username}"),
|
||||
icon: "6de6d34650760ba5551a79732e98ed60".to_string(),
|
||||
link: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the legacy username badge on Discord.com,
|
||||
/// with the provided username and discriminator
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_legacy_username_with_username_and_discriminator(
|
||||
username: String,
|
||||
discriminator: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: "legacy_username".to_string(),
|
||||
description: format!("Originally known as {username}#{discriminator}"),
|
||||
icon: "6de6d34650760ba5551a79732e98ed60".to_string(),
|
||||
link: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the bot commands badge on Discord.com
|
||||
///
|
||||
/// Note: This badge is only for bot accounts
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_bot_commands() -> Self {
|
||||
Self {
|
||||
id: "bot_commands".to_string(),
|
||||
description: "Supports Commands".to_string(),
|
||||
icon: "6f9e37f9029ff57aef81db857890005e".to_string(),
|
||||
link: Some(
|
||||
"https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the bot automod badge on Discord.com
|
||||
///
|
||||
/// Note: This badge is only for bot accounts
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_bot_automod() -> Self {
|
||||
Self {
|
||||
id: "automod".to_string(),
|
||||
description: "Uses AutoMod".to_string(),
|
||||
icon: "f2459b691ac7453ed6039bbcfaccbfcd".to_string(),
|
||||
link: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a badge representing the application guild subscription badge on Discord.com
|
||||
///
|
||||
/// No idea where this badge could show up, but apparently it means a guild has an
|
||||
/// application's premium
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://gist.github.com/XYZenix/c45156b7c883b5301c9028e39d71b479>
|
||||
pub fn discord_application_guild_subscription() -> Self {
|
||||
Self {
|
||||
id: "application_guild_subscription".to_string(),
|
||||
description: "This server has APPLICATION Premium".to_string(),
|
||||
icon: "d2010c413a8da2208b7e4f35bd8cd4ac".to_string(),
|
||||
link: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure which shows a mutual guild with a user
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#mutual-guild-structure>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MutualGuild {
|
||||
pub id: Snowflake,
|
||||
/// The user's nickname in the guild, if any
|
||||
pub nick: Option<String>,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::collections::HashMap;
|
|||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::Snowflake;
|
||||
use crate::types::{Snowflake, ThemeColors};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -41,7 +41,12 @@ pub struct UserModifySchema {
|
|||
pub email: Option<String>,
|
||||
/// The user's email token from their previous email, required if a new email is set.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-email> and <https://docs.discord.sex/resources/user#verify-user-email-change>
|
||||
/// See:
|
||||
///
|
||||
/// - the endpoints <https://docs.discord.sex/resources/user#modify-user-email> and <https://docs.discord.sex/resources/user#verify-user-email-change>
|
||||
///
|
||||
/// - the relevant methods [`ChorusUser::initiate_email_change`](crate::instance::ChorusUser::initiate_email_change) and [`ChorusUser::verify_email_change`](crate::instance::ChorusUser::verify_email_change)
|
||||
///
|
||||
/// for changing the user's email.
|
||||
///
|
||||
/// # Note
|
||||
|
@ -106,3 +111,120 @@ pub struct PrivateChannelCreateSchema {
|
|||
pub access_tokens: Option<Vec<String>>,
|
||||
pub nicks: Option<HashMap<Snowflake, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// A schema used to modify the current user's profile.
|
||||
///
|
||||
/// Similar to [crate::types::UserProfileMetadata]
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-profile>
|
||||
pub struct UserModifyProfileSchema {
|
||||
// Note: one of these causes a 500 if it is sent
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// The user's new pronouns (max 40 characters)
|
||||
pub pronouns: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// The user's new bio (max 190 characters)
|
||||
pub bio: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
// TODO: Add banner -- do we have an image data struct
|
||||
/// The user's new accent color encoded as an i32 representation of a hex color code
|
||||
pub accent_color: Option<i32>,
|
||||
|
||||
// Note: without the skip serializing this currently (2024/07/28) causes a 500!
|
||||
//
|
||||
// Which in turns locks the user's account, requiring phone number verification
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// The user's new [ThemeColors]
|
||||
pub theme_colors: Option<ThemeColors>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// The user's new profile popup animation particle type
|
||||
pub popout_animation_particle_type: Option<Snowflake>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// The user's new profile emoji id
|
||||
pub emoji_id: Option<Snowflake>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// The user's new profile ffect id
|
||||
pub profile_effect_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// A schema used to delete or disable the current user's profile.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#disable-user> and
|
||||
/// <https://docs.discord.sex/resources/user#delete-user>
|
||||
pub struct DeleteDisableUserSchema {
|
||||
/// The user's current password, if any
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// A schema used for [ChorusUser::verify_email_change](crate::instance::ChorusUser::verify_email_change)
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#verify-user-email-change>
|
||||
pub struct VerifyUserEmailChangeSchema {
|
||||
/// The verification code sent to the user's email
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// The return type of [ChorusUser::verify_email_change](crate::instance::ChorusUser::verify_email_change)
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#verify-user-email-change>
|
||||
pub struct VerifyUserEmailChangeResponse {
|
||||
/// The email_token to be used in [ChorusUser::modify](crate::instance::ChorusUser::modify)
|
||||
#[serde(rename = "token")]
|
||||
pub email_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
|
||||
/// Query string parameters for the route GET /users/{user.id}/profile
|
||||
/// ([crate::types::User::get_profile])
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-profile>
|
||||
pub struct GetUserProfileSchema {
|
||||
/// Whether to include the mutual guilds between the current user.
|
||||
///
|
||||
/// If unset it will default to true
|
||||
pub with_mutual_guilds: Option<bool>,
|
||||
/// Whether to include the mutual friends between the current user.
|
||||
///
|
||||
/// If unset it will default to false
|
||||
pub with_mutual_friends: Option<bool>,
|
||||
/// Whether to include the number of mutual friends between the current user
|
||||
///
|
||||
/// If unset it will default to false
|
||||
pub with_mutual_friends_count: Option<bool>,
|
||||
/// The guild id to get the user's member profile in, if any.
|
||||
///
|
||||
/// Note:
|
||||
///
|
||||
/// when you click on a user in the member list in the discord client, a request is sent with
|
||||
/// this property set to the selected guild id.
|
||||
///
|
||||
/// This makes the request include fields such as guild_member and guild_member_profile
|
||||
pub guild_id: Option<Snowflake>,
|
||||
/// The role id to get the user's application role connection metadata in, if any.
|
||||
pub connections_role_id: Option<Snowflake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Internal type for the [crate::instance::ChorusUser::get_pomelo_suggestions] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-pomelo-suggestions>
|
||||
pub(crate) struct GetPomeloSuggestionsReturn {
|
||||
pub username: String
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Internal type for the [crate::instance::ChorusUser::get_pomelo_eligibility] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-pomelo-eligibility>
|
||||
pub(crate) struct GetPomeloEligibilityReturn {
|
||||
pub taken: bool
|
||||
}
|
||||
|
|
|
@ -99,23 +99,29 @@ impl<'de> serde::Deserialize<'de> for Snowflake {
|
|||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::MySql> for Snowflake {
|
||||
fn type_info() -> <sqlx::MySql as sqlx::Database>::TypeInfo {
|
||||
<String as sqlx::Type<sqlx::MySql>>::type_info()
|
||||
impl sqlx::Type<sqlx::Any> for Snowflake {
|
||||
fn type_info() -> <sqlx::Any as sqlx::Database>::TypeInfo {
|
||||
<String as sqlx::Type<sqlx::Any>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Encode<'q, sqlx::MySql> for Snowflake {
|
||||
fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
|
||||
<String as sqlx::Encode<'q, sqlx::MySql>>::encode_by_ref(&self.0.to_string(), buf)
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Any> for Snowflake {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>,
|
||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||
<String as sqlx::Encode<'q, sqlx::Any>>::encode_by_ref(&self.0.to_string(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'d> sqlx::Decode<'d, sqlx::MySql> for Snowflake {
|
||||
fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'d>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
<String as sqlx::Decode<'d, sqlx::MySql>>::decode(value).map(|s| s.parse::<u64>().map(Snowflake).unwrap())
|
||||
impl<'d> sqlx::Decode<'d, sqlx::Any> for Snowflake {
|
||||
fn decode(
|
||||
value: <sqlx::Any as sqlx::Database>::ValueRef<'d>,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
<String as sqlx::Decode<'d, sqlx::Any>>::decode(value)
|
||||
.map(|s| s.parse::<u64>().map(Snowflake).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use chorus::gateway::{Gateway, GatewayOptions};
|
||||
use chorus::types::{IntoShared, PermissionFlags};
|
||||
use chorus::types::{DeleteDisableUserSchema, IntoShared, PermissionFlags};
|
||||
use chorus::{
|
||||
instance::{ChorusUser, Instance},
|
||||
types::{
|
||||
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
|
||||
RoleCreateModifySchema, RoleObject, Shared
|
||||
RoleCreateModifySchema, RoleObject, Shared,
|
||||
},
|
||||
UrlBundle,
|
||||
};
|
||||
|
@ -59,9 +59,12 @@ impl TestBundle {
|
|||
|
||||
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
||||
pub(crate) async fn setup() -> TestBundle {
|
||||
|
||||
// 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();
|
||||
// Requires the existence of the below user.
|
||||
|
@ -141,5 +144,9 @@ pub(crate) async fn setup() -> TestBundle {
|
|||
pub(crate) async fn teardown(mut bundle: TestBundle) {
|
||||
let id = bundle.guild.read().unwrap().id;
|
||||
Guild::delete(&mut bundle.user, id).await.unwrap();
|
||||
bundle.user.delete().await.unwrap()
|
||||
bundle
|
||||
.user
|
||||
.delete(DeleteDisableUserSchema { password: None })
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue