Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/app/services/providers/sunway_sms_provider.py
2026-06-13 22:34:36 +00:00

163 lines
5.2 KiB
Python

from __future__ import annotations
import httpx
import structlog
from typing import List, Optional
from app.services.providers.behin_sms_provider import BEHINSMS_ERROR_CODES
from app.utils.phone_utils import normalize_phone_number
logger = structlog.get_logger()
class SunwaySmsProvider:
"""
Provider برای ارسال پیامک از طریق سرویس سان‌وی اس‌ام‌اس (Sunway SMS)
مستندات API: سرویس HTTP-URL پنل sms.sunwaysms.com
"""
BASE_URL = "https://sms.sunwaysms.com/smsws/HttpService.ashx"
def __init__(self, username: str, password: str, sender: str):
if not username or not password or not sender:
raise ValueError("username, password و sender الزامی هستند")
self.username = username.strip()
self.password = password.strip()
sender = sender.strip()
if sender.startswith("98"):
sender = sender[2:]
elif sender.startswith("0098"):
sender = sender[4:]
self.sender = sender
def _make_request(self, service: str, params: dict) -> tuple[bool, str, Optional[str]]:
params["service"] = service
params["UserName"] = self.username
params["Password"] = self.password
try:
logger.info(
"sunwaysms_make_request",
service=service,
url=self.BASE_URL,
params_keys=list(params.keys()),
recipient_number=params.get("To"),
)
with httpx.Client(timeout=30.0) as client:
response = client.get(self.BASE_URL, params=params)
response.raise_for_status()
response_text = response.text.strip()
logger.info(
"sunwaysms_response",
service=service,
status_code=response.status_code,
response_text=response_text,
request_url=str(response.request.url),
)
return self._parse_response(response_text)
except httpx.HTTPError as e:
logger.error("sunwaysms_http_error", error=str(e), service=service, params=params)
return False, "", f"خطا در ارتباط با سرور سان‌وی اس‌ام‌اس: {str(e)}"
except Exception as e:
logger.error("sunwaysms_unexpected_error", error=str(e), service=service, params=params)
return False, "", f"خطای غیرمنتظره: {str(e)}"
def _parse_response(self, response_text: str) -> tuple[bool, str, Optional[str]]:
try:
if "," in response_text:
parts = response_text.split(",")
if all(part.strip().isdigit() for part in parts):
return True, response_text, None
result = int(response_text.strip())
if result == 50:
return True, str(result), None
if result >= 1000:
return True, str(result), None
if result in BEHINSMS_ERROR_CODES:
return False, str(result), BEHINSMS_ERROR_CODES[result]
return False, str(result), "وضعیت نامشخص از سرور"
except ValueError:
return False, response_text, "فرمت پاسخ نامعتبر از سرور"
def send_text(
self,
to_phone: str | List[str],
text: str,
is_flash: bool = False,
checking_message_id: Optional[str] = None,
) -> tuple[bool, Optional[str], Optional[str]]:
if not text or not text.strip():
return False, None, "متن پیامک خالی است"
if isinstance(to_phone, str):
phone_numbers = [to_phone]
else:
phone_numbers = list(to_phone)
if not phone_numbers:
return False, None, "هیچ شماره گیرنده‌ای مشخص نشده است"
if len(phone_numbers) > 1000:
return False, None, "حداکثر 1000 شماره می‌تواند ارسال شود"
try:
normalized_numbers = []
for phone in phone_numbers:
normalized_numbers.append(normalize_phone_number(phone))
normalized_numbers = list(set(normalized_numbers))
recipient_numbers_str = ",".join(normalized_numbers)
if not recipient_numbers_str.strip():
return False, None, "شماره گیرنده خالی است"
except ValueError as e:
logger.error("sunwaysms_normalize_error", error=str(e), phones=phone_numbers)
return False, None, str(e)
params = {
"To": recipient_numbers_str,
"Message": text,
"From": self.sender,
"Flash": "true" if is_flash else "false",
}
if checking_message_id:
params["chkMessageId"] = checking_message_id
success, result, error_msg = self._make_request("SendArray", params)
if success:
if "," in result:
message_ids = result.split(",")
return True, message_ids[0].strip(), None
return True, result, None
return False, None, error_msg or "خطا در ارسال پیامک"
def get_credit(self) -> tuple[bool, Optional[float], Optional[str]]:
success, result, error_msg = self._make_request("GetCredit", {})
if success:
try:
return True, float(result), None
except ValueError:
return False, None, "فرمت اعتبار نامعتبر است"
return False, None, error_msg or "خطا در دریافت اعتبار"
def get_message_status(self, message_id: str) -> tuple[bool, Optional[int], Optional[str]]:
if not message_id or not message_id.strip():
return False, None, "شناسه پیامک خالی است"
success, result, error_msg = self._make_request("GetMessageStatus", {"MessageID": message_id.strip()})
if success:
try:
return True, int(result), None
except ValueError:
return False, None, "فرمت وضعیت نامعتبر است"
return False, None, error_msg or "خطا در دریافت وضعیت پیامک"