diff --git a/src/api/users/connections.rs b/src/api/users/connections.rs index 58ed5d2..9a6f3ee 100644 --- a/src/api/users/connections.rs +++ b/src/api/users/connections.rs @@ -2,13 +2,14 @@ use futures_util::FutureExt; use reqwest::Client; use crate::{ - errors::ChorusResult, + errors::{ChorusError, ChorusResult}, instance::ChorusUser, ratelimiter::ChorusRequest, types::{ AuthorizeConnectionReturn, AuthorizeConnectionSchema, Connection, ConnectionSubreddit, ConnectionType, CreateConnectionCallbackSchema, CreateContactSyncConnectionSchema, - GetConnectionAccessTokenReturn, LimitType, ModifyConnectionSchema, + CreateDomainConnectionError, CreateDomainConnectionReturn, GetConnectionAccessTokenReturn, + LimitType, ModifyConnectionSchema, }, }; @@ -121,6 +122,92 @@ impl ChorusUser { 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 + pub async fn create_domain_connection( + &mut self, + domain: &String, + ) -> ChorusResult { + 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::(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 = + serde_json::from_str(error_string); + + if let Ok(deserialized_error) = try_deserialize { + return Ok(CreateDomainConnectionReturn::ProofNeeded( + deserialized_error.proof, + )); + } + } + } + + return Err(error); + } + // TODO: Add create_domain_connection () // It requires changing how chorus handles errors to support properly diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index b2ea6fc..3df0e0f 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -8,7 +8,7 @@ use chrono::NaiveDate; use serde::{Deserialize, Serialize}; use crate::types::{ - GuildAffinity, HarvestBackendType, Snowflake, ThemeColors, TwoWayLinkType, UserAffinity, + Connection, GuildAffinity, HarvestBackendType, Snowflake, ThemeColors, TwoWayLinkType, UserAffinity }; #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] @@ -395,3 +395,43 @@ pub struct UserAffinities { pub struct GuildAffinities { pub guild_affinities: Vec, } + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +/// 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 +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 +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) +}