Рефакторинг (этап 1): валидаторы и клавиатуры вынесены в модули
bot.py разгружен: чистые функции вынесены без изменения поведения: - validators.py — регэкспы, normalize_phone, format_phone_display, is_valid_name - keyboards.py — все get_*_keyboard Поведение проверено регрессионным прогоном (полный флоу заявки, админка, меню). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
"""Валидация и форматирование пользовательского ввода."""
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# Регулярные выражения для валидации
|
||||
# Российский номер для нормализации 8XXX / 7XXX -> +7XXX
|
||||
RU_PHONE_REGEX = re.compile(r"^\+?[78]\d{10}$")
|
||||
# Международный формат (E.164): + и 10–15 цифр. Принимаем и иностранные номера.
|
||||
INTL_PHONE_REGEX = re.compile(r"^\+?\d{10,15}$")
|
||||
# Имя: буквы (кириллица/латиница), пробел, дефис, апостроф; длина 2–50
|
||||
NAME_REGEX = re.compile(r"^[A-Za-zА-Яа-яЁё][A-Za-zА-Яа-яЁё \-']{1,49}$")
|
||||
|
||||
|
||||
def normalize_phone(text: str) -> Optional[str]:
|
||||
"""Очищает и нормализует телефон. Возвращает нормализованный номер или None, если формат неверен."""
|
||||
phone_cleaned = re.sub(r"[\s\-\(\)]", "", text)
|
||||
|
||||
# Российские номера приводим к виду +7XXXXXXXXXX
|
||||
if phone_cleaned.startswith("8") and len(phone_cleaned) == 11:
|
||||
phone_cleaned = "+7" + phone_cleaned[1:]
|
||||
elif phone_cleaned.startswith("7") and len(phone_cleaned) == 11:
|
||||
phone_cleaned = "+" + phone_cleaned
|
||||
|
||||
# Принимаем как российский, так и любой международный номер в формате E.164
|
||||
if RU_PHONE_REGEX.match(phone_cleaned) or INTL_PHONE_REGEX.match(phone_cleaned):
|
||||
return phone_cleaned
|
||||
return None
|
||||
|
||||
|
||||
def format_phone_display(phone: str) -> str:
|
||||
"""Красивое отображение номера: +7 (999) 123-45-67 для РФ, иначе как есть."""
|
||||
if not phone:
|
||||
return phone
|
||||
digits = re.sub(r"\D", "", phone)
|
||||
if len(digits) == 11 and digits[0] in ("7", "8"):
|
||||
d = digits
|
||||
return f"+7 ({d[1:4]}) {d[4:7]}-{d[7:9]}-{d[9:11]}"
|
||||
return phone
|
||||
|
||||
|
||||
def is_valid_name(text: str) -> bool:
|
||||
"""Имя должно состоять только из букв, пробела, дефиса или апострофа (без цифр и спецсимволов)."""
|
||||
return bool(NAME_REGEX.match(text.strip()))
|
||||
Reference in New Issue
Block a user