Skip to content

Commit

Permalink
feat: add get_user_harvest & create_user_harvest
Browse files Browse the repository at this point in the history
Also adds /types/entities/harvest.rs, types for Harvest
  • Loading branch information
kozabrada123 committed Aug 9, 2024
1 parent 9d84790 commit 87aad46
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 28 deletions.
103 changes: 99 additions & 4 deletions src/api/users/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use crate::{
instance::{ChorusUser, Instance},
ratelimiter::ChorusRequest,
types::{
DeleteDisableUserSchema, GetPomeloEligibilityReturn, GetPomeloSuggestionsReturn,
GetRecentMentionsSchema, GetUserProfileSchema, LimitType, PublicUser, Snowflake, User,
UserModifyProfileSchema, UserModifySchema, UserProfile, UserProfileMetadata, UserSettings,
CreateUserHarvestSchema, DeleteDisableUserSchema, GetPomeloEligibilityReturn,
GetPomeloSuggestionsReturn, GetRecentMentionsSchema, GetUserProfileSchema, Harvest,
HarvestBackendType, LimitType, PublicUser, Snowflake, User, UserModifyProfileSchema,
UserModifySchema, UserProfile, UserProfileMetadata, UserSettings,
VerifyUserEmailChangeResponse, VerifyUserEmailChangeSchema,
},
};
Expand Down Expand Up @@ -392,7 +393,7 @@ impl ChorusUser {
/// Fires a RecentMentionDelete gateway event. (Note: yet to be implemented in chorus, see [#545](https://github.com/polyphony-chat/chorus/issues/545))
///
/// As of 2024/08/09, Spacebar does not yet implement this endpoint.
///
///
/// 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()
Expand All @@ -410,6 +411,100 @@ impl ChorusUser {

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].
///
/// As of 2024/08/09, Spacebar does not yet implement this endpoint. (Or data harvesting)
///
/// 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.
///
/// 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)
///
/// 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
}
}

impl User {
Expand Down
96 changes: 96 additions & 0 deletions src/types/entities/harvest.rs
Original file line number Diff line number Diff line change
@@ -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,
}
2 changes: 2 additions & 0 deletions src/types/entities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub use config::*;
pub use emoji::*;
pub use guild::*;
pub use guild_member::*;
pub use harvest::*;
pub use integration::*;
pub use invite::*;
pub use message::*;
Expand Down Expand Up @@ -52,6 +53,7 @@ mod config;
mod emoji;
mod guild;
mod guild_member;
mod harvest;
mod integration;
mod invite;
mod message;
Expand Down
58 changes: 34 additions & 24 deletions src/types/schema/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::collections::HashMap;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};

use crate::types::{Snowflake, ThemeColors};
use crate::types::{HarvestBackendType, Snowflake, ThemeColors};

#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -218,15 +218,15 @@ pub struct GetUserProfileSchema {
///
/// See <https://docs.discord.sex/resources/user#get-pomelo-suggestions>
pub(crate) struct GetPomeloSuggestionsReturn {
pub username: String
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
pub taken: bool,
}

#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
Expand All @@ -235,25 +235,35 @@ pub(crate) struct GetPomeloEligibilityReturn {
///
/// 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>,
/// 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>>,
}

0 comments on commit 87aad46

Please sign in to comment.