from abc import ABC, abstractmethod import requests import logging from django.conf import settings logger = logging.getLogger(__name__) class SmsProviderABC(ABC): @abstractmethod def send_sms(self, mobile: str, message: str) -> bool | None: pass class DefaultSmsProvider(SmsProviderABC): def send_sms(self, mobile: str, message: str) -> bool | None: default_provider = DosefrNavadHashtSMS() return default_provider.send_sms(mobile, message) class DosefrNavadHashtSMS(SmsProviderABC): def send_sms(self, mobile: str, message: str) -> bool | None: payload = { 'USERNAME': 'vsms7937', 'PASSWORD': '335577775533', 'FROM': '30005367', 'TO': int(mobile), 'TEXT': f'{message}' } try: r = requests.get('http://www.0098sms.com/sendsmslink.aspx', params=payload, timeout=10) r.raise_for_status() except (requests.Timeout, requests.ConnectionError): # The provider may have dispatched the SMS before its reply was lost. logger.warning('SMS provider reply unavailable; dispatch is unconfirmed') return None except requests.HTTPError as error: if error.response is not None and error.response.status_code >= 500: logger.warning('SMS provider server error; dispatch is unconfirmed') return None return False except requests.RequestException: return False # Unknown bodies are NOT evidence of rejection. In particular, never # discard a delivered OTP just because the response contract differs. success_codes = getattr(settings, 'SMS_SUCCESS_CODES', ('0',)) failure_codes = getattr(settings, 'SMS_FAILURE_CODES', ('error',)) body = r.text.strip().lstrip('\ufeff').strip() def codes(values): if isinstance(values, str): values = values.split(',') return {str(value).strip() for value in values} if body in codes(success_codes): return True if body in codes(failure_codes): return False # Do not log the reply, URL, credentials, mobile, or OTP. logger.warning('SMS provider returned an unrecognized reply; dispatch is unconfirmed') return None