use crate::errors::Result;
use crate::models::*;
use serde::de::DeserializeOwned;
use serde_json::json;
use std::time::Duration;
const DEFAULT_BASE_URL: &str = "https://login.niazpardaz.ir/api/v2/RestWebApi";
const DEFAULT_TIMEOUT_SECS: u64 = 30;
#[derive(Debug, Clone)]
pub struct BlockingClient {
api_key: String,
base_url: String,
http_client: reqwest::blocking::Client,
}
impl BlockingClient {
pub fn new(api_key: impl Into<String>) -> Self {
let http_client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()
.expect("Failed to create HTTP client");
Self {
api_key: api_key.into().trim().to_string(),
base_url: DEFAULT_BASE_URL.to_string(),
http_client,
}
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn with_http_client(mut self, client: reqwest::blocking::Client) -> Self {
self.http_client = client;
self
}
pub fn send(&self, from: &str, to: &str, message: &str) -> Result<SendBatchSmsResult> {
self.send_bulk(from, &[to], message)
}
pub fn send_bulk(
&self,
from: &str,
to_numbers: &[&str],
message: &str,
) -> Result<SendBatchSmsResult> {
self.send_bulk_full(from, to_numbers, message, false, None)
}
pub fn send_bulk_full(
&self,
from: &str,
to_numbers: &[&str],
message: &str,
is_flash: bool,
send_delay: Option<i32>,
) -> Result<SendBatchSmsResult> {
let mut payload = json!({
"fromNumber": from,
"messageContent": message,
"toNumbers": to_numbers.join(","),
"isFlash": is_flash,
});
if let Some(delay) = send_delay {
payload["sendDelay"] = json!(delay);
}
self.post("/SendBatchSms", &payload)
}
pub fn send_sms_like_to_like(
&self,
from: &str,
to_numbers: &[&str],
messages: &[&str],
) -> Result<SendLikeToLikeResult> {
let payload = json!({
"fromNumber": from,
"messageContents": messages.join(","),
"toNumbers": to_numbers.join(","),
"isFlash": false,
});
self.post("/SendSmsLikeToLike", &payload)
}
pub fn send_voice_otp(&self, from: &str, to: &str, otp: &str) -> Result<SendBatchSmsResult> {
let payload = json!({
"fromNumber": from, "messageContent": otp,
"toNumbers": to, "isFlash": false,
});
self.post("/SendVoiceOtp", &payload)
}
pub fn get_batch_delivery(
&self,
batch_sms_id: i64,
page_index: i32,
page_size: i32,
) -> Result<BatchDeliveryResult> {
let payload =
json!({ "batchSmsId": batch_sms_id, "index": page_index, "count": page_size });
self.post("/GetBatchDelivery", &payload)
}
pub fn get_delivery_like_to_like(&self, sms_id: i64) -> Result<BatchDeliveryResult> {
self.post("/GetDeliveryLikeToLike", &json!({ "smsId": sms_id }))
}
pub fn get_credit(&self) -> Result<CreditResult> {
self.post("/GetCredit", &json!({}))
}
pub fn get_sender_numbers(&self) -> Result<SenderNumbersResult> {
self.post("/GetSenderNumbers", &json!({}))
}
pub fn get_inbox_count(&self, is_read: bool) -> Result<InboxCountResult> {
self.post("/GetInboxCount", &json!({ "isRead": is_read }))
}
pub fn get_messages(
&self,
message_type: i32,
from_numbers: &str,
page_index: i32,
page_size: i32,
) -> Result<MessagesResult> {
let payload = json!({ "messageType": message_type, "fromNumbers": from_numbers, "index": page_index, "count": page_size });
self.post("/GetMessages", &payload)
}
pub fn get_messages_by_date_range(
&self,
message_type: i32,
from_numbers: &str,
from_date: &str,
to_date: &str,
) -> Result<MessagesResult> {
let payload = json!({ "messageType": message_type, "fromNumbers": from_numbers, "fromDate": from_date, "toDate": to_date });
self.post("/GetMessagesByDateRange", &payload)
}
pub fn extract_telecom_blacklist_numbers(
&self,
numbers: &[&str],
) -> Result<BlacklistNumbersResult> {
self.post(
"/ExtractTelecomBlacklistNumbers",
&json!({ "numbers": numbers.join(",") }),
)
}
pub fn number_is_in_telecom_blacklist(&self, number: &str) -> Result<IsBlacklistResult> {
self.post("/NumberIsInTelecomBlacklist", &json!({ "number": number }))
}
pub fn check_sms_content(&self, message: &str) -> Result<CheckContentResult> {
self.post("/CheckSmsContent", &json!({ "message": message }))
}
fn post<T: DeserializeOwned>(&self, endpoint: &str, payload: &serde_json::Value) -> Result<T> {
let url = format!("{}{}", self.base_url, endpoint);
let response = self
.http_client
.post(&url)
.header("X-API-Key", &self.api_key)
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.json(payload)
.send()?;
let status = response.status();
let body = response.text()?;
if !status.is_success() {
return Err(crate::NiazpardazError::General(format!(
"HTTP {}",
status.as_u16()
)));
}
let api_response: ApiResponse = serde_json::from_str(&body)?;
if !api_response.success {
let code = api_response
.result
.get("resultCode")
.and_then(|v| v.as_i64())
.unwrap_or(0) as i32;
let msg = api_response
.error_message
.unwrap_or_else(|| "خطای نامشخص".to_string());
return Err(crate::NiazpardazError::Api { code, message: msg });
}
Ok(serde_json::from_value(api_response.result)?)
}
}