Подготовка к релизу

This commit is contained in:
2025-10-30 18:28:26 +03:00
parent 69c0162c6b
commit 4981db90a0
191 changed files with 45557 additions and 3651 deletions
+177
View File
@@ -0,0 +1,177 @@
"""
Система валидации данных для телеграм-бота.
Обеспечивает безопасность и корректность входящих данных.
"""
import re
from typing import Optional, Union, List
class ValidationError(Exception):
"""Исключение валидации данных"""
def __init__(self, message: str, field: str = None):
self.message = message
self.field = field
super().__init__(message)
class Validator:
"""Базовый класс для валидаторов"""
@staticmethod
def validate_string_length(text: str, min_length: int = 0, max_length: int = 1000) -> bool:
"""Валидация длины строки"""
if not isinstance(text, str):
return False
return min_length <= len(text) <= max_length
@staticmethod
def validate_numeric_range(value: Union[int, float], min_val: Union[int, float], max_val: Union[int, float]) -> bool:
"""Валидация числового диапазона"""
try:
numeric_value = float(value)
return min_val <= numeric_value <= max_val
except (ValueError, TypeError):
return False
@staticmethod
def validate_user_id(user_id: Union[int, str]) -> bool:
"""Валидация ID пользователя Telegram"""
try:
uid = int(user_id)
return 1 <= uid <= 2147483647 # Диапазон Telegram user ID
except (ValueError, TypeError):
return False
@staticmethod
def validate_chat_id(chat_id: Union[int, str]) -> bool:
"""Валидация ID чата Telegram"""
try:
cid = int(chat_id)
return -2147483648 <= cid <= 2147483647 # Диапазон Telegram chat ID
except (ValueError, TypeError):
return False
class InputValidator:
"""Валидатор пользовательского ввода"""
# Регулярные выражения для валидации
EMAIL_PATTERN = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$|^[a-zA-Z0-9._%+-]+@localhost$')
PHONE_PATTERN = re.compile(r'^\+?[\d\s\-\(\)]{10,}$')
URL_PATTERN = re.compile(r'^https?://[^\s/$.?#].[^\s]*$')
@classmethod
def validate_email(cls, email: str) -> bool:
"""Валидация email адреса"""
return bool(cls.EMAIL_PATTERN.match(email)) if email else False
@classmethod
def validate_phone(cls, phone: str) -> bool:
"""Валидация номера телефона"""
return bool(cls.PHONE_PATTERN.match(phone)) if phone else False
@classmethod
def validate_url(cls, url: str) -> bool:
"""Валидация URL"""
return bool(cls.URL_PATTERN.match(url)) if url else False
@classmethod
def validate_city_name(cls, city: str, max_length: int = 50) -> bool:
"""Валидация названия города"""
if not city or not isinstance(city, str):
return False
# Проверка длины
if len(city) < 2 or len(city) > max_length:
return False
# Проверка на наличие только допустимых символов (буквы, пробелы, дефисы)
return bool(re.match(r'^[a-zA-Zа-яА-Я\s\-]+$', city))
@classmethod
def validate_username(cls, username: str) -> bool:
"""Валидация username Telegram"""
if not username or not isinstance(username, str):
return False
# Username должен начинаться с буквы, содержать только буквы, цифры и подчеркивания
# Длина от 5 до 32 символов
if not (5 <= len(username) <= 32):
return False
return bool(re.match(r'^[a-zA-Z][a-zA-Z0-9_]*$', username))
@classmethod
def validate_text_content(cls, text: str, max_length: int = 4000) -> bool:
"""Валидация текстового контента"""
if not text or not isinstance(text, str):
return False
# Проверка длины
if len(text) > max_length:
return False
# Проверка на наличие потенциально опасных символов
dangerous_chars = ['<', '>', '"', "'", '&']
return not any(char in text for char in dangerous_chars)
@classmethod
def validate_donation_amount(cls, amount: Union[str, float]) -> bool:
"""Валидация суммы доната"""
try:
amount_float = float(amount)
return 1.0 <= amount_float <= 100000.0 # От 1 рубля до 100k рублей
except (ValueError, TypeError):
return False
@classmethod
def validate_time_format(cls, time_str: str) -> bool:
"""Валидация формата времени"""
if not time_str or not isinstance(time_str, str):
return False
# Проверяем форматы: YYYY-MM-DD HH:MM:SS, YYYY-MM-DD HH:MM, +30m, +2h, +1d
patterns = [
r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(?::\d{2})?$', # Абсолютное время
r'^\+(\d+)[mhd]$' # Относительное время
]
return any(re.match(pattern, time_str) for pattern in patterns)
@classmethod
def sanitize_filename(cls, filename: str) -> str:
"""Очистка имени файла от опасных символов"""
if not filename:
return "unnamed_file"
# Оставляем только безопасные символы
safe_chars = re.sub(r'[^\w\.\-\s]', '', filename)
return safe_chars.strip() or "unnamed_file"
@classmethod
def validate_csv_filename(cls, filename: str) -> bool:
"""Валидация имени CSV файла"""
if not filename or not isinstance(filename, str):
return False
# Проверка длины
if len(filename) > 255:
return False
# Проверка на наличие только безопасных символов
return bool(re.match(r'^[a-zA-Z0-9._\-/\s]+$', filename))
@classmethod
def validate_error_type(cls, error_type: str) -> bool:
"""Валидация типа ошибки"""
if not error_type or not isinstance(error_type, str):
return False
valid_types = ['bug', 'feature', 'crash', 'ui', 'security', 'improvement', 'other']
return error_type.lower() in valid_types
@classmethod
def validate_priority(cls, priority: str) -> bool:
"""Валидация приоритета"""
valid_priorities = ['low', 'medium', 'high', 'critical']
return priority.lower() in valid_priorities