""" نرمال‌سازیِ شماره‌ی موبایلِ ایران به شکلِ یکتای ‎09XXXXXXXXX‎. چرا: پیش از این `phone` رشته‌ی خام بود و ‎0912…‎، ‎+98912…‎، ‎98912…‎ و همان شماره با ارقامِ فارسی چهار «شماره»ی جدا بودند — هر کدام cooldown و سقفِ پیامکِ خودش را داشت و حسابِ جدا می‌ساخت (security-before-production.md، بندِ ۱۷). هرچه شکلِ ‎09\\d{9}‎ نگیرد رد می‌شود، پس شماره‌ی خارجی یا خطِ ثابت هم به کاوه‌نگار نمی‌رسد (پیامکِ بین‌المللی گران است و هدفِ SMS pumping). عمداً بی‌وابستگی به DRF است تا config/settings.py هم بتواند واردش کند (کلیدهای TEST_LOGIN_PHONES)؛ فیلدِ serializer در accounts/serializers.py است. """ import re import unicodedata _SEPARATORS = re.compile(r'[\s\-().\u200c\u200e\u200f]') # [0-9] نه \d: در پایتون \d هر رقمِ یونیکد (تمام‌عرض، ریاضی، دوناگری…) را هم می‌گیرد. _MOBILE = re.compile(r'09[0-9]{9}') def ascii_digits(value: str) -> str: """ هر رقمِ دهدهیِ یونیکد (فارسی، عربی، تمام‌عرض «0»، ریاضی «𝟎»، …) → رقمِ لاتین. بدونِ این «09123456789» شماره‌ی جدایی می‌شد با سقفِ پیامک و حسابِ جدا. «²» و «①» رقمِ دهدهی نیستند و دست نمی‌خورند، پس شماره‌ای که آن‌ها را دارد رد می‌شود. """ return ''.join(str(unicodedata.decimal(ch)) if not ch.isascii() and ch.isdecimal() else ch for ch in value) def normalize_phone(value) -> str | None: """شکلِ یکتای ‎09XXXXXXXXX‎، یا None اگر موبایلِ ایران نباشد.""" s = _SEPARATORS.sub('', ascii_digits(str(value or ''))) if s.startswith('+98'): s = '0' + s[3:] elif s.startswith('0098'): s = '0' + s[4:] elif s.startswith('98') and len(s) == 12: s = '0' + s[2:] elif s.startswith('9') and len(s) == 10: s = '0' + s return s if _MOBILE.fullmatch(s) else None