Merge a9b1a61d84
into ec9541f38e
This commit is contained in:
commit
b29d987ee4
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,390 @@
|
|||
use futures_util::FutureExt;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::{
|
||||
errors::{ChorusError, ChorusResult},
|
||||
instance::ChorusUser,
|
||||
ratelimiter::ChorusRequest,
|
||||
types::{
|
||||
AuthorizeConnectionReturn, AuthorizeConnectionSchema, Connection, ConnectionSubreddit,
|
||||
ConnectionType, CreateConnectionCallbackSchema, CreateContactSyncConnectionSchema,
|
||||
CreateDomainConnectionError, CreateDomainConnectionReturn, GetConnectionAccessTokenReturn,
|
||||
LimitType, ModifyConnectionSchema,
|
||||
},
|
||||
};
|
||||
|
||||
impl ChorusUser {
|
||||
/// Fetches a url that can be used for authorizing a new connection.
|
||||
///
|
||||
/// The user should then visit the url and authenticate to create the connection.
|
||||
///
|
||||
/// # Notes
|
||||
/// This route seems to be preferred by the official infrastructure (client) to
|
||||
/// [Self::create_connection_callback].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#authorize-user-connection>
|
||||
///
|
||||
/// Note: it doesn't seem to be actually unauthenticated
|
||||
pub async fn authorize_connection(
|
||||
&mut self,
|
||||
connection_type: ConnectionType,
|
||||
query_parameters: AuthorizeConnectionSchema,
|
||||
) -> ChorusResult<String> {
|
||||
let connection_type_string = serde_json::to_string(&connection_type)
|
||||
.expect("Failed to serialize connection type!")
|
||||
.replace('"', "");
|
||||
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/connections/{}/authorize",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_type_string
|
||||
))
|
||||
// Note: ommiting this header causes a 401 Unauthorized,
|
||||
// even though discord.sex mentions it as unauthenticated
|
||||
.header("Authorization", self.token())
|
||||
.query(&query_parameters);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request
|
||||
.deserialize_response::<AuthorizeConnectionReturn>(self)
|
||||
.await
|
||||
.map(|response| response.url)
|
||||
}
|
||||
|
||||
/// Creates a new connection for the current user.
|
||||
///
|
||||
/// # Notes
|
||||
/// The official infrastructure (client) prefers the route
|
||||
/// [Self::authorize_connection] to this one.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#create-user-connection-callback>
|
||||
// TODO: When is this called? When should it be used over authorize_connection?
|
||||
pub async fn create_connection_callback(
|
||||
&mut self,
|
||||
connection_type: ConnectionType,
|
||||
json_schema: CreateConnectionCallbackSchema,
|
||||
) -> ChorusResult<Connection> {
|
||||
let connection_type_string = serde_json::to_string(&connection_type)
|
||||
.expect("Failed to serialize connection type!")
|
||||
.replace('"', "");
|
||||
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/connections/{}/callback",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_type_string
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.json(&json_schema);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Creates a new contact sync connection for the current user.
|
||||
///
|
||||
/// # Notes
|
||||
/// To create normal connection types, see [Self::authorize_connection] and
|
||||
/// [Self::create_connection_callback]
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#create-contact-sync-connection>
|
||||
pub async fn create_contact_sync_connection(
|
||||
&mut self,
|
||||
connection_account_id: &String,
|
||||
json_schema: CreateContactSyncConnectionSchema,
|
||||
) -> ChorusResult<Connection> {
|
||||
let request = Client::new()
|
||||
.put(format!(
|
||||
"{}/users/@me/connections/contacts/{}",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_account_id
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.json(&json_schema);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Creates a new domain connection for the current user.
|
||||
///
|
||||
/// This route has two possible successful return values:
|
||||
/// [CreateDomainConnectionReturn::Ok] and [CreateDomainConnectionReturn::ProofNeeded]
|
||||
///
|
||||
/// To properly handle both, please see their respective documentation pages.
|
||||
///
|
||||
/// # Notes
|
||||
/// To create normal connection types, see [Self::authorize_connection] and
|
||||
/// [Self::create_connection_callback]
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// let domain = "example.com".to_string();
|
||||
///
|
||||
/// let result = user.create_domain_connection(&domain).await;
|
||||
///
|
||||
/// if let Ok(returned) = result {
|
||||
/// match returned {
|
||||
/// CreateDomainConnectionReturn::ProofNeeded(proof) => {
|
||||
/// println!("Additional proof needed!");
|
||||
/// println!("Either:");
|
||||
/// println!("");
|
||||
/// println!("- create a DNS TXT record with the name _discord.{domain} and content {proof}");
|
||||
/// println!("or");
|
||||
/// println!("- create a file at https://{domain}/.well-known/discord with the content {proof}");
|
||||
/// // Once the user has added the proof, retry calling the endpoint
|
||||
/// }
|
||||
/// CreateDomainConnectionReturn::Ok(connection) => {
|
||||
/// println!("Successfulyl created connection! {:?}", connection);
|
||||
/// }
|
||||
/// }
|
||||
/// } else {
|
||||
/// println!("Failed to create connection: {:?}", result);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#create-domain-connection>
|
||||
pub async fn create_domain_connection(
|
||||
&mut self,
|
||||
domain: &String,
|
||||
) -> ChorusResult<CreateDomainConnectionReturn> {
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/connections/domain/{}",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
domain
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
let result = chorus_request
|
||||
.deserialize_response::<Connection>(self)
|
||||
.await;
|
||||
|
||||
if let Ok(connection) = result {
|
||||
return Ok(CreateDomainConnectionReturn::Ok(connection));
|
||||
}
|
||||
|
||||
let error = result.err().unwrap();
|
||||
|
||||
if let ChorusError::ReceivedErrorCode {
|
||||
error_code,
|
||||
error: ref error_string,
|
||||
} = error
|
||||
{
|
||||
if error_code == 400 {
|
||||
let try_deserialize: Result<CreateDomainConnectionError, serde_json::Error> =
|
||||
serde_json::from_str(error_string);
|
||||
|
||||
if let Ok(deserialized_error) = try_deserialize {
|
||||
return Ok(CreateDomainConnectionReturn::ProofNeeded(
|
||||
deserialized_error.proof,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(error)
|
||||
}
|
||||
|
||||
// TODO: Add create_domain_connection (<https://docs.discord.sex/resources/user#create-domain-connection>)
|
||||
// It requires changing how chorus handles errors to support properly
|
||||
|
||||
/// Fetches the current user's [Connection]s
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-connections>
|
||||
pub async fn get_connections(&mut self) -> ChorusResult<Vec<Connection>> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/connections",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Refreshes a local user's [Connection].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#refresh-user-connection>
|
||||
pub async fn refresh_connection(
|
||||
&mut self,
|
||||
connection_type: ConnectionType,
|
||||
connection_account_id: &String,
|
||||
) -> ChorusResult<()> {
|
||||
let connection_type_string = serde_json::to_string(&connection_type)
|
||||
.expect("Failed to serialize connection type!")
|
||||
.replace('"', "");
|
||||
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/connections/{}/{}/refresh",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_type_string,
|
||||
connection_account_id
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.handle_request_as_result(self).await
|
||||
}
|
||||
|
||||
/// Changes settings on a local user's [Connection].
|
||||
///
|
||||
/// # Notes
|
||||
/// Not all connection types support all parameters.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-connection>
|
||||
pub async fn modify_connection(
|
||||
&mut self,
|
||||
connection_type: ConnectionType,
|
||||
connection_account_id: &String,
|
||||
json_schema: ModifyConnectionSchema,
|
||||
) -> ChorusResult<Connection> {
|
||||
let connection_type_string = serde_json::to_string(&connection_type)
|
||||
.expect("Failed to serialize connection type!")
|
||||
.replace('"', "");
|
||||
|
||||
let request = Client::new()
|
||||
.patch(format!(
|
||||
"{}/users/@me/connections/{}/{}",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_type_string,
|
||||
connection_account_id
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.json(&json_schema);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Deletes a local user's [Connection].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#delete-user-connection>
|
||||
pub async fn delete_connection(
|
||||
&mut self,
|
||||
connection_type: ConnectionType,
|
||||
connection_account_id: &String,
|
||||
) -> ChorusResult<()> {
|
||||
let connection_type_string = serde_json::to_string(&connection_type)
|
||||
.expect("Failed to serialize connection type!")
|
||||
.replace('"', "");
|
||||
|
||||
let request = Client::new()
|
||||
.delete(format!(
|
||||
"{}/users/@me/connections/{}/{}",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_type_string,
|
||||
connection_account_id
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.handle_request_as_result(self).await
|
||||
}
|
||||
|
||||
/// Returns a new access token for the given connection.
|
||||
///
|
||||
/// Only available for [ConnectionType::Twitch], [ConnectionType::YouTube] and [ConnectionType::Spotify] connections.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-connection-access-token>
|
||||
pub async fn get_connection_access_token(
|
||||
&mut self,
|
||||
connection_type: ConnectionType,
|
||||
connection_account_id: &String,
|
||||
) -> ChorusResult<String> {
|
||||
let connection_type_string = serde_json::to_string(&connection_type)
|
||||
.expect("Failed to serialize connection type!")
|
||||
.replace('"', "");
|
||||
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/connections/{}/{}/access-token",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_type_string,
|
||||
connection_account_id
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request
|
||||
.deserialize_response::<GetConnectionAccessTokenReturn>(self)
|
||||
.await
|
||||
.map(|res| res.access_token)
|
||||
}
|
||||
|
||||
/// Fetches a list of [subreddits](crate::types::ConnectionSubreddit)
|
||||
/// the connected account moderates.
|
||||
///
|
||||
/// Only available for [ConnectionType::Reddit] connections.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-connection-subreddits>
|
||||
pub async fn get_connection_subreddits(
|
||||
&mut self,
|
||||
connection_account_id: &String,
|
||||
) -> ChorusResult<Vec<ConnectionSubreddit>> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/connections/reddit/{}/subreddits",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
connection_account_id
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
}
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
pub use channels::*;
|
||||
pub use connections::*;
|
||||
pub use guilds::*;
|
||||
pub use relationships::*;
|
||||
pub use users::*;
|
||||
|
||||
pub mod channels;
|
||||
pub mod connections;
|
||||
pub mod guilds;
|
||||
pub mod relationships;
|
||||
pub mod users;
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use reqwest::Client;
|
||||
use serde_json::to_string;
|
||||
|
@ -11,22 +14,69 @@ use crate::{
|
|||
errors::{ChorusError, ChorusResult},
|
||||
instance::{ChorusUser, Instance},
|
||||
ratelimiter::ChorusRequest,
|
||||
types::{LimitType, User, UserModifySchema, UserSettings},
|
||||
types::{
|
||||
AuthorizeConnectionSchema, BurstCreditsInfo, ConnectionType, CreateUserHarvestSchema,
|
||||
DeleteDisableUserSchema, GetPomeloEligibilityReturn, GetPomeloSuggestionsReturn,
|
||||
GetRecentMentionsSchema, GetUserProfileSchema, GuildAffinities, Harvest,
|
||||
HarvestBackendType, LimitType, ModifyUserNoteSchema, PremiumUsage, PublicUser, Snowflake,
|
||||
User, UserAffinities, UserModifyProfileSchema, UserModifySchema, UserNote, 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 +90,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 +116,566 @@ 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.
|
||||
///
|
||||
/// This endpoint returns a token which can be used with [Self::modify]
|
||||
/// to set a new email address (email_token).
|
||||
///
|
||||
/// # Notes
|
||||
/// Should be the follow-up to [Self::initiate_email_change]
|
||||
///
|
||||
/// As of 2024/08/08, Spacebar does not yet implement this endpoint.
|
||||
// FIXME: Does this mean PUT users/@me/email is different?
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Notes:
|
||||
/// "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])
|
||||
///
|
||||
/// As of 2024/08/08, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// 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.
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/08, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Migrates the user from the username#discriminator to the unique username system.
|
||||
///
|
||||
/// Fires a [UserUpdate](crate::types::UserUpdate) gateway event.
|
||||
///
|
||||
/// Updates [Self::object] to an updated representation returned by the server.
|
||||
// FIXME: Is this appropriate behaviour?
|
||||
///
|
||||
/// # Notes
|
||||
/// "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])
|
||||
//
|
||||
/// As of 2024/08/08, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#create-pomelo-migration>
|
||||
pub async fn create_pomelo_migration(&mut self, username: &String) -> ChorusResult<()> {
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/pomelo",
|
||||
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(),
|
||||
};
|
||||
|
||||
let result = chorus_request.deserialize_response::<User>(self).await;
|
||||
|
||||
// FIXME: Does UserUpdate do this automatically? or would a user need to manually observe ChorusUser::object
|
||||
if let Ok(new_object) = result {
|
||||
*self.object.write().unwrap() = new_object;
|
||||
return ChorusResult::Ok(());
|
||||
}
|
||||
|
||||
ChorusResult::Err(result.err().unwrap())
|
||||
}
|
||||
|
||||
/// Fetches a list of [Message](crate::types::Message)s that the current user has been
|
||||
/// mentioned in during the last 7 days.
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/09, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-recent-mentions>
|
||||
pub async fn get_recent_mentions(
|
||||
&mut self,
|
||||
query_parameters: GetRecentMentionsSchema,
|
||||
) -> ChorusResult<Vec<crate::types::Message>> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/mentions",
|
||||
self.belongs_to.read().unwrap().urls.api
|
||||
))
|
||||
.header("Authorization", self.token())
|
||||
.query(&query_parameters);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request
|
||||
.deserialize_response::<Vec<crate::types::Message>>(self)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Acknowledges a message the current user has been mentioned in.
|
||||
///
|
||||
/// Fires a `RecentMentionDelete` gateway event. (Note: yet to be implemented in chorus, see [#545](https://github.com/polyphony-chat/chorus/issues/545))
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/09, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#delete-recent-mention>
|
||||
pub async fn delete_recent_mention(&mut self, message_id: Snowflake) -> ChorusResult<()> {
|
||||
let request = Client::new()
|
||||
.delete(format!(
|
||||
"{}/users/@me/mentions/{}",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
message_id
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.handle_request_as_result(self).await
|
||||
}
|
||||
|
||||
/// If it exists, returns the most recent [Harvest] (personal data harvest request).
|
||||
///
|
||||
/// To create a new [Harvest], see [Self::create_harvest].
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/09, Spacebar does not yet implement this endpoint. (Or data harvesting)
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-harvest>
|
||||
pub async fn get_harvest(&mut self) -> ChorusResult<Option<Harvest>> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/harvest",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
// Manual handling, because a 204 with no harvest is a success state
|
||||
// TODO: Maybe make this a method on ChorusRequest if we need it a lot
|
||||
let response = chorus_request.send_request(self).await?;
|
||||
log::trace!("Got response: {:?}", response);
|
||||
|
||||
if response.status() == http::StatusCode::NO_CONTENT {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let response_text = match response.text().await {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
return Err(ChorusError::InvalidResponse {
|
||||
error: format!(
|
||||
"Error while trying to process the HTTP response into a String: {}",
|
||||
e
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let object = match serde_json::from_str::<Harvest>(&response_text) {
|
||||
Ok(object) => object,
|
||||
Err(e) => {
|
||||
return Err(ChorusError::InvalidResponse {
|
||||
error: format!(
|
||||
"Error while trying to deserialize the JSON response into requested type T: {}. JSON Response: {}",
|
||||
e, response_text
|
||||
),
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok(Some(object))
|
||||
}
|
||||
|
||||
/// Creates a personal data harvest request ([Harvest]) for the current user.
|
||||
///
|
||||
/// # Notes
|
||||
/// To fetch the latest existing harvest, see [Self::get_harvest].
|
||||
///
|
||||
/// Invalid options in the backends array are ignored.
|
||||
///
|
||||
/// If the array is empty (after ignoring), it requests all [HarvestBackendType]s.
|
||||
///
|
||||
/// As of 2024/08/09, Spacebar does not yet implement this endpoint. (Or data harvesting)
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#create-user-harvest>
|
||||
pub async fn create_harvest(
|
||||
&mut self,
|
||||
backends: Vec<HarvestBackendType>,
|
||||
) -> ChorusResult<Harvest> {
|
||||
let schema = if backends.is_empty() {
|
||||
CreateUserHarvestSchema { backends: None }
|
||||
} else {
|
||||
CreateUserHarvestSchema {
|
||||
backends: Some(backends),
|
||||
}
|
||||
};
|
||||
|
||||
let request = Client::new()
|
||||
.post(format!(
|
||||
"{}/users/@me/harvest",
|
||||
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(self).await
|
||||
}
|
||||
|
||||
/// Returns a mapping of user IDs ([Snowflake]s) to notes ([String]s) for the current user.
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/21, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-notes>
|
||||
pub async fn get_user_notes(&mut self) -> ChorusResult<HashMap<Snowflake, String>> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/notes",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Fetches the note ([UserNote]) for the given user.
|
||||
///
|
||||
/// If the current user has no note for the target, this endpoint
|
||||
/// returns `Err(NotFound { error: "{\"message\": \"Unknown User\", \"code\": 10013}" })`
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get_note`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-note>
|
||||
pub async fn get_user_note(&mut self, target_user_id: Snowflake) -> ChorusResult<UserNote> {
|
||||
User::get_note(self, target_user_id).await
|
||||
}
|
||||
|
||||
/// Sets the note for the given user.
|
||||
///
|
||||
/// Fires a `UserNoteUpdate` gateway event. (Note: yet to be implemented in chorus, see [#546](https://github.com/polyphony-chat/chorus/issues/546))
|
||||
///
|
||||
/// # Notes
|
||||
/// This function is a wrapper around [`User::set_note`].
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-note>
|
||||
pub async fn set_user_note(
|
||||
&mut self,
|
||||
target_user_id: Snowflake,
|
||||
note: Option<String>,
|
||||
) -> ChorusResult<()> {
|
||||
User::set_note(self, target_user_id, note).await
|
||||
}
|
||||
|
||||
/// Fetches the current user's affinity scores for other users.
|
||||
///
|
||||
/// (Affinity scores are a measure of how likely a user is to be friends with another user.)
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-affinities>
|
||||
pub async fn get_user_affinities(&mut self) -> ChorusResult<UserAffinities> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/affinities/users",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Fetches the current user's affinity scores for their joined guilds.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-guild-affinities>
|
||||
pub async fn get_guild_affinities(&mut self) -> ChorusResult<GuildAffinities> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/affinities/guilds",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Fetches the current user's usage of various premium perks ([PremiumUsage] object).
|
||||
///
|
||||
/// The local user must have premium (nitro), otherwise the request will fail
|
||||
/// with a 404 NotFound error and the message {"message": "Premium usage not available", "code": 10084}.
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/16, Spacebar does not yet implement this endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-premium-usage>
|
||||
pub async fn get_premium_usage(&mut self) -> ChorusResult<PremiumUsage> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/premium-usage",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
|
||||
/// Fetches info about the current user's burst credits
|
||||
/// (how many are remaining, when they will replenish).
|
||||
///
|
||||
/// Burst credits are used to create burst reactions.
|
||||
///
|
||||
/// # Notes
|
||||
/// As of 2024/08/18, Spacebar does not yet implement this endpoint.
|
||||
pub async fn get_burst_credits(&mut self) -> ChorusResult<BurstCreditsInfo> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/burst-credits",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
))
|
||||
.header("Authorization", self.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(self).await
|
||||
}
|
||||
}
|
||||
|
||||
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 +683,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 +760,121 @@ 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())
|
||||
chorus_request
|
||||
.deserialize_response::<UserSettings>(user)
|
||||
.await
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Fetches the note ([UserNote]) for the given user.
|
||||
///
|
||||
/// If the current user has no note for the target, this endpoint
|
||||
/// returns `Err(NotFound { error: "{\"message\": \"Unknown User\", \"code\": 10013}" })`
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-note>
|
||||
pub async fn get_note(
|
||||
user: &mut ChorusUser,
|
||||
target_user_id: Snowflake,
|
||||
) -> ChorusResult<UserNote> {
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/users/@me/notes/{}",
|
||||
user.belongs_to.read().unwrap().urls.api,
|
||||
target_user_id
|
||||
))
|
||||
.header("Authorization", user.token());
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.deserialize_response(user).await
|
||||
}
|
||||
|
||||
/// Sets the note for the given user.
|
||||
///
|
||||
/// Fires a `UserNoteUpdate` gateway event. (Note: yet to be implemented in chorus, see [#546](https://github.com/polyphony-chat/chorus/issues/546))
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-note>
|
||||
pub async fn set_note(
|
||||
user: &mut ChorusUser,
|
||||
target_user_id: Snowflake,
|
||||
note: Option<String>,
|
||||
) -> ChorusResult<()> {
|
||||
let schema = ModifyUserNoteSchema { note };
|
||||
|
||||
let request = Client::new()
|
||||
.put(format!(
|
||||
"{}/users/@me/notes/{}",
|
||||
user.belongs_to.read().unwrap().urls.api,
|
||||
target_user_id
|
||||
))
|
||||
.header("Authorization", user.token())
|
||||
.json(&schema);
|
||||
|
||||
let chorus_request = ChorusRequest {
|
||||
request,
|
||||
limit_type: LimitType::default(),
|
||||
};
|
||||
|
||||
chorus_request.handle_request_as_result(user).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,12 +69,15 @@ pub struct Message {
|
|||
pub reaction_remove: Publisher<types::MessageReactionRemove>,
|
||||
pub reaction_remove_all: Publisher<types::MessageReactionRemoveAll>,
|
||||
pub reaction_remove_emoji: Publisher<types::MessageReactionRemoveEmoji>,
|
||||
pub recent_mention_delete: Publisher<types::RecentMentionDelete>,
|
||||
pub ack: Publisher<types::MessageACK>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct User {
|
||||
pub update: Publisher<types::UserUpdate>,
|
||||
pub connections_update: Publisher<types::UserConnectionsUpdate>,
|
||||
pub note_update: Publisher<types::UserNoteUpdate>,
|
||||
pub guild_settings_update: Publisher<types::UserGuildSettingsUpdate>,
|
||||
pub presence_update: Publisher<types::PresenceUpdate>,
|
||||
pub typing_start: Publisher<types::TypingStartEvent>,
|
||||
|
|
|
@ -404,6 +404,7 @@ impl Gateway {
|
|||
"MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO
|
||||
"MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO
|
||||
"MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO
|
||||
"RECENT_MENTION_DELETE" => message.recent_mention_delete,
|
||||
"MESSAGE_ACK" => message.ack,
|
||||
"PRESENCE_UPDATE" => user.presence_update, // TODO
|
||||
"RELATIONSHIP_ADD" => relationship.add,
|
||||
|
@ -413,6 +414,8 @@ impl Gateway {
|
|||
"STAGE_INSTANCE_DELETE" => stage_instance.delete,
|
||||
"TYPING_START" => user.typing_start,
|
||||
"USER_UPDATE" => user.update, // TODO
|
||||
"USER_CONNECTIONS_UPDATE" => user.connections_update, // TODO
|
||||
"USER_NOTE_UPDATE" => user.note_update,
|
||||
"USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update,
|
||||
"VOICE_STATE_UPDATE" => voice.state_update, // TODO
|
||||
"VOICE_SERVER_UPDATE" => voice.server_update,
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
/// A 3rd party service connection to a user's account.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#connection-object>
|
||||
// TODO: Should (could) this type be Updateable and Composite?
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct Connection {
|
||||
/// The id of the account on the 3rd party service
|
||||
#[serde(rename = "id")]
|
||||
pub connected_account_id: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub connection_type: ConnectionType,
|
||||
|
||||
/// The username of the connection account
|
||||
pub name: String,
|
||||
|
||||
/// If the connection is verified
|
||||
pub verified: bool,
|
||||
|
||||
/// Service specific metadata about the connection / connected account
|
||||
// FIXME: Is there a better type? As far as I see the value is always encoded as a string
|
||||
pub metadata: Option<HashMap<String, String>>,
|
||||
pub metadata_visibility: ConnectionVisibilityType,
|
||||
|
||||
/// If the connection if revoked
|
||||
pub revoked: bool,
|
||||
|
||||
// TODO: Add integrations
|
||||
pub friend_sync: bool,
|
||||
|
||||
/// Whether activities related to this connection will be shown in presence
|
||||
pub show_activity: bool,
|
||||
|
||||
/// Whether this connection has a corresponding 3rd party OAuth2 token
|
||||
pub two_way_link: bool,
|
||||
|
||||
pub visibility: ConnectionVisibilityType,
|
||||
|
||||
/// The access token for the connection account
|
||||
///
|
||||
/// Note: not included when fetching a user's connections via OAuth2
|
||||
pub access_token: Option<String>,
|
||||
}
|
||||
|
||||
/// A partial / public [Connection] type.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#partial-connection-structure>
|
||||
// FIXME: Should (could) this type also be Updateable and Composite?
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PublicConnection {
|
||||
/// The id of the account on the 3rd party service
|
||||
#[serde(rename = "id")]
|
||||
pub connected_account_id: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub connection_type: ConnectionType,
|
||||
|
||||
/// The username of the connection account
|
||||
pub name: String,
|
||||
|
||||
/// If the connection is verified
|
||||
pub verified: bool,
|
||||
|
||||
/// Service specific metadata about the connection / connected account
|
||||
// FIXME: Is there a better type? As far as I see the value is always encoded as a string
|
||||
pub metadata: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl From<Connection> for PublicConnection {
|
||||
fn from(value: Connection) -> Self {
|
||||
Self {
|
||||
connected_account_id: value.connected_account_id,
|
||||
connection_type: value.connection_type,
|
||||
name: value.name,
|
||||
verified: value.verified,
|
||||
metadata: value.metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// A type of connection; the service the connection is for
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#connection-type>
|
||||
pub enum ConnectionType {
|
||||
#[serde(rename = "amazon-music")]
|
||||
AmazonMusic,
|
||||
/// Battle.net
|
||||
BattleNet,
|
||||
/// Bungie.net
|
||||
Bungie,
|
||||
/// Discord?'s contact sync
|
||||
///
|
||||
/// (Not returned in Get User Profile or when fetching connections)
|
||||
Contacts,
|
||||
Crunchyroll,
|
||||
Domain,
|
||||
Ebay,
|
||||
EpicGames,
|
||||
Facebook,
|
||||
GitHub,
|
||||
Instagram,
|
||||
LeagueOfLegends,
|
||||
PayPal,
|
||||
/// Playstation network
|
||||
Playstation,
|
||||
Reddit,
|
||||
Roblox,
|
||||
RiotGames,
|
||||
/// Samsung Galaxy
|
||||
///
|
||||
/// Users can no longer add this service
|
||||
Samsung,
|
||||
Spotify,
|
||||
/// Users can no longer add this service
|
||||
Skype,
|
||||
Steam,
|
||||
TikTok,
|
||||
Twitch,
|
||||
Twitter,
|
||||
Xbox,
|
||||
YouTube,
|
||||
}
|
||||
|
||||
impl Display for ConnectionType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Self::AmazonMusic => f.write_str("Amazon Music"),
|
||||
Self::BattleNet => f.write_str("Battle.net"),
|
||||
Self::Bungie => f.write_str("Bungie.net"),
|
||||
Self::Ebay => f.write_str("eBay"),
|
||||
Self::EpicGames => f.write_str("Epic Games"),
|
||||
Self::LeagueOfLegends => f.write_str("League of Legends"),
|
||||
Self::Playstation => f.write_str("PlayStation Network"),
|
||||
Self::RiotGames => f.write_str("Riot Games"),
|
||||
Self::Samsung => f.write_str("Samsung Galaxy"),
|
||||
_ => f.write_str(format!("{:?}", self).as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionType {
|
||||
/// Returns an array of all the connections
|
||||
pub fn array() -> [ConnectionType; 25] {
|
||||
[
|
||||
ConnectionType::AmazonMusic,
|
||||
ConnectionType::BattleNet,
|
||||
ConnectionType::Bungie,
|
||||
ConnectionType::Contacts,
|
||||
ConnectionType::Crunchyroll,
|
||||
ConnectionType::Domain,
|
||||
ConnectionType::Ebay,
|
||||
ConnectionType::EpicGames,
|
||||
ConnectionType::Facebook,
|
||||
ConnectionType::GitHub,
|
||||
ConnectionType::Instagram,
|
||||
ConnectionType::LeagueOfLegends,
|
||||
ConnectionType::PayPal,
|
||||
ConnectionType::Playstation,
|
||||
ConnectionType::Reddit,
|
||||
ConnectionType::RiotGames,
|
||||
ConnectionType::Samsung,
|
||||
ConnectionType::Spotify,
|
||||
ConnectionType::Skype,
|
||||
ConnectionType::Steam,
|
||||
ConnectionType::TikTok,
|
||||
ConnectionType::Twitch,
|
||||
ConnectionType::Twitter,
|
||||
ConnectionType::Xbox,
|
||||
ConnectionType::YouTube,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize_repr, Deserialize_repr, Debug, Clone, Eq, PartialEq, Hash, Copy, PartialOrd, Ord,
|
||||
)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#visibility-type>
|
||||
pub enum ConnectionVisibilityType {
|
||||
/// Invisible to everyone except the user themselves
|
||||
None = 0,
|
||||
/// Visible to everyone
|
||||
Everyone = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// A type of two-way connection link
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#two-way-link-type>
|
||||
pub enum TwoWayLinkType {
|
||||
/// The connection is linked via web
|
||||
Web,
|
||||
/// The connection is linked via mobile
|
||||
Mobile,
|
||||
/// The connection is linked via desktop
|
||||
Desktop,
|
||||
}
|
||||
|
||||
impl Display for TwoWayLinkType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(format!("{:?}", self).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
/// Defines a subreddit as fetched through a Reddit connection.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#subreddit-structure>
|
||||
pub struct ConnectionSubreddit {
|
||||
/// The subreddit's internal id, e.g. "t5_388p4"
|
||||
pub id: String,
|
||||
/// How many reddit users follow the subreddit
|
||||
pub subscribers: usize,
|
||||
/// The subreddit's relative url, e.g. "/r/discordapp/"
|
||||
pub url: String,
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::types::Snowflake;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Updateable;
|
||||
|
||||
// FIXME: Should this type be Composite?
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
/// A user's data harvest.
|
||||
///
|
||||
/// # Reference
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#harvest-object>
|
||||
pub struct Harvest {
|
||||
pub harvest_id: Snowflake,
|
||||
/// The id of the user being harvested
|
||||
pub user_id: Snowflake,
|
||||
pub status: HarvestStatus,
|
||||
/// The time the harvest was created
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// The time the harvest was last polled
|
||||
pub polled_at: Option<DateTime<Utc>>,
|
||||
/// The time the harvest was completed
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
impl Updateable for Harvest {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn id(&self) -> Snowflake {
|
||||
self.harvest_id
|
||||
}
|
||||
}
|
||||
|
||||
#[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")]
|
||||
/// Current status of a [Harvest]
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#harvest-status> and <https://docs.discord.sex/resources/user#harvest-object>
|
||||
pub enum HarvestStatus {
|
||||
/// The harvest is queued and has not been started
|
||||
Queued = 0,
|
||||
/// The harvest is currently running / being processed
|
||||
Running = 1,
|
||||
/// The harvest has failed
|
||||
Failed = 2,
|
||||
/// The harvest has been completed successfully
|
||||
Completed = 3,
|
||||
#[default]
|
||||
Unknown = 4,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
/// A type of backend / service a harvest can be requested for.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#harvest-backend-type> and <https://support.discord.com/hc/en-us/articles/360004957991-Your-Discord-Data-Package>
|
||||
pub enum HarvestBackendType {
|
||||
/// All account information;
|
||||
Accounts,
|
||||
/// Actions the user has taken;
|
||||
///
|
||||
/// Represented as "Your Activity" in the discord client
|
||||
Analytics,
|
||||
/// First-party embedded activity information;
|
||||
///
|
||||
/// e.g.: Chess in the Park, Checkers in the Park, Poker Night 2.0;
|
||||
/// Sketch Heads, Watch Together, Letter League, Land-io, Know What I Meme
|
||||
Activities,
|
||||
/// The user's messages
|
||||
Messages,
|
||||
/// Official Discord programes;
|
||||
///
|
||||
/// e.g.: Partner, HypeSquad, Verified Server
|
||||
Programs,
|
||||
/// Guilds the user is a member of;
|
||||
Servers,
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ pub use audit_log::*;
|
|||
pub use auto_moderation::*;
|
||||
pub use channel::*;
|
||||
pub use config::*;
|
||||
pub use connection::*;
|
||||
pub use emoji::*;
|
||||
pub use guild::*;
|
||||
pub use guild_member::*;
|
||||
pub use harvest::*;
|
||||
pub use integration::*;
|
||||
pub use invite::*;
|
||||
pub use message::*;
|
||||
|
@ -49,9 +51,11 @@ mod audit_log;
|
|||
mod auto_moderation;
|
||||
mod channel;
|
||||
mod config;
|
||||
mod connection;
|
||||
mod emoji;
|
||||
mod guild;
|
||||
mod guild_member;
|
||||
mod harvest;
|
||||
mod integration;
|
||||
mod invite;
|
||||
mod message;
|
||||
|
|
|
@ -6,7 +6,8 @@ use crate::errors::ChorusError;
|
|||
use crate::types::utils::Snowflake;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_default_from_null};
|
||||
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, PublicConnection};
|
||||
|
||||
#[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),
|
||||
|
@ -139,6 +146,8 @@ impl sqlx::Type<sqlx::Any> for ThemeColors {
|
|||
}
|
||||
|
||||
#[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,640 @@ 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
|
||||
#[serde(deserialize_with = "deserialize_default_from_null")]
|
||||
// Note: spacebar will send this is as null, while it should be ""
|
||||
// See issue 1188
|
||||
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>,
|
||||
|
||||
pub connected_accounts: Vec<PublicConnection>,
|
||||
|
||||
// TODO: Add 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>,
|
||||
}
|
||||
|
||||
/// Structure which is returned by the [crate::instance::ChorusUser::get_user_note] endpoint.
|
||||
///
|
||||
/// Note that [crate::instance::ChorusUser::get_user_notes] endpoint
|
||||
/// returns a completely different structure;
|
||||
// Specualation: this is probably how Discord stores notes internally
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-note>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct UserNote {
|
||||
/// Actual note contents; max 256 characters
|
||||
pub note: String,
|
||||
/// The ID of the user the note is on
|
||||
pub note_user_id: Snowflake,
|
||||
/// The ID of the user who created the note (always the current user)
|
||||
pub user_id: Snowflake,
|
||||
}
|
||||
|
||||
/// Structure which defines an affinity the local user has with another user.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#user-affinity-structure>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct UserAffinity {
|
||||
/// The other user's id
|
||||
pub user_id: Snowflake,
|
||||
/// The affinity score
|
||||
pub affinity: f32,
|
||||
}
|
||||
|
||||
/// Structure which defines an affinity the local user has with a guild.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#guild-affinity-structure>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct GuildAffinity {
|
||||
/// The guild's id
|
||||
pub guild_id: Snowflake,
|
||||
/// The affinity score
|
||||
pub affinity: f32,
|
||||
}
|
||||
|
||||
/// Structure which defines the local user's premium perk usage.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-premium-usage>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PremiumUsage {
|
||||
/// Number of Nitro stickers the user has sent
|
||||
pub nitro_sticker_sends: PremiumUsageData,
|
||||
/// Number of animated emojis the user has sent
|
||||
pub total_animated_emojis: PremiumUsageData,
|
||||
/// Number of global emojis the user has sent
|
||||
pub total_global_emojis: PremiumUsageData,
|
||||
/// Number of large uploads the user has made
|
||||
pub total_large_uploads: PremiumUsageData,
|
||||
/// Number of times the user has streamed in HD
|
||||
pub total_hd_streams: PremiumUsageData,
|
||||
/// Number of hours the user has streamed in HD
|
||||
pub hd_hours_streamed: PremiumUsageData,
|
||||
}
|
||||
|
||||
/// Structure for the data in [PremiumUsage].
|
||||
///
|
||||
/// Currently only contains the number of uses of a premium perk.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/user#premium-usage-structure>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PremiumUsageData {
|
||||
/// Total number of uses for this perk
|
||||
pub value: usize,
|
||||
}
|
||||
|
||||
impl From<PremiumUsageData> for usize {
|
||||
fn from(value: PremiumUsageData) -> Self {
|
||||
value.value
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for PremiumUsageData {
|
||||
fn from(value: usize) -> Self {
|
||||
PremiumUsageData { value }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,6 +149,15 @@ pub struct MessageReactionRemoveEmoji {
|
|||
pub emoji: Emoji,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, WebSocketEvent)]
|
||||
/// Sent when a message that mentioned the current user in the last week is acknowledged and deleted.
|
||||
///
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/topics/gateway-events#recent-mention-delete>
|
||||
pub struct RecentMentionDelete {
|
||||
pub message_id: Snowflake,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
|
||||
/// Officially Undocumented
|
||||
///
|
||||
|
|
|
@ -30,7 +30,7 @@ pub struct Session {
|
|||
// Note: I don't think this one exists yet? Though I might've made a mistake and this might be a duplicate
|
||||
pub struct ClientInfo {
|
||||
pub client: Option<String>,
|
||||
pub os: String,
|
||||
pub os: Option<String>,
|
||||
pub version: u8,
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::types::entities::PublicUser;
|
||||
use crate::types::events::WebSocketEvent;
|
||||
use crate::types::utils::Snowflake;
|
||||
use crate::types::Connection;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)]
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#user-update>;
|
||||
|
@ -16,6 +17,27 @@ pub struct UserUpdate {
|
|||
pub user: PublicUser,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)]
|
||||
/// Sent to indicate updates to a user's [Connection].
|
||||
///
|
||||
/// Not documented anywhere
|
||||
pub struct UserConnectionsUpdate {
|
||||
#[serde(flatten)]
|
||||
pub connection: Connection,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)]
|
||||
/// See <https://docs.discord.sex/topics/gateway-events#user-note-update-structure>;
|
||||
///
|
||||
/// Sent when a note the current user has on another user is modified;
|
||||
///
|
||||
/// If the field "note" is an empty string, the note was removed.
|
||||
pub struct UserNoteUpdate {
|
||||
/// Id of the user the note is for
|
||||
pub id: Snowflake,
|
||||
pub note: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)]
|
||||
/// Undocumented;
|
||||
///
|
||||
|
|
|
@ -4,10 +4,13 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::Snowflake;
|
||||
use crate::types::{
|
||||
Connection, GuildAffinity, HarvestBackendType, Snowflake, ThemeColors, TwoWayLinkType,
|
||||
UserAffinity,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -41,7 +44,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 +114,347 @@ 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 {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// Whether to include the mutual guilds between the current user.
|
||||
///
|
||||
/// If unset it will default to true
|
||||
pub with_mutual_guilds: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// Whether to include the mutual friends between the current user.
|
||||
///
|
||||
/// If unset it will default to false
|
||||
pub with_mutual_friends: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// 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>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// 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>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
|
||||
/// Query string parameters for the route GET /users/@me/mentions
|
||||
/// ([crate::instance::ChorusUser::get_recent_mentions])
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-recent-mentions>
|
||||
pub struct GetRecentMentionsSchema {
|
||||
/// Only fetch messages before this message id
|
||||
///
|
||||
/// Due to the nature of snowflakes, this can be easily used to fetch
|
||||
/// messages before a certain timestamp
|
||||
pub before: Option<Snowflake>,
|
||||
/// Max number of messages to return
|
||||
///
|
||||
/// Should be between 1 and 100.
|
||||
///
|
||||
/// If unset the limit is 25 messages
|
||||
pub limit: Option<u8>,
|
||||
/// Limit messages to a specific guild
|
||||
pub guild_id: Option<Snowflake>,
|
||||
/// Whether to include role mentions.
|
||||
///
|
||||
/// If unset the server assumes true
|
||||
pub roles: Option<bool>,
|
||||
/// Whether to include @everyone and @here mentions.
|
||||
///
|
||||
/// If unset the server assumes true
|
||||
pub everyone: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Internal type for the [crate::instance::ChorusUser::create_harvest] endpoint.
|
||||
// (koza): imo it's nicer if the user can just pass a vec, instead of having to bother with
|
||||
// a specific type
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#create-user-harvest>
|
||||
pub(crate) struct CreateUserHarvestSchema {
|
||||
pub backends: Option<Vec<HarvestBackendType>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Internal type for the [crate::instance::ChorusUser::set_user_note] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-note>
|
||||
pub(crate) struct ModifyUserNoteSchema {
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Query string parameters for the route GET /connections/{connection.type}/authorize
|
||||
/// ([crate::instance::ChorusUser::authorize_connection])
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#authorize-user-connection>
|
||||
pub struct AuthorizeConnectionSchema {
|
||||
/// The type of two-way link ([TwoWayLinkType]) to create
|
||||
pub two_way_link_type: Option<TwoWayLinkType>,
|
||||
/// The device code to use for the two-way link
|
||||
pub two_way_user_code: Option<String>,
|
||||
/// If this is a continuation of a previous authorization
|
||||
pub continuation: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Internal type for the [crate::instance::ChorusUser::authorize_connection] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#authorize-user-connection>
|
||||
pub(crate) struct AuthorizeConnectionReturn {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Json schema for the route POST /connections/{connection.type}/callback ([crate::instance::ChorusUser::create_connection_callback]).
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#create-user-connection-callback>
|
||||
pub struct CreateConnectionCallbackSchema {
|
||||
/// The authorization code for the connection
|
||||
pub code: String,
|
||||
/// The "state" used to authorize a connection
|
||||
// TODO: what is this?
|
||||
pub state: String,
|
||||
pub two_way_link_code: Option<String>,
|
||||
pub insecure: Option<bool>,
|
||||
pub friend_sync: Option<bool>,
|
||||
/// Additional parameters used for OpenID Connect
|
||||
// FIXME: Is this correct? in other connections additional info
|
||||
// is provided like this, only being string - string
|
||||
pub openid_params: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Json schema for the route PUT /users/@me/connections/contacts/{connection.id} ([crate::instance::ChorusUser::create_contact_sync_connection]).
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#create-contact-sync-connection>
|
||||
pub struct CreateContactSyncConnectionSchema {
|
||||
/// The username of the connection account
|
||||
pub name: String,
|
||||
/// Whether to sync friends over the connection
|
||||
pub friend_sync: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Json schema for the route PATCH /users/@me/connections/{connection.type}/{connection.id} ([crate::instance::ChorusUser::modify_connection]).
|
||||
///
|
||||
/// Note: not all connection types support all parameters.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-connection>
|
||||
pub struct ModifyConnectionSchema {
|
||||
/// The connection account's username
|
||||
///
|
||||
/// Note: We have not found which connection this could apply to
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// Whether activities related to this connection will be shown in presence
|
||||
///
|
||||
/// e.g. on a Spotify connection, "Display Spotify as your status"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub show_activity: Option<bool>,
|
||||
|
||||
/// Whether or not to sync friends from this connection
|
||||
///
|
||||
/// Note: we have not found which connections this can apply to
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub friend_sync: Option<bool>,
|
||||
|
||||
/// Whether to show additional metadata on the user's profile
|
||||
///
|
||||
/// e.g. on a Steam connection, "Display details on profile"
|
||||
/// (number of games, items, member since)
|
||||
///
|
||||
/// on a Twitter connection, number of posts / followers, member since
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata_visibility: Option<bool>,
|
||||
|
||||
/// Whether to show the connection on the user's profile
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub visibility: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// Internal type for the [crate::instance::ChorusUser::get_connection_access_token] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-connection-access-token>
|
||||
pub(crate) struct GetConnectionAccessTokenReturn {
|
||||
pub access_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
|
||||
/// Return type for the [crate::instance::ChorusUser::get_user_affinities] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-user-affinities>
|
||||
pub struct UserAffinities {
|
||||
pub user_affinities: Vec<UserAffinity>,
|
||||
// FIXME: Is this also a UserAffinity vec?
|
||||
// Also, no idea what this means
|
||||
pub inverse_user_affinities: Vec<UserAffinity>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
|
||||
/// Return type for the [crate::instance::ChorusUser::get_guild_affinities] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#get-guild-affinities>
|
||||
pub struct GuildAffinities {
|
||||
pub guild_affinities: Vec<GuildAffinity>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Return type for the error in the [crate::instance::ChorusUser::create_domain_connection] endpoint.
|
||||
///
|
||||
/// This allows us to retrieve the needed proof for actually verifying the connection.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#create-domain-connection>
|
||||
pub(crate) struct CreateDomainConnectionError {
|
||||
pub message: String,
|
||||
pub code: u16,
|
||||
pub proof: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Return type for the [crate::instance::ChorusUser::create_domain_connection] endpoint.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#create-domain-connection>
|
||||
pub enum CreateDomainConnectionReturn {
|
||||
/// Additional proof is needed to verify domain ownership.
|
||||
///
|
||||
/// The inner object is a proof string (e.g.
|
||||
/// `dh=dceaca792e3c40fcf356a9297949940af5cfe538`)
|
||||
///
|
||||
/// To verify ownership, either:
|
||||
///
|
||||
/// - add the proof string as a TXT DNS record to the domain,
|
||||
/// with the name of the record being `_discord.{domain}` or
|
||||
///
|
||||
/// - serve the proof string as a file at `https://{domain}/.well-known/discord`
|
||||
///
|
||||
/// After either of these proofs are added, the request should be retried.
|
||||
///
|
||||
ProofNeeded(String),
|
||||
/// The domain connection was successfully created, no further action is needed.
|
||||
///
|
||||
/// The inner object is the new connection.
|
||||
Ok(Connection),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Return type for the [crate::instance::ChorusUser::get_burst_credits] endpoint.
|
||||
///
|
||||
/// # Reference
|
||||
/// ```json
|
||||
/// {
|
||||
/// "amount": 2,
|
||||
/// "replenished_today": false,
|
||||
/// "next_replenish_at": "2024-08-18T23:53:17+00:00"
|
||||
/// }
|
||||
/// ```
|
||||
pub struct BurstCreditsInfo {
|
||||
/// Amount of remaining burst credits the local user has
|
||||
pub amount: u16,
|
||||
pub replenished_today: bool,
|
||||
/// When the user's burst credits will automatically replenish again
|
||||
pub next_replenish_at: DateTime<Utc>,
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use chorus::types::{PublicUser, Snowflake, User};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen_test::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
mod common;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
||||
|
@ -20,3 +26,20 @@ fn to_public_user() {
|
|||
let from_user = user.into_public_user();
|
||||
assert_eq!(public_user, from_user);
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||
async fn test_get_user_profile() {
|
||||
let mut bundle = common::setup().await;
|
||||
|
||||
let user_id = bundle.user.object.read().unwrap().id;
|
||||
|
||||
let user_profile = bundle
|
||||
.user
|
||||
.get_user_profile(user_id, chorus::types::GetUserProfileSchema::default())
|
||||
.await;
|
||||
|
||||
assert!(user_profile.is_ok());
|
||||
|
||||
common::teardown(bundle).await;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue