Compare commits

..
Author SHA1 Message Date
phoenix e487a2c275 Adding type needed to send message
Release Tagging / release (pull_request) Successful in 30s
swoosh Build / Check (pull_request) Successful in 34s
swoosh Build / Rustfmt (pull_request) Successful in 27s
swoosh Build / Test Suite (pull_request) Successful in 40s
swoosh Build / Clippy (pull_request) Successful in 33s
swoosh Build / build (pull_request) Successful in 33s
2026-06-03 16:09:30 -04:00
phoenix 97dcb360ab Fixed type
Release Tagging / release (pull_request) Successful in 36s
swoosh Build / Check (pull_request) Successful in 36s
swoosh Build / Test Suite (pull_request) Successful in 31s
swoosh Build / Rustfmt (pull_request) Successful in 33s
swoosh Build / Clippy (pull_request) Successful in 32s
swoosh Build / build (pull_request) Successful in 37s
2026-06-03 15:55:09 -04:00
phoenix 7c3b51d6a0 Renaming struct 2026-06-03 15:52:43 -04:00
phoenix c6103ebba4 Adding type that maps to the response when a message is sent 2026-06-03 15:51:57 -04:00
11 changed files with 275 additions and 2529 deletions
-73
View File
@@ -1,73 +0,0 @@
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
+4 -4
View File
@@ -3,24 +3,24 @@ name: Release Tagging
on: on:
push: push:
branches: branches:
- main - v0.3.x
pull_request: pull_request:
branches: branches:
- main - v0.3.x
jobs: jobs:
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v5
with: with:
fetch-depth: 0 # Important for git describe --tags fetch-depth: 0 # Important for git describe --tags
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
components: cargo components: cargo
- name: Extract Version from Cargo.toml - name: Extract Version from Cargo.toml
+20 -25
View File
@@ -3,27 +3,25 @@ name: swoosh Build
on: on:
push: push:
branches: branches:
- main - v0.3.x
pull_request:
concurrency: branches:
group: ${{ github.workflow }}-${{ github.ref }} - v0.3.x
cancel-in-progress: true
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/textsender-models_deploy_key
@@ -34,16 +32,15 @@ jobs:
name: Test Suite name: Test Suite
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/textsender-models_deploy_key
@@ -54,17 +51,16 @@ jobs:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/textsender-models_deploy_key
@@ -75,17 +71,16 @@ jobs:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy - run: rustup component add clippy
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/textsender-models_deploy_key
@@ -96,18 +91,18 @@ jobs:
name: build name: build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/textsender-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/textsender-models_deploy_key
cargo build cargo build
Generated
+199 -2028
View File
File diff suppressed because it is too large Load Diff
+10 -8
View File
@@ -1,17 +1,19 @@
[package] [package]
name = "swoosh" name = "swoosh"
version = "0.5.4" version = "0.2.1"
edition = "2024" edition = "2024"
rust-version = "1.97" rust-version = "1.95"
license = "MIT"
description = "Library to send text messages" description = "Library to send text messages"
[dependencies] [dependencies]
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.150" } serde_json = { version = "1.0.149" }
reqwest = { version = "0.13.4", features = ["form", "json", "blocking", "multipart", "stream"] } rand = { version = "0.10.1" }
time = { version = "0.3.53", features = ["formatting", "macros", "parsing", "serde"] } time = { version = "0.3.47", features = ["formatting", "macros", "parsing", "serde"] }
base64-ng = { version = "1.3.7" } uuid = { version = "1.23.1", features = ["v4", "serde"] }
schedtxt_models = { git = "ssh://git@git.kundeng.us/phoenix/schedtxt_models.git", tag = "v0.5.3", features = ["config", "contact", "message"] } 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-20-15820d5505-111" }
[dev-dependencies] [dev-dependencies]
tempfile = { version = "3.27.0" }
-22
View File
@@ -1,22 +0,0 @@
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
@@ -1,3 +0,0 @@
# swoosh
A library used to send text messages and is used in conjunction of the `schedtxt`
project.
-106
View File
@@ -1,107 +1 @@
pub mod twilio; 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,
}
-245
View File
@@ -1,246 +1 @@
pub mod types; 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,
Err(_err) => serde_json::Value::Null,
}
}
/// 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 {
match reqwest::Client::builder().build() {
Ok(client) => {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
"Content-Type",
"application/x-www-form-urlencoded".parse().unwrap(),
);
headers.insert("Accept", "application/json".parse().unwrap());
let auth = generate_auth(config).unwrap();
headers.insert("Authorization", auth.parse().unwrap());
let params = init_params(contact, msg, config, param);
let url = format!(
"https://api.twilio.com/2010-04-01/Accounts/{}/Messages.json",
config.account_sid
);
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.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
}
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: &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()) {
Ok(encoded) => Ok(format!("Basic {encoded}")),
Err(err) => Err(err),
}
}
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>,
) -> bool {
match scheduled {
Some(schedule_at) => {
let early = now
.unwrap()
.checked_add(time::Duration::seconds(DEFAULT_SCHEDULING_SECONDS))
.unwrap();
*schedule_at > early
}
None => false,
}
}
+36 -14
View File
@@ -1,38 +1,60 @@
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct TwilioAPIMessage { pub struct TwilioAPIMessage {
pub account_sid: String, pub account_sid: String,
pub api_version: String, pub api_version: String,
pub body: String, pub body: String,
#[serde(with = "time::serde::rfc3339::option")] // pub date_created: time::OffsetDateTime,
pub date_created: Option<time::OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")]
pub date_sent: Option<time::OffsetDateTime>, pub date_sent: Option<time::OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")]
pub date_updated: Option<time::OffsetDateTime>, pub date_updated: Option<time::OffsetDateTime>,
pub direction: String, pub direction: String,
// Not definite of what type this is // Not definite of what type this is
#[serde(skip_serializing_if = "String::is_empty")] pub error_code: Option<String>,
pub error_code: String,
// Not definite of what type this is // Not definite of what type this is
#[serde(skip_serializing_if = "String::is_empty")] pub error_message: Option<String>,
pub error_message: String,
pub from: String, pub from: String,
pub messaging_service_sid: String, pub messaging_service_sid: String,
pub num_media: String, pub num_media: String,
pub num_segments: String, pub num_segments: String,
#[serde(skip_serializing_if = "String::is_empty")] pub price: Option<String>,
pub price: String, pub price_unit: Option<String>,
#[serde(skip_serializing_if = "String::is_empty")]
pub price_unit: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub sid: String, pub sid: String,
pub status: String, pub status: String,
pub subresource_uris: SubresourceURI, pub subresource_uris: SubresourceURI,
pub to: String, pub to: String,
pub uri: String, pub uri: String
} }
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct SubresourceURI { pub struct SubresourceURI {
pub media: String, pub media: String,
} }
/*
{
"account_sid": "ACefa1ef516314c9d1a68cbd657de49277",
"api_version": "2010-04-01",
"body": "This is a test",
"date_created": "Wed, 03 Jun 2026 19:40:55 +0000",
"date_sent": null,
"date_updated": "Wed, 03 Jun 2026 19:40:55 +0000",
"direction": "outbound-api",
"error_code": null,
"error_message": null,
"from": "+12243026041",
"messaging_service_sid": "MG803f3676706b92eb02e18dd820c447f2",
"num_media": "0",
"num_segments": "0",
"price": null,
"price_unit": null,
"sid": "SMf5c09c776c931bdb00c6e274da2ce34d",
"status": "scheduled",
"subresource_uris": {
"media": "/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SMf5c09c776c931bdb00c6e274da2ce34d/Media.json"
},
"to": "+16303831708",
"uri": "/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SMf5c09c776c931bdb00c6e274da2ce34d.json"
}
*/
+6 -1
View File
@@ -1,4 +1,9 @@
/// Parameters - Controls flow of sending message
// Things needed to send a message
// Contact - Receipient to send to (textsender-models)
// Message - The content to sent (textsender-models)
// Parameters - Controls flow of sending message
// Config - Contains info needed to send message (textsender-models)
pub struct Parameters { pub struct Parameters {
pub schedule: bool, pub schedule: bool,
pub schedule_at: Option<time::OffsetDateTime>, pub schedule_at: Option<time::OffsetDateTime>,