use reqwest::Client;
use serde::de::DeserializeOwned;
use serde_json::json;
use std::time::Duration;
use crate::errors::{NiazpardazError, Result};
use crate::models::*;
const DEFAULT_BASE_URL: &str = "https://login.niazpardaz.ir/api/v2/RestWebApi";
const DEFAULT_TIMEOUT_SECS: u64 = 30;
#[derive(Debug, Clone)]
pub struct NiazpardazClient {
api_key: String,
base_url: String,
http_client: Client,
}
pub struct ClientBuilder {
api_key: String,
base_url: String,
timeout: Duration,
http_client: Option<Client>,
}
impl ClientBuilder {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into().trim().to_string(),
base_url: DEFAULT_BASE_URL.to_string(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS),
http_client: None,
}
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn http_client(mut self, client: Client) -> Self {
self.http_client = Some(client);
self
}
pub fn build(self) -> NiazpardazClient {
let http_client = self.http_client.unwrap_or_else(|| {
Client::builder()
.timeout(self.timeout)
.build()
.expect("Failed to create HTTP client")
});
NiazpardazClient {
api_key: self.api_key,
base_url: self.base_url,
http_client,
}
}
}
impl NiazpardazClient {
pub fn new(api_key: impl Into<String>) -> Self {
ClientBuilder::new(api_key).build()
}
pub fn builder(api_key: impl Into<String>) -> ClientBuilder {
ClientBuilder::new(api_key)
}
pub async fn send(&self, from: &str, to: &str, message: &str) -> Result<SendBatchSmsResult> {
self.send_bulk(from, &[to], message).await
}
pub async fn send_bulk(
&self,
from: &str,
to_numbers: &[&str],
message: &str,
) -> Result<SendBatchSmsResult> {
self.send_bulk_full(from, to_numbers, message, false, None)
.await
}
pub async 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).await
}
pub async fn send_sms_like_to_like(
&self,
from: &str,
to_numbers: &[&str],
messages: &[&str],
) -> Result<SendLikeToLikeResult> {
self.send_sms_like_to_like_full(from, to_numbers, messages, false)
.await
}
pub async fn send_sms_like_to_like_full(
&self,
from: &str,
to_numbers: &[&str],
messages: &[&str],
is_flash: bool,
) -> Result<SendLikeToLikeResult> {
let payload = json!({
"fromNumber": from,
"messageContents": messages.join(","),
"toNumbers": to_numbers.join(","),
"isFlash": is_flash,
});
self.post("/SendSmsLikeToLike", &payload).await
}
pub async 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).await
}
pub async 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).await
}
pub async fn get_delivery_like_to_like(&self, sms_id: i64) -> Result<BatchDeliveryResult> {
let payload = json!({ "smsId": sms_id });
self.post("/GetDeliveryLikeToLike", &payload).await
}
pub async fn get_credit(&self) -> Result<CreditResult> {
self.post("/GetCredit", &json!({})).await
}
pub async fn get_sender_numbers(&self) -> Result<SenderNumbersResult> {
self.post("/GetSenderNumbers", &json!({})).await
}
pub async fn get_inbox_count(&self, is_read: bool) -> Result<InboxCountResult> {
let payload = json!({ "isRead": is_read });
self.post("/GetInboxCount", &payload).await
}
pub async 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).await
}
pub async 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).await
}
pub async fn extract_telecom_blacklist_numbers(
&self,
numbers: &[&str],
) -> Result<BlacklistNumbersResult> {
let payload = json!({ "numbers": numbers.join(",") });
self.post("/ExtractTelecomBlacklistNumbers", &payload).await
}
pub async fn number_is_in_telecom_blacklist(&self, number: &str) -> Result<IsBlacklistResult> {
let payload = json!({ "number": number });
self.post("/NumberIsInTelecomBlacklist", &payload).await
}
pub async fn check_sms_content(&self, message: &str) -> Result<CheckContentResult> {
let payload = json!({ "message": message });
self.post("/CheckSmsContent", &payload).await
}
async 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()
.await?;
let status = response.status();
let body = response.text().await?;
if !status.is_success() {
return Err(NiazpardazError::General(format!(
"خطا در ارتباط با سرور: HTTP {}",
status.as_u16()
)));
}
let api_response: ApiResponse = serde_json::from_str(&body)?;
if !api_response.success {
let error_code = api_response
.result
.get("resultCode")
.and_then(|v| v.as_i64())
.unwrap_or(0) as i32;
let message = api_response
.error_message
.unwrap_or_else(|| "خطای نامشخص".to_string());
return Err(NiazpardazError::Api {
code: error_code,
message,
});
}
let result: T = serde_json::from_value(api_response.result)?;
Ok(result)
}
}