Compare commits

..
Author SHA1 Message Date
phoenix 7873d09d3e Adding license (#18)
Release Tagging / release (push) Successful in 40s
swoosh Build / Check (push) Successful in 1m6s
swoosh Build / Rustfmt (push) Successful in 28s
swoosh Build / Test Suite (push) Successful in 1m31s
swoosh Build / Clippy (push) Successful in 1m37s
swoosh Build / build (push) Successful in 2m1s
Reviewed-on: #18
2026-07-14 11:18:40 -04:00
phoenix 84ba4c21ec Dependency name change (#17)
Release Tagging / release (push) Successful in 59s
swoosh Build / Rustfmt (push) Successful in 39s
swoosh Build / Test Suite (push) Successful in 1m41s
swoosh Build / Clippy (push) Successful in 1m33s
swoosh Build / Check (push) Successful in 3m10s
swoosh Build / build (push) Successful in 1m48s
Reviewed-on: #17
2026-07-13 11:44:24 -04:00
phoenix 1c49242bdf Update rust (#16)
Release Tagging / release (push) Successful in 1m13s
swoosh Build / Check (push) Successful in 1m34s
swoosh Build / build (push) Successful in 1m56s
swoosh Build / Rustfmt (push) Successful in 27s
swoosh Build / Clippy (push) Successful in 2m10s
swoosh Build / Test Suite (push) Successful in 4m6s
Reviewed-on: #16
2026-07-10 11:42:16 -04:00
phoenix 50e4b209f5 Update rust (#15)
swoosh pr / Check (pull_request) Successful in 1m50s
swoosh pr / Clippy (pull_request) Successful in 1m15s
swoosh pr / Rustfmt (pull_request) Successful in 1m29s
Release Tagging / release (pull_request) Successful in 1m46s
Release Tagging / release (push) Successful in 1m1s
swoosh Build / Check (push) Successful in 1m0s
swoosh Build / Rustfmt (push) Successful in 42s
swoosh Build / Test Suite (push) Successful in 2m21s
swoosh Build / Clippy (push) Successful in 1m27s
swoosh Build / build (push) Successful in 1m33s
Reviewed-on: #15
2026-07-05 17:08:08 -04:00
phoenix d36483e6f8 Fix (#14)
swoosh Build / Test Suite (push) Successful in 1m6s
swoosh Build / Rustfmt (push) Successful in 33s
swoosh Build / Check (push) Successful in 1m46s
Release Tagging / release (push) Successful in 44s
swoosh Build / Clippy (push) Successful in 1m39s
swoosh Build / build (push) Successful in 1m39s
swoosh pr / Check (pull_request) Has been cancelled
swoosh pr / Rustfmt (pull_request) Has been cancelled
swoosh pr / Clippy (pull_request) Has been cancelled
Release Tagging / release (pull_request) Successful in 42s
Reviewed-on: #14
2026-07-04 20:59:05 -04:00
phoenix 768b2b6d08 tsk-11: New code to send message (#13)
Release Tagging / release (push) Successful in 46s
swoosh Build / Rustfmt (push) Successful in 32s
swoosh Build / Test Suite (push) Successful in 1m34s
swoosh Build / Check (push) Successful in 1m52s
swoosh Build / Clippy (push) Successful in 1m28s
swoosh Build / build (push) Successful in 1m15s
Closes #11

Reviewed-on: #13
2026-07-03 00:45:59 -04:00
phoenix e1e730fd1c bump: textsender_models (#10)
Release Tagging / release (push) Successful in 59s
swoosh Build / Rustfmt (push) Successful in 28s
swoosh Build / build (push) Successful in 1m29s
swoosh Build / Clippy (push) Successful in 2m6s
swoosh Build / Check (push) Successful in 1m11s
swoosh Build / Test Suite (push) Successful in 1m37s
Reviewed-on: #10
2026-06-27 17:27:34 -04:00
phoenix 9f174da464 Rust 1.96 (#9)
swoosh Build / Test Suite (push) Successful in 1m36s
Release Tagging / release (push) Successful in 1m42s
swoosh Build / Rustfmt (push) Successful in 40s
swoosh Build / Check (push) Successful in 1m30s
swoosh Build / Clippy (push) Successful in 1m29s
swoosh Build / build (push) Successful in 2m0s
Reviewed-on: #9
2026-06-14 17:52:28 -04:00
phoenix 3cfa757a07 Updating packages (#8)
Release Tagging / release (push) Successful in 55s
swoosh Build / Check (push) Successful in 1m34s
swoosh Build / Rustfmt (push) Successful in 39s
swoosh Build / Test Suite (push) Successful in 2m2s
swoosh Build / Clippy (push) Successful in 1m24s
swoosh Build / build (push) Successful in 1m21s
Reviewed-on: #8
2026-06-13 16:43:23 -04:00
phoenix 1aa2c5870c Updating package name (#7)
Release Tagging / release (push) Successful in 40s
swoosh Build / Rustfmt (push) Successful in 37s
swoosh Build / Check (push) Successful in 1m37s
swoosh Build / Clippy (push) Successful in 1m13s
swoosh Build / build (push) Successful in 1m24s
swoosh Build / Test Suite (push) Successful in 2m14s
Reviewed-on: #7
2026-06-13 12:33:21 -04:00
phoenix 122f1183ac Bug fix (#6)
Release Tagging / release (push) Successful in 53s
swoosh Build / Check (push) Successful in 1m6s
swoosh Build / Rustfmt (push) Successful in 29s
swoosh Build / Test Suite (push) Successful in 1m14s
swoosh Build / Clippy (push) Successful in 1m1s
swoosh Build / build (push) Successful in 1m10s
Reviewed-on: #6
2026-06-07 15:21:23 -04:00
9 changed files with 1157 additions and 583 deletions
+73
View File
@@ -0,0 +1,73 @@
name: swoosh pr
on:
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
name: Check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key
cargo check
fmt:
name: Rustfmt
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key
cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key
cargo clippy -- -D warnings
+2 -2
View File
@@ -13,14 +13,14 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0 # Important for git describe --tags
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.95
toolchain: 1.97
components: cargo
- name: Extract Version from Cargo.toml
+14 -13
View File
@@ -4,19 +4,20 @@ on:
push:
branches:
- main
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
name: Check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.95
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
@@ -33,10 +34,10 @@ jobs:
name: Test Suite
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.95
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
@@ -53,10 +54,10 @@ jobs:
name: Rustfmt
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.95
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt
- run: |
@@ -74,10 +75,10 @@ jobs:
name: Clippy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.95
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy
- run: |
@@ -95,10 +96,10 @@ jobs:
name: build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.95
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
Generated
+755 -506
View File
File diff suppressed because it is too large Load Diff
+8 -15
View File
@@ -1,24 +1,17 @@
[package]
name = "swoosh"
version = "0.3.2"
version = "0.5.4"
edition = "2024"
rust-version = "1.95"
rust-version = "1.97"
license = "MIT"
description = "Library to send text messages"
[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149" }
tokio = { version = "1.52.2", features = ["full"] }
futures = { version = "0.3.32" }
http = { version = "1.4.0" }
reqwest = { version = "0.13.3", features = ["form", "json", "blocking", "multipart", "stream"] }
rand = { version = "0.10.1" }
time = { version = "0.3.47", features = ["formatting", "macros", "parsing", "serde"] }
uuid = { version = "1.23.1", features = ["v4", "serde"] }
base64-ng = { version = "1.0.6" }
const_format = { version = "0.2.36" }
josekit = { version = "0.10.3" }
textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0" }
serde_json = { version = "1.0.150" }
reqwest = { version = "0.13.4", features = ["form", "json", "blocking", "multipart", "stream"] }
time = { version = "0.3.53", features = ["formatting", "macros", "parsing", "serde"] }
base64-ng = { version = "1.3.7" }
schedtxt_models = { git = "ssh://git@git.kundeng.us/phoenix/schedtxt_models.git", tag = "v0.5.3", features = ["config", "contact", "message"] }
[dev-dependencies]
tempfile = { version = "3.27.0" }
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2026 Kun Deng.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# swoosh
A library used to send text messages and is used in conjunction of the `schedtxt`
project.
+106
View File
@@ -1 +1,107 @@
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,
account_sid: &str,
) {
self.config = VendorConfig {
auth_token: auth_token.to_string(),
phone_number: phone_number.to_string(),
service_sid: service_sid.to_string(),
account_sid: account_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<reqwest::Response, std::io::Error> {
if self.recipient.phone_number.is_empty() {
Err(std::io::Error::other("Recipient not provided"))
} else if self.message.content.is_empty() {
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.account_sid.is_empty()
{
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.account_sid);
let headers = match twilio::api::init_headers(
&self.config.account_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(&params);
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 account_sid: String,
}
#[derive(Default, Debug)]
pub struct Message {
pub content: String,
}
#[derive(Default, Debug)]
pub struct Recipient {
pub phone_number: String,
}
+174 -47
View File
@@ -1,5 +1,7 @@
pub mod types;
/// Converts the response into a json value
/// That way you do not need to share swoosh's dependency
pub async fn response_to_json(response: reqwest::Response) -> serde_json::Value {
match response.json().await {
Ok(json_body) => json_body,
@@ -7,18 +9,21 @@ pub async fn response_to_json(response: reqwest::Response) -> serde_json::Value
}
}
pub async fn send_mssage(
msg: &textsender_models::message::Message,
contact: &textsender_models::contact::Contact,
param: crate::twilio::types::Parameters,
config: &textsender_models::config::auxiliary::TwilioConfig,
/// Backwards compatible
pub use send_message as send_mssage;
/// Send a message
pub async fn send_message(
msg: &schedtxt_models::message::Message,
contact: &schedtxt_models::contact::Contact,
param: &crate::twilio::types::Parameters,
config: &schedtxt_models::config::auxiliary::TwilioConfig,
) -> Result<reqwest::Response, std::io::Error> {
if config.account_sid.is_empty() {
Err(std::io::Error::other("Account SID is empty"))
} else if config.auth_token.is_empty() {
Err(std::io::Error::other(" Auth token is empty"))
} else {
let now = time::OffsetDateTime::now_utc();
match reqwest::Client::builder().build() {
Ok(client) => {
let mut headers = reqwest::header::HeaderMap::new();
@@ -30,27 +35,7 @@ pub async fn send_mssage(
let auth = generate_auth(config).unwrap();
headers.insert("Authorization", auth.parse().unwrap());
let mut params = std::collections::HashMap::new();
params.insert("To", contact.phone_number.as_str());
params.insert("ProvideFeedback", "true");
params.insert("ForceDelivery", "false");
params.insert("ContentRetention", "retain");
params.insert("AddressRetention", "obfuscate");
params.insert("SmartEncoded", "true");
params.insert("ShortenUrls", "true");
let date = match param.schedule_at {
Some(date_value) => date_value.to_string(),
None => String::new(),
};
if param.schedule && is_scheduleable(&Some(now), &param.schedule_at) {
params.insert("ScheduleType", "fixed");
params.insert("SendAt", date.as_str());
}
params.insert("SendAsMms", "true");
params.insert("RiskCheck", "enable");
params.insert("From", config.phone_number.as_str());
params.insert("MessagingServiceSid", config.service_sid.as_str());
params.insert("Body", msg.content.as_str());
let params = init_params(contact, msg, config, param);
let url = format!(
"https://api.twilio.com/2010-04-01/Accounts/{}/Messages.json",
@@ -73,8 +58,162 @@ pub async fn send_mssage(
}
}
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<reqwest::header::HeaderMap, std::io::Error> {
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 format!("Basic {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<String, String> {
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: &schedtxt_models::contact::Contact,
message: &schedtxt_models::message::Message,
config: &schedtxt_models::config::auxiliary::TwilioConfig,
param: &crate::twilio::types::Parameters,
) -> std::collections::HashMap<String, String> {
let now = time::OffsetDateTime::now_utc();
let mut params = std::collections::HashMap::new();
params.insert(String::from("To"), contact.phone_number.clone());
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.phone_number.clone());
params.insert(
String::from("MessagingServiceSid"),
config.service_sid.clone(),
);
params.insert(String::from("Body"), message.content.clone());
/*
let mut params = std::collections::HashMap::new();
params.insert("StatusCallback", "http://OjQozHznkhNTTR.vpnrM1zdXFuiQ");
params.insert("MaxPrice", "1");
params.insert("ProvideFeedback", "true");
params.insert("Attempt", "5");
params.insert("ValidityPeriod", "1537");
params.insert("ForceDelivery", "false");
params.insert("ContentRetention", "retain");
params.insert("AddressRetention", "obfuscate");
params.insert("SmartEncoded", "true");
params.insert("PersistentAction", "string");
params.insert("PersistentAction", "string");
params.insert("ShortenUrls", "true");
params.insert("SendAsMms", "true");
params.insert("RiskCheck", "enable");
*/
params
}
fn convert_time_to_iso(time: time::OffsetDateTime) -> Result<String, time::error::Format> {
use time::format_description::well_known::Iso8601;
time.format(&Iso8601::DEFAULT)
}
fn generate_auth(
config: &textsender_models::config::auxiliary::TwilioConfig,
config: &schedtxt_models::config::auxiliary::TwilioConfig,
) -> Result<String, base64_ng::EncodeError> {
let input = format!("{}:{}", config.account_sid, config.auth_token);
match base64_ng::encode(input.as_bytes()) {
@@ -82,29 +221,17 @@ fn generate_auth(
Err(err) => Err(err),
}
}
/*
let mut params = std::collections::HashMap::new();
params.insert("StatusCallback", "http://OjQozHznkhNTTR.vpnrM1zdXFuiQ");
params.insert("MaxPrice", "1");
params.insert("ProvideFeedback", "true");
params.insert("Attempt", "5");
params.insert("ValidityPeriod", "1537");
params.insert("ForceDelivery", "false");
params.insert("ContentRetention", "retain");
params.insert("AddressRetention", "obfuscate");
params.insert("SmartEncoded", "true");
params.insert("PersistentAction", "string");
params.insert("PersistentAction", "string");
params.insert("ShortenUrls", "true");
params.insert("SendAsMms", "true");
params.insert("RiskCheck", "enable");
*/
fn g_auth(account_sid: &str, auth_token: &str) -> Result<String, base64_ng::EncodeError> {
let input = format!("{}:{}", account_sid, auth_token);
base64_ng::encode(input.as_bytes())
}
const DEFAULT_SCHEDULING_SECONDS: i64 = 300;
fn is_scheduleable(
now: &Option<time::OffsetDateTime>,
scheduled: &Option<time::OffsetDateTime>,
now: Option<&time::OffsetDateTime>,
scheduled: Option<&time::OffsetDateTime>,
) -> bool {
match scheduled {
Some(schedule_at) => {