diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..a8a8453 --- /dev/null +++ b/.env.sample @@ -0,0 +1,4 @@ +AUTH_SID=n9c43yr3f2c4743f7y4329dnq238f7 +AUTH_TOKEN=9nmc4820qf4cor838r4fy3o84rfy348fyr +PHONE_NUMBER="+1234567890" +SERVICE_SID=n9f4dw4y327453947dhny23q7hy4379y5 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index e006b3a..e1a1a36 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -35,6 +35,11 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: 1.95 + env: + AUTH_SID: 'n9c43yr3f2c4743f7y4329dnq238f7' + AUTH_TOKEN: '9nmc4820qf4cor838r4fy3o84rfy348fyr' + PHONE_NUMBER: '+1234567890' + SERVICE_SID: 'n9f4dw4y327453947dhny23q7hy4379y5' - run: | mkdir -p ~/.ssh echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key @@ -42,6 +47,8 @@ jobs: ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts eval $(ssh-agent -s) ssh-add -v ~/.ssh/deploy_key + + cp .env.sample .env cargo test fmt: diff --git a/.gitignore b/.gitignore index 2f7896d..b7f7b51 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +.env target/ diff --git a/Cargo.lock b/Cargo.lock index 562e9ce..856ea61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,12 +23,131 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "aws-lc-rs" version = "1.17.0" @@ -69,6 +188,19 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -135,6 +267,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const_format" version = "0.2.36" @@ -200,6 +341,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "deranged" version = "0.5.8" @@ -258,6 +405,39 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -364,6 +544,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -445,6 +638,18 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.14" @@ -485,6 +690,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "http" version = "1.4.1" @@ -821,6 +1032,15 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -833,6 +1053,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -853,6 +1079,9 @@ name = "log" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +dependencies = [ + "value-bag", +] [[package]] name = "lru-slab" @@ -958,6 +1187,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -993,12 +1228,43 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -1278,6 +1544,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.40" @@ -1420,9 +1699,11 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "sender" -version = "0.0.8" +version = "0.1.0" dependencies = [ + "async-std", "futures", + "serde_json", "swoosh", "textsender_models", "tokio", @@ -1919,6 +2200,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 678b887..b9ffaa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "sender" -version = "0.0.8" +version = "0.1.0" edition = "2024" rust-version = "1.95" [dependencies] # uuid = { version = "1.23.1", features = ["v4", "serde"] } # serde = { version = "1.0.228", features = ["derive"] } -# serde_json = { version = "1.0.149" } +serde_json = { version = "1.0.149" } tokio = { version = "1.52.2", features = ["full"] } futures = { version = "0.3.32" } textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.2.5-v0.3.0-migrate-38dbcb09f7-111" } swoosh = { git = "ssh://git@git.kundeng.us/phoenix/swoosh.git", tag = "v0.3.1-main-ab01daafea-871" } [dev-dependencies] +async-std = { version = "1.13.2" } diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..b091ca9 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,68 @@ +pub const APP_NAME: &str = "sender"; + +/// Returns numbers and message paths +pub fn get_paths(args: Vec) -> 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" + ); + } +} diff --git a/src/main.rs b/src/main.rs index 6d7b17b..51d1427 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,47 @@ +pub mod config; +pub mod messaging; +pub mod version; + #[tokio::main] async fn main() { println!("sender"); + let args: Vec = 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:?}"); + } + }; } diff --git a/src/messaging/mod.rs b/src/messaging/mod.rs new file mode 100644 index 0000000..a7f1893 --- /dev/null +++ b/src/messaging/mod.rs @@ -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, + 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, std::io::Error> { + match std::fs::File::open(filepath) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let contacts: Vec = 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 { + 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:?}"); + } + }; + } +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..22ea1eb --- /dev/null +++ b/src/version.rs @@ -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) -> bool { + let valid_flags = vec!["-v", "--version"]; + + for arg in args { + for valid_flag in &valid_flags { + if valid_flag == arg { + return true; + } + } + } + + false +} diff --git a/tests/message-0.json b/tests/message-0.json new file mode 100644 index 0000000..2a9a1e6 --- /dev/null +++ b/tests/message-0.json @@ -0,0 +1,3 @@ +{ + "content": "What is life?" +} diff --git a/tests/numbers-0.json b/tests/numbers-0.json new file mode 100644 index 0000000..2a8e1de --- /dev/null +++ b/tests/numbers-0.json @@ -0,0 +1,5 @@ +[ + { + "phone_number": "+10123456789" + } +]