From 170f79bbd1ea7ecac33c6725c50f2b7b66a44dca Mon Sep 17 00:00:00 2001 From: kozabrada123 Date: Sat, 10 Aug 2024 11:34:56 +0200 Subject: [PATCH] feat: add authorize_connection Also adds: src/types/entities/connection.rs, Connection and PublicConnection, src/api/users/connections.rs --- src/api/users/connections.rs | 47 ++++++++ src/api/users/mod.rs | 2 + src/api/users/users.rs | 11 +- src/types/entities/connection.rs | 191 +++++++++++++++++++++++++++++++ src/types/entities/mod.rs | 2 + src/types/schema/user.rs | 24 +++- 6 files changed, 271 insertions(+), 6 deletions(-) create mode 100644 src/api/users/connections.rs create mode 100644 src/types/entities/connection.rs diff --git a/src/api/users/connections.rs b/src/api/users/connections.rs new file mode 100644 index 0000000..2fd9580 --- /dev/null +++ b/src/api/users/connections.rs @@ -0,0 +1,47 @@ +use reqwest::Client; + +use crate::{ + errors::ChorusResult, + instance::ChorusUser, + ratelimiter::ChorusRequest, + types::{AuthorizeConnectionReturn, AuthorizeConnectionSchema, ConnectionType, LimitType}, +}; + +impl ChorusUser { + /// Fetches a url that can be used for authorizing a new connection. + /// + /// # Reference + /// See + /// + /// Note: it doesn't seem to be actually unauthenticated + pub async fn authorize_connection( + &mut self, + connection_type: ConnectionType, + query_parameters: AuthorizeConnectionSchema, + ) -> ChorusResult { + 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::(self) + .await + .map(|response| response.url) + } +} diff --git a/src/api/users/mod.rs b/src/api/users/mod.rs index b11772a..702233c 100644 --- a/src/api/users/mod.rs +++ b/src/api/users/mod.rs @@ -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; diff --git a/src/api/users/users.rs b/src/api/users/users.rs index 1de9edc..9064efd 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -15,11 +15,12 @@ use crate::{ instance::{ChorusUser, Instance}, ratelimiter::ChorusRequest, types::{ - CreateUserHarvestSchema, DeleteDisableUserSchema, GetPomeloEligibilityReturn, - GetPomeloSuggestionsReturn, GetRecentMentionsSchema, GetUserProfileSchema, Harvest, - HarvestBackendType, LimitType, ModifyUserNoteSchema, PublicUser, Snowflake, User, - UserModifyProfileSchema, UserModifySchema, UserNote, UserProfile, UserProfileMetadata, - UserSettings, VerifyUserEmailChangeResponse, VerifyUserEmailChangeSchema, + AuthorizeConnectionSchema, ConnectionType, CreateUserHarvestSchema, + DeleteDisableUserSchema, GetPomeloEligibilityReturn, GetPomeloSuggestionsReturn, + GetRecentMentionsSchema, GetUserProfileSchema, Harvest, HarvestBackendType, LimitType, + ModifyUserNoteSchema, PublicUser, Snowflake, User, UserModifyProfileSchema, + UserModifySchema, UserNote, UserProfile, UserProfileMetadata, UserSettings, + VerifyUserEmailChangeResponse, VerifyUserEmailChangeSchema, }, }; diff --git a/src/types/entities/connection.rs b/src/types/entities/connection.rs new file mode 100644 index 0000000..b4e6b3d --- /dev/null +++ b/src/types/entities/connection.rs @@ -0,0 +1,191 @@ +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 +// 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>, + 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, +} + +/// A partial / public [Connection] type. +/// +/// # Reference +/// See +// 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>, +} + +impl From 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 +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()), + } + } +} + +#[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 +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 +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()) + } +} diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index c736b99..6ac2629 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -8,6 +8,7 @@ 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::*; @@ -50,6 +51,7 @@ mod audit_log; mod auto_moderation; mod channel; mod config; +mod connection; mod emoji; mod guild; mod guild_member; diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index cc6e36b..93202f2 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use chrono::NaiveDate; use serde::{Deserialize, Serialize}; -use crate::types::{HarvestBackendType, Snowflake, ThemeColors}; +use crate::types::{HarvestBackendType, Snowflake, ThemeColors, TwoWayLinkType}; #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(rename_all = "snake_case")] @@ -275,3 +275,25 @@ pub(crate) struct CreateUserHarvestSchema { pub(crate) struct ModifyUserNoteSchema { pub note: Option, } + +#[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 +pub struct AuthorizeConnectionSchema { + /// The type of two-way link ([TwoWayLinkType]) to create + pub two_way_link_type: Option, + /// The device code to use for the two-way link + pub two_way_user_code: Option, + /// 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 +pub(crate) struct AuthorizeConnectionReturn { + pub url: String, +}