1use base64::Engine;
2use chrono::{DateTime, NaiveDateTime, Utc};
3
4use reqwest::header::HeaderMap;
5use reqwest::header::HeaderValue;
6use reqwest::Client;
7use reqwest::Method;
8use reqwest::StatusCode;
9use serde_json::json;
10
11use tracing::warn;
12
13use super::{BranchSendResult, SendError, CUSTOM_ENGINE};
14use crate::BranchSendNotifyResult;
15use crate::SendNotifyError;
16use crate::SendNotifyItem;
17use crate::SendNotifyStatus;
18use crate::{
19 now_time, response_check, response_msg, sms_lib::phone_numbers_check, BranchSendDetailResult,
20 SendDetailItem, SendResultItem, SendStatus,
21};
22
23pub struct CloOpenSms {}
24
25impl CloOpenSms {
26 pub fn send_notify_output(res: &Result<(), String>) -> (StatusCode, String) {
27 match res {
28 Ok(_) => (StatusCode::OK, "".to_owned()),
29 Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_owned()),
30 }
31 }
32 pub fn send_notify_parse(notify_data: &str) -> BranchSendNotifyResult {
35 if gjson::get(notify_data, "Request.smsType")
52 .to_string()
53 .as_str()
54 != "1"
55 {
56 return Err(SendNotifyError::Ignore);
57 }
58 let tmp = gjson::get(notify_data, "Request");
59
60 let receive_time =
61 chrono::NaiveDateTime::parse_from_str(&tmp.get("recvTime").to_string(), "%Y%m%d%H%M%S")
62 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
63 .ok();
64 let send_time =
65 chrono::NaiveDateTime::parse_from_str(&tmp.get("dateSent").to_string(), "%Y%m%d%H%M%S")
66 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
67 .ok();
68
69 Ok(vec![SendNotifyItem {
70 send_id: tmp.get("content").to_string(),
71 status: if send_time.unwrap_or_default() > 0 {
72 if tmp.get("status").i8() == 0 {
73 SendNotifyStatus::Completed
74 } else {
75 SendNotifyStatus::Failed
76 }
77 } else {
78 SendNotifyStatus::Progress
79 },
80 message: tmp.get("deliverCode").to_string(),
81 send_time,
82 receive_time,
83 code: tmp.get("deliverCode").to_string(),
84 mobile: Some(tmp.get("fromNum").to_string()),
85 }])
86 }
87 fn sig(account_sid: &str, account_token: &str, time: u64) -> String {
89 let datetime = NaiveDateTime::from_timestamp_opt(time as i64, 0).unwrap_or_default();
90 let datetime_str = datetime.format("%Y%m%d%H%M%S").to_string();
91 let sig_str = format!("{}{}{}", account_sid, account_token, datetime_str);
92 let result = md5::compute(sig_str);
93 format!("{:x}", result).to_uppercase()
94 }
95 fn auth(account_sid: &str, time: u64) -> String {
97 let datetime = NaiveDateTime::from_timestamp_opt(time as i64, 0).unwrap_or_default();
98 let datetime_str = datetime.format("%Y%m%d%H%M%S").to_string();
99 let auth_str = format!("{}:{}", account_sid, datetime_str);
100 CUSTOM_ENGINE.encode(auth_str)
101 }
102
103 pub async fn send_detail(
104 client: Client,
105 account_sid: &str,
106 account_token: &str,
107 app_id: &str,
108 ) -> BranchSendDetailResult {
109 let ntime = now_time().unwrap_or_default();
110 let sig = Self::sig(account_sid, account_token, ntime);
111 let auth = Self::auth(account_sid, ntime);
112 let reqjson = json!({
116 "smsType": "1",
117 "count": "500",
118 "appId": app_id,
119 });
120
121 let mut headers = HeaderMap::new();
122
123 if let Ok(value) = HeaderValue::from_str("application/json;charset=utf-8") {
124 headers.insert("Content-Type", value);
125 }
126 if let Ok(value) = HeaderValue::from_str(auth.as_str()) {
127 headers.insert("Authorization", value);
128 }
129
130 let request = client
131 .request(
132 Method::POST,
133 format!(
134 "https://app.cloopen.com:8883/2013-12-26/Accounts/{}/SMS/GetArrived?sig={}",
135 account_sid, sig
136 ),
137 )
138 .headers(headers)
139 .body(reqjson.to_string());
140 let result = request.send().await.map_err(|e| e.to_string())?;
141 let (status, res) = response_check(result, true).await?;
142 if status != StatusCode::OK {
144 warn!("sms response fail: {}", &res);
145 return Err(format!("http bad:{}", res));
146 }
147 let code = gjson::get(&res, "statusCode").to_string();
148 if code.as_str() == "000000" {
149 let items = gjson::get(&res, "reports");
150 let mut out = Vec::with_capacity(items.array().len());
151 for tmp in items.array() {
152 let receive_time = chrono::NaiveDateTime::parse_from_str(
153 &tmp.get("recvTime").to_string(),
154 "%Y%m%d%H%M%S",
155 )
156 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
157 .ok();
158 let send_time = chrono::NaiveDateTime::parse_from_str(
159 &tmp.get("dateSent").to_string(),
160 "%Y%m%d%H%M%S",
161 )
162 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
163 .ok();
164 out.push(SendDetailItem {
165 send_id: tmp.get("content").to_string(),
166 status: if send_time.unwrap_or_default() > 0 {
167 if tmp.get("status").i8() == 0 {
168 SendNotifyStatus::Completed
169 } else {
170 SendNotifyStatus::Failed
171 }
172 } else {
173 SendNotifyStatus::Progress
174 },
175 message: match tmp.get("status").i8() {
176 0 => "OK".to_owned(),
177 _ => "send fail".to_owned(),
178 },
179 send_time,
180 receive_time,
181 code: tmp.get("status").to_string(),
182 mobile: Some(tmp.get("fromNum").to_string()),
183 });
184 }
185 return Ok(out);
186 }
187 Err(response_msg(&res, &["statusMsg"]))
188 }
189 pub fn branch_limit() -> u16 {
190 200
191 }
192 pub async fn branch_send(
194 client: Client,
195 account_sid: &str,
196 account_token: &str,
197 app_id: &str,
198 template_id: &str,
199 template_arr: Option<Vec<String>>,
200 phone_numbers: &[&str],
201 ) -> BranchSendResult {
202 let phone_numbers = phone_numbers_check(phone_numbers)?;
203 let ntime = 1700447467; let sig = Self::sig(account_sid, account_token, ntime);
206 let auth = Self::auth(account_sid, ntime);
207 let reqjson = json!({
208 "to": phone_numbers.join(","),
209 "templateId": template_id,
210 "appId": app_id,
211 "datas":template_arr
212 });
213
214 let mut headers = HeaderMap::new();
215 if let Ok(value) = HeaderValue::from_str("application/json") {
216 headers.insert("Accept", value);
217 }
218 if let Ok(value) = HeaderValue::from_str("application/json;charset=utf-8") {
219 headers.insert("Content-Type", value);
220 }
221 if let Ok(value) = HeaderValue::from_str(auth.as_str()) {
222 headers.insert("Authorization", value);
223 }
224
225 let url = format!(
226 "https://app.cloopen.com:8883/2013-12-26/Accounts/{}/SMS/TemplateSMS?sig={}",
227 account_sid, sig
228 );
229
230 let request = client
233 .request(Method::POST, url)
234 .headers(headers)
235 .body(reqjson.to_string());
236 let result = request
237 .send()
238 .await
239 .map_err(|e| SendError::Next(format!("request send fail:{}", e)))?;
240 let (status, res) = response_check(result, true)
241 .await
242 .map_err(SendError::Next)?;
243 if status != StatusCode::OK {
244 warn!("sms response fail: {}", &res);
245 return Err(SendError::Next(format!("http bad:{}", res)));
246 }
247 let code = gjson::get(&res, "statusCode").to_string();
249 if code.as_str() == "000000" {
250 return Ok(phone_numbers
252 .iter()
253 .map(|e| SendResultItem {
254 mobile: e.to_string(),
255 status: SendStatus::Progress,
256 message: gjson::get(&res, "statusMsg").to_string(),
257 send_id: gjson::get(&res, "templateSMS.smsMessageSid").to_string(),
258 })
259 .collect());
260 }
261 Err(SendError::Next(response_msg(&res, &["statusMsg"])))
262 }
263}