mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
"""
|
||
Система валидации данных для телеграм-бота.
|
||
Обеспечивает безопасность и корректность входящих данных.
|
||
"""
|
||
|
||
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 |