Migrate (#4)
sender Build / Check (push) Successful in 27s
sender Build / Test Suite (push) Successful in 32s
sender Build / Rustfmt (push) Successful in 36s
sender Build / Clippy (push) Failing after 37s
sender Build / build (push) Successful in 35s

Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2026-06-06 17:47:45 -04:00
parent b45cd58f6b
commit c32090881d
11 changed files with 541 additions and 3 deletions
+68
View File
@@ -0,0 +1,68 @@
pub const APP_NAME: &str = "sender";
/// Returns numbers and message paths
pub fn get_paths(args: Vec<String>) -> Result<(String, String), std::io::Error> {
if args.len() < 2 {
Err(std::io::Error::other("Not enough arguments"))
} else {
let numbers_path = args[1].clone();
let message_path = args[2].clone();
if !path_exists(&numbers_path) && !path_exists(&message_path) {
Err(std::io::Error::other("Paths do not exists"))
} else {
Ok((numbers_path, message_path))
}
}
}
fn path_exists(path: &str) -> bool {
let file = std::path::Path::new(path);
file.exists()
}
pub async fn parse_config() -> (textsender_models::config::auxiliary::TwilioConfig, bool) {
let mut config = textsender_models::config::auxiliary::TwilioConfig {
..Default::default()
};
let auth_sid_var = textsender_models::envy::environment::get_env("AUTH_SID").await;
let auth_token_var = textsender_models::envy::environment::get_env("AUTH_TOKEN").await;
let phone_number_var = textsender_models::envy::environment::get_env("PHONE_NUMBER").await;
let service_sid_var = textsender_models::envy::environment::get_env("SERVICE_SID").await;
config.account_sid = auth_sid_var.value;
config.auth_token = auth_token_var.value;
config.phone_number = phone_number_var.value;
config.service_sid = service_sid_var.value;
let success = {
!config.account_sid.is_empty()
&& !config.auth_token.is_empty()
&& !config.phone_number.is_empty()
&& !config.service_sid.is_empty()
};
(config, success)
}
#[cfg(test)]
mod tests {
#[test]
fn test_parse_config() {
let (config, success) = async_std::task::block_on(super::parse_config());
assert!(success, "Config was not parsed");
assert_eq!(
false,
config.account_sid.is_empty(),
"Account SID is missing"
);
assert_eq!(false, config.auth_token.is_empty(), "AUTH TOKEN is missing");
assert_eq!(
false,
config.phone_number.is_empty(),
"Phone Number is missing"
);
assert_eq!(
false,
config.service_sid.is_empty(),
"Service SID is missing"
);
}
}
+43
View File
@@ -1,4 +1,47 @@
pub mod config;
pub mod messaging;
pub mod version;
#[tokio::main]
async fn main() {
println!("sender");
let args: Vec<String> = std::env::args().collect();
if version::contains_version(&args) {
version::print_version();
std::process::exit(-1);
}
match config::get_paths(args) {
Ok((n_path, m_path)) => {
println!("Message path: {m_path:?}");
println!("Phone Number path: {n_path:?}");
let (config, success) = config::parse_config().await;
if !success {
eprintln!("Error parsing config");
} else {
let contacts = match messaging::parse_numbers(&n_path) {
Ok(contacts) => contacts,
Err(err) => {
eprintln!("Error: {err:?}");
std::process::exit(-1);
}
};
let message = match messaging::parse_message(&m_path) {
Ok(message) => message,
Err(err) => {
eprintln!("Error: {err:?}");
std::process::exit(-1);
}
};
println!("Message: {:?}", message.content);
messaging::send_messages(&config, &contacts, &message, 5).await;
}
}
Err(err) => {
eprintln!("Error: {err:?}");
}
};
}
+99
View File
@@ -0,0 +1,99 @@
use textsender_models::config::auxiliary::TwilioConfig;
use textsender_models::contact::Contact;
use textsender_models::message::Message;
pub async fn send_messages(
config: &TwilioConfig,
contacts: &Vec<Contact>,
message: &Message,
second_interval: u64,
) {
let long_sleep_limit: i64 = 5;
let mut count: i64 = 0;
for contact in contacts.iter() {
println!("Sending to: {:?}", contact.phone_number);
let param = swoosh::twilio::types::Parameters {
schedule: false,
schedule_at: None,
};
match swoosh::twilio::api::send_mssage(message, contact, param, config).await {
Ok(response) => {
println!("Raw response: {response:?}");
let val = swoosh::twilio::api::response_to_json(response).await;
println!("Response: {val:?}");
}
Err(err) => {
eprintln!("Error: {err:?}");
}
}
if count % long_sleep_limit == 0 {
// Sleep for X seconds
let wait_time = second_interval;
std::thread::sleep(std::time::Duration::from_secs(wait_time));
} else {
// Sleep for a second
std::thread::sleep(std::time::Duration::from_secs(1));
}
count += 1;
}
}
pub fn parse_numbers(filepath: &str) -> Result<Vec<Contact>, std::io::Error> {
match std::fs::File::open(filepath) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let contacts: Vec<Contact> = match serde_json::from_reader(reader) {
Ok(va) => va,
Err(err) => {
return Err(std::io::Error::other(err.to_string()));
}
};
Ok(contacts)
}
Err(err) => Err(err),
}
}
pub fn parse_message(filepath: &str) -> Result<Message, std::io::Error> {
match std::fs::File::open(filepath) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let message: Message = serde_json::from_reader(reader)?;
Ok(message)
}
Err(err) => Err(err),
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_parse_message() {
let message_path = String::from("tests/message-0.json");
match super::parse_message(&message_path) {
Ok(message) => {
assert_eq!(false, message.content.is_empty(), "Content is empty");
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
};
}
#[test]
fn test_parse_numbers() {
let numbers_path = String::from("tests/numbers-0.json");
match super::parse_numbers(&numbers_path) {
Ok(contacts) => {
assert_eq!(false, contacts.is_empty(), "Should not be empty");
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
};
}
}
+20
View File
@@ -0,0 +1,20 @@
pub fn print_version() {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
println!("{name:?} {version:?}");
}
pub fn contains_version(args: &Vec<String>) -> bool {
let valid_flags = vec!["-v", "--version"];
for arg in args {
for valid_flag in &valid_flags {
if valid_flag == arg {
return true;
}
}
}
false
}