93 lines
2.8 KiB
Python
93 lines
2.8 KiB
Python
"""
|
|
Модель пользователя
|
|
"""
|
|
import asyncio
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from config.constants import EMPTY_VALUES
|
|
|
|
|
|
def escape_html(text: str) -> str:
|
|
"""Экранирование HTML символов"""
|
|
if not text:
|
|
return ""
|
|
return (text
|
|
.replace('&', '&')
|
|
.replace('<', '<')
|
|
.replace('>', '>')
|
|
.replace('"', '"')
|
|
.replace("'", '''))
|
|
|
|
|
|
@dataclass
|
|
class User:
|
|
"""Модель пользователя бота"""
|
|
|
|
id: Optional[int] = None
|
|
telegram_id: int = None
|
|
username: Optional[str] = None
|
|
first_name: str = ""
|
|
last_name: Optional[str] = None
|
|
chat_id: int = None
|
|
profile_link: str = ""
|
|
is_active: bool = True
|
|
is_superuser: bool = False
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
banned_until: Optional[datetime] = None
|
|
ban_reason: Optional[str] = None
|
|
|
|
@property
|
|
def full_name(self) -> str:
|
|
"""Полное имя пользователя"""
|
|
parts = []
|
|
if self.first_name:
|
|
parts.append(escape_html(self.first_name))
|
|
if self.last_name:
|
|
parts.append(escape_html(self.last_name))
|
|
return ' '.join(parts) if parts else 'Неизвестно'
|
|
|
|
@property
|
|
def display_name(self) -> str:
|
|
"""Отображаемое имя пользователя"""
|
|
if self.username:
|
|
return f"@{escape_html(self.username)}"
|
|
return escape_html(self.full_name)
|
|
|
|
@property
|
|
def is_banned(self) -> bool:
|
|
"""Проверка, забанен ли пользователь"""
|
|
if not self.banned_until:
|
|
return False
|
|
return datetime.now() < self.banned_until
|
|
|
|
|
|
@classmethod
|
|
def _parse_datetime(cls, date_str) -> Optional[datetime]:
|
|
"""Безопасный парсинг datetime из строки"""
|
|
if not date_str or date_str in EMPTY_VALUES:
|
|
return None
|
|
try:
|
|
return datetime.fromisoformat(date_str)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
@classmethod
|
|
async def _parse_datetime_async(cls, date_str) -> Optional[datetime]:
|
|
"""Асинхронный безопасный парсинг datetime из строки"""
|
|
if not date_str or date_str in EMPTY_VALUES:
|
|
return None
|
|
try:
|
|
loop = asyncio.get_event_loop()
|
|
return await loop.run_in_executor(None, datetime.fromisoformat, date_str)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def update_timestamp(self):
|
|
"""Обновление времени последнего обновления"""
|
|
self.updated_at = datetime.now()
|
|
|