1use chrono::{DateTime, Utc};
2use reqwest::header::HeaderMap;
3use reqwest::header::HeaderValue;
4use reqwest::Client;
5use reqwest::Method;
6use reqwest::StatusCode;
7use ring::digest;
8use serde_json::json;
9use tracing::{debug, info, warn};
10
11use super::{BranchSendDetailResult, BranchSendResult, SendDetailItem, SendResultItem, SendStatus};
12
13use crate::SendNotifyStatus;
14use crate::{
15 sms_lib::{now_time, phone_numbers_check, rand_str, response_check, response_msg, SendError},
16 BranchSendNotifyResult, SendNotifyError, SendNotifyItem,
17};
18
19pub struct NeteaseSms {}
20impl NeteaseSms {
21 pub fn send_notify_output(res: &Result<(), String>) -> String {
22 match res {
23 Ok(_) => {
24 json!({
25 "code" : 200,
26 "msg" : "接收成功"
27 })
28 }
29 Err(err) => {
30 json!({
31 "code" : 500,
32 "msg" : err
33 })
34 }
35 }
36 .to_string()
37 }
38 pub fn send_notify_parse(
41 notify_data: &str,
42 sign_data: Option<(&str, &str, &str, &str)>,
47 ) -> BranchSendNotifyResult {
48 if let Some((app_secret, header_md5, header_curtime, header_checksum)) = sign_data {
49 let result = md5::compute(notify_data.as_bytes());
50 let hex_string = format!("{:x}", result);
51 debug!("body md5:{}", hex_string);
52 if hex_string.to_lowercase() != header_md5.to_lowercase() {
53 info!("md5 not match:{}!={}", hex_string, header_md5);
54 return Err(SendNotifyError::Sign(format!(
55 "body md5 not match on :{}",
56 hex_string
57 )));
58 }
59 let data = format!("{}{}{}", app_secret, hex_string, header_curtime);
60 let actual = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, data.as_bytes());
61 let sign = hex::encode(actual.as_ref());
62 if sign.to_lowercase() != header_checksum.to_lowercase() {
63 info!("md5 not match:{}!={}", hex_string, header_checksum);
64 return Err(SendNotifyError::Sign(format!("sign bad on :{}", sign)));
65 }
66 }
67 if gjson::get(notify_data, "eventType").to_string().as_str() != "11" {
68 return Err(SendNotifyError::Ignore);
69 }
70 let items = gjson::get(notify_data, "objects");
80 let mut out = Vec::with_capacity(items.array().len());
81 for tmp in items.array() {
82 let send_time = chrono::NaiveDateTime::parse_from_str(
83 &tmp.get("sendTime").to_string(),
84 "%Y-%m-%d %H:%M:%S",
85 )
86 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
87 .ok();
88 let receive_time = chrono::NaiveDateTime::parse_from_str(
89 &tmp.get("reportTime").to_string(),
90 "%Y-%m-%d %H:%M:%S",
91 )
92 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
93 .ok();
94
95 out.push(SendNotifyItem {
96 status: if tmp.get("result").str() == "DELIVRD" {
97 SendNotifyStatus::Completed
98 } else {
99 SendNotifyStatus::Failed
100 },
101 message: tmp.get("result").to_string(),
102
103 send_time,
104 receive_time,
105 code: tmp.get("result").to_string(),
106 send_id: tmp.get("sendid").to_string(),
107 mobile: Some(tmp.get("mobile").to_string()),
108 });
109 }
110 Ok(out)
111 }
112 fn signature(app_secret: &str) -> (String, String, String) {
114 let nowtime = now_time().unwrap_or_default();
115 let randstring = rand_str(32);
116 let check_sum = {
117 let tmp = format!("{}{}{}", app_secret, randstring, nowtime);
118 let sha1 = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, tmp.as_bytes());
119 hex::encode(sha1)
120 };
121 (randstring, nowtime.to_string(), check_sum)
123 }
124
125 pub async fn send_detail(
126 client: Client,
127 app_key: &str,
128 app_secret: &str,
129 sendid: &str,
130 ) -> BranchSendDetailResult {
131 let (randstring, nowtime, check_sum) = Self::signature(app_secret);
132
133 let mut headers = HeaderMap::new();
134 if let Ok(value) = HeaderValue::from_str(app_key) {
135 headers.insert("AppKey", value);
136 }
137 if let Ok(value) = HeaderValue::from_str(&randstring) {
138 headers.insert("Nonce", value);
139 }
140 if let Ok(value) = HeaderValue::from_str(&nowtime) {
141 headers.insert("CurTime", value);
142 }
143 if let Ok(value) = HeaderValue::from_str(&check_sum) {
144 headers.insert("CheckSum", value);
145 }
146 if let Ok(value) = HeaderValue::from_str("application/x-www-form-urlencoded;charset=utf-8")
147 {
148 headers.insert("Content-Type", value);
149 }
150
151 let form_data = vec![("sendid", sendid)];
152
153 let request = client
154 .request(
155 Method::POST,
156 "https://api.netease.im/sms/querystatus.action",
157 )
158 .headers(headers)
159 .form(&form_data);
160 let result = request.send().await.map_err(|e| e.to_string())?;
161 let (status, res) = response_check(result, true).await?;
162 if status != StatusCode::OK {
163 warn!("sms response fail: {}", &res);
164 return Err(format!("http bad:{}", res));
165 }
166 let code = gjson::get(&res, "code").to_string();
167 if code.as_str() == "200" {
168 let items = gjson::get(&res, "obj");
169 let mut out = Vec::with_capacity(items.array().len());
170 for tmp in items.array() {
171 out.push(SendDetailItem {
172 send_id: sendid.to_owned(),
173 status: if tmp.get("status").i8() == 2 || tmp.get("status").i8() == 3 {
174 SendNotifyStatus::Failed
175 } else if tmp.get("status").i8() == 1 {
176 SendNotifyStatus::Completed
177 } else {
178 SendNotifyStatus::Progress
179 },
180 message: match tmp.get("status").i8() {
181 1 => "OK".to_owned(),
182 3 => "spam".to_owned(),
183 _ => "send fail".to_owned(),
184 },
185 send_time: Some(tmp.get("status").u64() / 1000),
186 receive_time: None,
187 code: code.to_string(),
188 mobile: Some(tmp.get("mobile").to_string()),
189 });
190 }
191 return Ok(out);
192 }
193 Err(response_msg(&res, &["obj"]))
194 }
195 pub fn branch_limit() -> u16 {
196 100
197 }
198 pub async fn branch_send(
200 client: Client,
201 app_key: &str,
202 app_secret: &str,
203 template_id: &str,
204 template_arr: Option<Vec<String>>,
205 phone_numbers: &[&str],
206 ) -> BranchSendResult {
207 let phone_numbers = phone_numbers_check(phone_numbers)?;
208
209 let (randstring, nowtime, check_sum) = Self::signature(app_secret);
210
211 let mut headers = HeaderMap::new();
212 if let Ok(value) = HeaderValue::from_str(app_key) {
213 headers.insert("AppKey", value);
214 }
215 if let Ok(value) = HeaderValue::from_str(&randstring) {
216 headers.insert("Nonce", value);
217 }
218 if let Ok(value) = HeaderValue::from_str(&nowtime) {
219 headers.insert("CurTime", value);
220 }
221 if let Ok(value) = HeaderValue::from_str(&check_sum) {
222 headers.insert("CheckSum", value);
223 }
224 if let Ok(value) = HeaderValue::from_str("application/x-www-form-urlencoded;charset=utf-8")
225 {
226 headers.insert("Content-Type", value);
227 }
228
229 let mut form_data = vec![];
230 form_data.push(("templateid", template_id));
231 let mobile = json!(phone_numbers).to_string();
232 form_data.push(("mobiles", &mobile));
233 let ptmp = if let Some(params) = template_arr {
234 json!(params).to_string()
235 } else {
236 "".to_string()
237 };
238 if !ptmp.is_empty() {
239 form_data.push(("params", ptmp.as_str()));
240 }
241
242 let request = client
243 .request(
244 Method::POST,
245 "https://api.netease.im/sms/sendtemplate.action",
246 )
247 .headers(headers)
248 .form(&form_data);
249 let result = request
250 .send()
251 .await
252 .map_err(|e| SendError::Next(format!("request send fail:{}", e)))?;
253 let (status, res) = response_check(result, true)
254 .await
255 .map_err(SendError::Next)?;
256 if status != StatusCode::OK {
257 warn!("sms response fail: {}", &res);
258 return Err(SendError::Next(format!("http bad:{}", res)));
259 }
260
261 let code = gjson::get(&res, "code").to_string();
262 if code.as_str() == "200" {
263 return Ok(phone_numbers
264 .iter()
265 .map(|e| SendResultItem {
266 mobile: e.to_string(),
267 status: SendStatus::Progress,
268 message: gjson::get(&res, "obj").to_string(),
269 send_id: gjson::get(&res, "msg").to_string(),
270 })
271 .collect());
272 }
273 if code.as_str() == "412" || code.as_str() == "601" {
274 return Err(SendError::Finish(response_msg(&res, &["obj"])));
275 }
276 Err(SendError::Next(response_msg(&res, &["obj"])))
277 }
278}