diff --git a/src/lib.rs b/src/lib.rs index 037fc14..4377191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,105 @@ pub mod twilio; + +#[derive(Default, Debug)] +pub struct SendMsg { + config: VendorConfig, + message: Message, + recipient: Recipient, +} + +impl SendMsg { + pub fn load_config( + &mut self, + auth_token: &str, + phone_number: &str, + service_sid: &str, + auth_sid: &str, + ) { + self.config = VendorConfig { + auth_token: auth_token.to_string(), + phone_number: phone_number.to_string(), + service_sid: service_sid.to_string(), + auth_sid: auth_sid.to_string(), + } + } + + pub fn load_message(&mut self, content: &str) { + self.message = Message { + content: content.to_string(), + } + } + + pub fn load_recipient(&mut self, recpient: &str) { + self.recipient = Recipient { + phone_number: recpient.to_string(), + } + } + + pub async fn send_message( + &self, + param: &crate::twilio::types::Parameters, + ) -> Result { + if self.recipient.phone_number.is_empty() { + return Err(std::io::Error::other("Recipient not provided")); + } else if self.message.content.is_empty() { + return Err(std::io::Error::other("Message not provided")); + } else if self.config.phone_number.is_empty() + || self.config.service_sid.is_empty() + || self.config.auth_token.is_empty() + || self.config.auth_sid.is_empty() + { + return Err(std::io::Error::other("Config not populated")); + } else { + let client = match reqwest::Client::builder().build() { + Ok(client) => client, + Err(err) => { + return Err(std::io::Error::other(err)); + } + }; + + let url = twilio::api::twilio_url(&self.config.auth_sid); + let headers = + match twilio::api::init_headers(&self.config.auth_sid, &self.config.auth_token) { + Ok(headers) => headers, + Err(err) => { + return Err(std::io::Error::other(err)); + } + }; + let params = twilio::api::initial_params( + &self.recipient.phone_number, + &self.message.content, + &self.config.phone_number, + &self.config.service_sid, + param, + ); + let request = client.post(url).headers(headers).form(¶ms); + match request.send().await { + Ok(response) => match response.status() { + reqwest::StatusCode::CREATED => Ok(response), + status => Err(std::io::Error::other(format!( + "Unaccounted status: {status:?}" + ))), + }, + Err(err) => Err(std::io::Error::other(err)), + } + } + } +} + +#[derive(Default, Debug)] +pub struct VendorConfig { + pub auth_token: String, + pub phone_number: String, + pub service_sid: String, + pub auth_sid: String, +} + +#[derive(Default, Debug)] +pub struct Message { + pub content: String, +} + +#[derive(Default, Debug)] +pub struct Recipient { + pub phone_number: String, +} diff --git a/src/twilio/api/mod.rs b/src/twilio/api/mod.rs index 961e51c..24fcb08 100644 --- a/src/twilio/api/mod.rs +++ b/src/twilio/api/mod.rs @@ -58,6 +58,92 @@ pub async fn send_message( } } +pub fn twilio_url(account_sid: &str) -> String { + format!( + "https://api.twilio.com/2010-04-01/Accounts/{}/Messages.json", + account_sid + ) +} + +pub fn init_headers( + account_sid: &str, + auth_token: &str, +) -> Result { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + "Content-Type", + match "application/x-www-form-urlencoded".parse() { + Ok(value) => value, + Err(err) => { + return Err(std::io::Error::other(err)); + } + }, + ); + let auth = match g_auth(account_sid, auth_token) { + Ok(auth) => auth, + Err(err) => { + return Err(std::io::Error::other(err)); + } + }; + headers.insert( + "Authorization", + match auth.parse() { + Ok(auth) => auth, + Err(err) => { + return Err(std::io::Error::other(err)); + } + }, + ); + + Ok(headers) +} + +pub fn initial_params( + recpient: &str, + content: &str, + config_source_phone_number: &str, + config_service_sid: &str, + param: &crate::twilio::types::Parameters, +) -> std::collections::HashMap { + let now = time::OffsetDateTime::now_utc(); + let mut params = std::collections::HashMap::new(); + params.insert(String::from("To"), recpient.to_string()); + params.insert(String::from("ProvideFeedback"), String::from("true")); + params.insert(String::from("ForceDelivery"), String::from("false")); + params.insert(String::from("ContentRetention"), String::from("retain")); + params.insert(String::from("AddressRetention"), String::from("obfuscate")); + params.insert(String::from("SmartEncoded"), String::from("true")); + params.insert(String::from("ShortenUrls"), String::from("true")); + let date = match param.schedule_at { + Some(date_value) => match convert_time_to_iso(date_value) { + Ok(converted) => converted, + Err(err) => { + eprintln!("Error: {err:?}"); + String::new() + } + }, + None => String::new(), + }; + let scheduled_at = match param.schedule_at { + Some(s) => s, + None => time::OffsetDateTime::now_utc(), + }; + if param.schedule && is_scheduleable(Some(&now), Some(&scheduled_at)) { + params.insert(String::from("ScheduleType"), String::from("fixed")); + params.insert(String::from("SendAt"), date.clone()); + } + params.insert(String::from("SendAsMms"), String::from("true")); + params.insert(String::from("RiskCheck"), String::from("enable")); + params.insert(String::from("From"), config_source_phone_number.to_string()); + params.insert( + String::from("MessagingServiceSid"), + config_service_sid.to_string(), + ); + params.insert(String::from("Body"), content.to_string()); + + params +} + fn init_params( contact: &textsender_models::contact::Contact, message: &textsender_models::message::Message, @@ -136,6 +222,11 @@ fn generate_auth( } } +fn g_auth(account_sid: &str, auth_token: &str) -> Result { + let input = format!("{}:{}", account_sid, auth_token); + base64_ng::encode(input.as_bytes()) +} + const DEFAULT_SCHEDULING_SECONDS: i64 = 300; fn is_scheduleable(