diff --git a/simplelogin/src/lib.rs b/simplelogin/src/lib.rs index e53c511..1e2cdd9 100644 --- a/simplelogin/src/lib.rs +++ b/simplelogin/src/lib.rs @@ -85,6 +85,104 @@ pub struct Stats { pub nb_reply: u32, } +#[derive(Debug, Deserialize)] +pub struct AliasOptionsSuffix { + pub signed_suffix: String, + pub suffix: String, + pub is_custom: bool, + pub is_premium: bool, +} + +#[derive(Debug, Deserialize)] +pub struct AliasOptionsRecommendation { + pub alias: String, + pub hostname: String, +} + +#[derive(Debug, Deserialize)] +pub struct AliasOptions { + pub can_create: bool, + pub prefix_suggestion: String, + pub suffixes: Vec, + pub recommendation: Option, +} + +#[derive(Debug, Deserialize)] +pub struct AliasMailbox { + pub email: String, + pub id: u64, +} + +#[derive(Debug, Deserialize)] +pub struct AliasContact { + pub email: String, + pub name: Option, + pub reverse_alias: String, +} + +#[derive(Debug, Deserialize)] +pub struct AliasActivity { + pub action: String, + pub contact: AliasContact, + pub timestamp: u64, +} + +#[derive(Debug, Deserialize)] +pub struct Alias { + pub creation_date: String, + pub creation_timestamp: u64, + pub email: String, + pub name: Option, + pub enabled: bool, + pub pinned: bool, + pub note: Option, + pub id: u64, + pub mailbox: AliasMailbox, + pub mailboxes: Vec, + pub nb_block: u64, + pub nb_forward: u64, + pub nb_reply: u64, + pub support_pgp: bool, + pub disable_pgp: bool, + pub latest_activity: Option, +} + +#[derive(Serialize)] +pub struct NewCustomAliasRequest<'a> { + pub alias_prefix: &'a str, + pub signed_suffix: &'a str, + pub mailbox_ids: &'a [u64], + pub note: &'a Option<&'a str>, + pub name: &'a Option<&'a str>, +} + +#[derive(Debug, Deserialize)] +pub struct AliasListResponse { + pub aliases: Vec, +} + +pub enum NewRandomAliasMode { + UUID, WORD, +} + +pub enum AliasListMode { + ALL, + PINNED, + DISABLED, + ENABLED, +} + +impl AliasListMode { + pub fn param_name(&self) -> Option<&str> { + match self { + Self::ALL => None, + Self::PINNED => Some("pinned"), + Self::DISABLED => Some("disabled"), + Self::ENABLED => Some("enabled"), + } + } +} + pub enum ErrorKind { State, Json, @@ -192,9 +290,14 @@ impl Context { } async fn get_request( - &mut self, endpoint: &str, headers: &[(&str, &str)] + &mut self, endpoint: &str, params: &[(&str, &str)], headers: &[(&str, &str)], ) -> Result { - let mut builder = self.client.get(self.url.to_owned() + endpoint); + let base_url = self.url.to_owned() + endpoint; + let mut builder = self.client.get( + reqwest::Url::parse_with_params(base_url.as_str(), params) + .map_err(|_| { + Error::new(ErrorKind::Http, "Url parse error") + })?); for header in headers { builder = builder.header(header.0, header.1); } @@ -202,11 +305,19 @@ impl Context { } async fn post_request( - &mut self, endpoint: &str, body: &str + &mut self, endpoint: &str, params: &[(&str, &str)], + headers: &[(&str, &str)], body: &str ) -> Result { - Self::perform_request(self.client.post(self.url.to_owned() + endpoint) - .header("Content-Type", "application/json") - .body(body.to_owned())).await + let base_url = self.url.to_owned() + endpoint; + let mut builder = self.client.post( + reqwest::Url::parse_with_params(base_url.as_str(), params).map_err(|_| { + Error::new(ErrorKind::Http, "Url parse error") + })?).header("Content-Type", "application/json") + .body(body.to_owned()); + for header in headers { + builder = builder.header(header.0, header.1); + } + Self::perform_request(builder).await } async fn patch_request( @@ -247,7 +358,7 @@ impl Context { device: &str ) -> Result { let req_body = LoginRequest {email, password, device}; - let body = self.post_request("api/auth/login", + let body = self.post_request("api/auth/login", &[], &[], Self::obj_to_json(&req_body)?.as_str()).await?; let resp_obj: LoginResponse = Self::json_to_obj(body.as_str())?; if resp_obj.mfa_enabled { @@ -275,7 +386,7 @@ impl Context { mfa_key: self.mfa_key.as_ref().unwrap().as_str(), device: self.device.as_ref().unwrap().as_str(), })?; - let body = self.post_request("api/auth/mfa", req_json.as_str()).await?; + let body = self.post_request("api/auth/mfa", &[], &[], req_json.as_str()).await?; let resp_obj: MfaResponse = Self::json_to_obj(body.as_str())?; self.api_key = Some(resp_obj.api_key); self.device = None; @@ -289,7 +400,7 @@ impl Context { pub async fn logout(&mut self) -> Result<(), Error> { match self.api_key.as_ref() { Some(api_key) => { - self.get_request("api/logout", + self.get_request("api/logout", &[], &[("Authentication", api_key.clone().as_str())]).await?; self.api_key = None; Ok(()) @@ -301,7 +412,7 @@ impl Context { pub async fn get_user_info(&mut self) -> Result { match self.api_key.as_ref() { Some(api_key) => { - let res = self.get_request("api/user_info", + let res = self.get_request("api/user_info", &[], &[("Authentication", api_key.clone().as_str())]).await?; Ok(Self::json_to_obj(&res)?) }, @@ -327,7 +438,112 @@ impl Context { pub async fn stats(&mut self) -> Result { match self.api_key.as_ref() { Some(api_key) => { - let res = self.get_request("api/stats", + let res = self.get_request("api/stats", &[], + &[("Authentication", api_key.clone().as_str())]).await?; + Ok(Self::json_to_obj(&res)?) + }, + None => Err(Error::new(ErrorKind::State, "Not logged in")), + } + } + + pub async fn alias_options(&mut self, hostname: Option<&str>) -> Result { + match self.api_key.as_ref() { + Some(api_key) => { + let params: Vec<(&str, &str)> = match hostname { + Some(hn) => vec![("hostname", hn)], + None => vec![], + }; + let res = self.get_request("api/v5/alias/options", ¶ms, + &[("Authentication", api_key.clone().as_str())]).await?; + Ok(Self::json_to_obj(&res)?) + }, + None => Err(Error::new(ErrorKind::State, "Not logged in")), + } + } + + pub async fn new_custom_alias( + &mut self, hostname: Option<&str>, alias_prefix: &str, + signed_suffix: &str, mailbox_ids: &[u64], note: Option<&str>, + name: Option<&str> + ) -> Result { + match self.api_key.as_ref() { + Some(api_key) => { + let params: Vec<(&str, &str)> = match hostname { + Some(hn) => vec![("hostname", hn)], + None => vec![], + }; + let req_json = Self::obj_to_json(&NewCustomAliasRequest { + alias_prefix, signed_suffix, mailbox_ids, + name: ¬e, note: &name})?; + let res = self.post_request("api/v3/alias/custom/new", ¶ms, + &[("Authentication", api_key.clone().as_str())], + req_json.as_str()).await?; + Ok(Self::json_to_obj(&res)?) + }, + None => Err(Error::new(ErrorKind::State, "Not logged in")), + } + } + + pub async fn new_random_alias( + &mut self, hostname: Option<&str>, + mode: Option, note: &str + ) -> Result { + match self.api_key.as_ref() { + Some(api_key) => { + let mut params = vec![]; + if let Some(hn) = hostname { + params.push(("hostname", hn)); + } + if let Some(md) = mode { + params.push(("mode", match md { + NewRandomAliasMode::UUID => "uuid", + NewRandomAliasMode::WORD => "word", + })); + } + let req_json = serde_json::json!({"note": note}); + let res = self.post_request("api/alias/random/new", ¶ms, + &[("Authentication", api_key.clone().as_str())], + req_json.as_str().unwrap()).await?; + Ok(Self::json_to_obj(&res)?) + }, + None => Err(Error::new(ErrorKind::State, "Not logged in")), + } + } + + pub async fn list_aliases( + &mut self, page_id: u64, mode: AliasListMode, query: Option<&str> + ) -> Result, Error> { + match self.api_key.as_ref() { + Some(api_key) => { + let page_id_str = u64::to_string(&page_id); + let params = match mode.param_name() { + Some(name) => vec![(name, "true"), + ("page_id", page_id_str.as_str())], + None => vec![("page_id", page_id_str.as_str())], + }; + let res = match query { + Some(q) => { + let req_json = serde_json::json!({"query": q}); + self.post_request("api/v2/aliases", ¶ms, + &[("Authentication", api_key.clone().as_str())], + req_json.as_str().unwrap()).await? + }, + None => self.get_request("api/v2/aliases", ¶ms, + &[("Authentication", api_key.clone().as_str())]).await?, + }; + Ok(Self::json_to_obj::(&res)?.aliases) + }, + None => Err(Error::new(ErrorKind::State, "Not logged in")), + } + } + + pub async fn get_alias( + &mut self, alias_id: u64 + ) -> Result { + let endpoint = format!("api/aliases/{alias_id}"); + match self.api_key.as_ref() { + Some(api_key) => { + let res = self.get_request(endpoint.as_str(), &[], &[("Authentication", api_key.clone().as_str())]).await?; Ok(Self::json_to_obj(&res)?) },