use reqwest::Client;
pub struct IppanelClient {
pub api_key: String,
pub base_url: String,
client: Client,
}
impl IppanelClient {
pub fn new(api_key: Option<String>, base_url: Option<String>) -> Self {
Self {
api_key: api_key.unwrap_or_else(|| "".to_string()),
base_url: base_url.unwrap_or_else(|| "https://api2.ippanel.com/api/v1".to_string()),
client: Client::new(),
}
}
pub fn set_base_url(&mut self, base_url: String) {
self.base_url = base_url;
}
pub fn set_api_key(&mut self, api_key: String) {
self.api_key = api_key;
}
pub async fn get_credit(&self) -> Result<f64, crate::error::IppanelError> {
let url = format!("{}/sms/accounting/credit/show", self.base_url);
let res = self
.client
.get(&url)
.header("apikey", &self.api_key)
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
if let Some(credit) = json.get("data").and_then(|d| d.get("credit")).and_then(|c| c.as_f64()) {
Ok(credit)
} else {
Err(crate::error::IppanelError::new("Invalid response: missing credit field", crate::error::ErrorCode::Unknown.code()))
}
}
pub async fn send(&self, originator: &str, recipients: &[&str], message: &str, summary: &str) -> Result<i64, crate::error::IppanelError> {
let url = format!("{}/sms/send/webservice/single", self.base_url);
let recipients_vec: Vec<String> = recipients.iter().map(|s| s.to_string()).collect();
let body = serde_json::json!({
"sender": originator,
"recipient": recipients_vec,
"message": message,
"description": {
"summary": summary,
"count_recipient": recipients.len().to_string()
}
});
let res = self
.client
.post(&url)
.header("apikey", &self.api_key)
.json(&body)
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
if let Some(message_id) = json.get("data").and_then(|d| d.get("message_id")).and_then(|id| id.as_i64()) {
Ok(message_id)
} else {
Err(crate::error::IppanelError::new("Invalid response: missing message_id field", crate::error::ErrorCode::Unknown.code()))
}
}
pub async fn get_message(&self, message_id: i64) -> Result<crate::models::message::Message, crate::error::IppanelError> {
let url = format!("{}/sms/message/all", self.base_url);
let res = self
.client
.get(&url)
.header("apikey", &self.api_key)
.query(&[("message_id", message_id)])
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
if let Some(data) = json.get("data").and_then(|d| d.as_array()).and_then(|arr| arr.get(0)) {
let msg: crate::models::message::Message = serde_json::from_value(data.clone())
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
Ok(msg)
} else {
Err(crate::error::IppanelError::new("Invalid response: missing message data", crate::error::ErrorCode::Unknown.code()))
}
}
pub async fn fetch_statuses(&self, message_id: i64, page: u32, limit: u32) -> Result<(Vec<crate::models::recipient::Recipient>, crate::models::pagination_info::PaginationInfo), crate::error::IppanelError> {
let url = format!("{}/sms/message/show-recipient/message-id/{}", self.base_url, message_id);
let res = self
.client
.get(&url)
.header("apikey", &self.api_key)
.query(&[("page", page), ("per_page", limit)])
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
let mut recipients = Vec::new();
if let Some(deliveries) = json.get("data").and_then(|d| d.get("deliveries")).and_then(|d| d.as_array()) {
for r in deliveries {
let rec: crate::models::recipient::Recipient = serde_json::from_value(r.clone())
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
recipients.push(rec);
}
}
let mut pagination = crate::models::pagination_info::PaginationInfo {
total: 0,
limit: 0,
page: 0,
pages: 0,
prev: None,
next: None,
};
if let Some(meta) = json.get("meta") {
if let Ok(p) = serde_json::from_value(meta.clone()) {
pagination = p;
}
}
Ok((recipients, pagination))
}
pub async fn fetch_inbox(&self, page: u32, limit: u32) -> Result<(Vec<crate::models::inbox_message::InboxMessage>, crate::models::pagination_info::PaginationInfo), crate::error::IppanelError> {
let url = format!("{}/inbox", self.base_url);
let res = self
.client
.get(&url)
.header("apikey", &self.api_key)
.query(&[("page", page), ("per_page", limit)])
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
let mut messages = Vec::new();
if let Some(data) = json.get("data").and_then(|d| d.as_array()) {
for r in data {
let msg: crate::models::inbox_message::InboxMessage = serde_json::from_value(r.clone())
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
messages.push(msg);
}
}
let mut pagination = crate::models::pagination_info::PaginationInfo {
total: 0,
limit: 0,
page: 0,
pages: 0,
prev: None,
next: None,
};
if let Some(meta) = json.get("meta") {
if let Ok(p) = serde_json::from_value(meta.clone()) {
pagination = p;
}
}
Ok((messages, pagination))
}
pub async fn create_pattern(&self, pattern: &str, description: &str, variables: &[(String, String)], delimiter: &str, is_shared: bool) -> Result<String, crate::error::IppanelError> {
let url = format!("{}/sms/pattern/normal/store", self.base_url);
let vars: Vec<serde_json::Value> = variables.iter().map(|(name, typ)| serde_json::json!({"name": name, "type": typ})).collect();
let body = serde_json::json!({
"message": pattern,
"delimiter": delimiter,
"description": description,
"variable": vars,
"is_share": is_shared,
});
let res = self
.client
.post(&url)
.header("apikey", &self.api_key)
.json(&body)
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
if let Some(code) = json.get("data").and_then(|d| d.as_array()).and_then(|arr| arr.get(0)).and_then(|v| v.get("code")).and_then(|c| c.as_str()) {
Ok(code.to_string())
} else {
Err(crate::error::IppanelError::new("Invalid response: missing pattern code", crate::error::ErrorCode::Unknown.code()))
}
}
pub async fn send_pattern(&self, pattern_code: &str, originator: &str, recipient: &str, variables: &[(String, String)]) -> Result<i64, crate::error::IppanelError> {
let url = format!("{}/sms/pattern/normal/send", self.base_url);
let vars: std::collections::HashMap<String, String> = variables.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let body = serde_json::json!({
"code": pattern_code,
"sender": originator,
"recipient": recipient,
"variable": vars,
});
let res = self
.client
.post(&url)
.header("apikey", &self.api_key)
.json(&body)
.send()
.await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
let status = res.status();
let json: serde_json::Value = res.json().await
.map_err(|e| crate::error::IppanelError::new(e.to_string(), crate::error::ErrorCode::Unknown.code()))?;
if !status.is_success() {
let code = json.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
let msg = json.get("data").and_then(|d| d.get("error")).and_then(|e| e.as_str()).unwrap_or("API error");
return Err(crate::error::IppanelError::new(msg, code));
}
if let Some(message_id) = json.get("data").and_then(|d| d.get("message_id")).and_then(|id| id.as_i64()) {
Ok(message_id)
} else {
Err(crate::error::IppanelError::new("Invalid response: missing message_id field", crate::error::ErrorCode::Unknown.code()))
}
}
}