""" Утилиты для работы с информацией о пользователях """ from typing import Optional, Union from enum import Enum from aiogram.types import Message, CallbackQuery, User, InlineQuery, ChatMemberUpdated __all__ = ( 'get_user_display_name', 'get_user_mention', 'get_user_id', 'username', 'format_user', 'UserFormat', 'is_bot', 'has_username', 'is_premium', 'get_language_code', 'compare_users', 'get_user_info_dict' ) class UserFormat(str, Enum): """Форматы отображения пользователя""" USERNAME = 'username' # @username или @id123 FULL_NAME = 'full_name' # Имя Фамилия MENTION = 'mention' # HTML mention MENTION_MARKDOWN = 'markdown' # Markdown mention FIRST_NAME = 'first_name' # Только имя ID_ONLY = 'id' # Только ID DETAILED = 'detailed' # @username (Имя Фамилия, ID: 123) # Тип для всех событий с пользователем EventType = Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated] def _extract_user(event: EventType) -> Optional[User]: """ Извлекает объект User из события. Args: event: Объект события Returns: User или None """ if isinstance(event, (Message, CallbackQuery, InlineQuery)): return event.from_user elif isinstance(event, ChatMemberUpdated): return event.from_user or event.new_chat_member.user return None def get_user_display_name( event: EventType, default: str = "Unknown User" ) -> str: """ Возвращает отображаемое имя пользователя (Full Name). Args: event: Объект события (Message, CallbackQuery, и т.д.) default: Значение по умолчанию если пользователь не найден Returns: str: Полное имя пользователя Example: >> get_user_display_name(message) 'John Doe' >> get_user_display_name(message) 'John' # Если нет фамилии """ user = _extract_user(event) if not user: return default # Полное имя (приоритет) if user.full_name: return user.full_name # Только имя if user.first_name: return user.first_name # Username как запасной вариант if user.username: return f"@{user.username}" # ID как последний вариант return f"User {user.id}" def get_user_mention( event: EventType, parse_mode: str = 'HTML', show_username: bool = False ) -> str: """ Возвращает упоминание пользователя (кликабельное). Args: event: Объект события parse_mode: Режим парсинга ('HTML' или 'Markdown') show_username: Показывать username вместо имени Returns: str: HTML/Markdown упоминание Example: >> get_user_mention(message) 'John Doe' >> get_user_mention(message, parse_mode='Markdown') '[John Doe](tg://user?id=123456789)' >> get_user_mention(message, show_username=True) '@johndoe' """ user = _extract_user(event) if not user: return "Unknown User" # Определяем текст для отображения if show_username and user.username: display_text = f"@{user.username}" else: display_text = user.full_name or user.first_name or f"User {user.id}" # Формируем ссылку user_link = f"tg://user?id={user.id}" if parse_mode.upper() == 'HTML': return f'{display_text}' elif parse_mode.upper() in ('MARKDOWN', 'MARKDOWNV2'): # Экранируем специальные символы для Markdown display_text = display_text.replace('[', '\\[').replace(']', '\\]') return f'[{display_text}]({user_link})' else: return display_text def get_user_id(event: EventType) -> Optional[int]: """ Возвращает ID пользователя. Args: event: Объект события Returns: int или None: ID пользователя Example: >> get_user_id(message) 123456789 """ user = _extract_user(event) return user.id if user else None def username( event: EventType, with_at: bool = True, fallback_to_id: bool = True ) -> str: """ Возвращает username пользователя или ID если username отсутствует. Это основная функция для получения идентификатора пользователя в формате @username или @id123. Args: event: Объект события (Message, CallbackQuery, и т.д.) with_at: Добавлять @ в начало fallback_to_id: Использовать ID если нет username Returns: str: Username или ID пользователя Raises: ValueError: Если информация о пользователе отсутствует Example: >> username(message) '@johndoe' >> username(message) # Нет username '@123456789' >> username(message, with_at=False) 'johndoe' >> username(message, fallback_to_id=False) '' # Если нет username """ user = _extract_user(event) if not user: raise ValueError("Информация о пользователе отсутствует в событии") # Если есть username if user.username: return f"@{user.username}" if with_at else user.username # Fallback на ID if fallback_to_id: return f"@{user.id}" if with_at else str(user.id) # Если ничего нет return "" def format_user( event: EventType, format_type: UserFormat = UserFormat.USERNAME, default: str = "@System" ) -> str: """ Универсальная функция форматирования пользователя. Args: event: Объект события format_type: Тип форматирования (из enum UserFormat) default: Значение по умолчанию Returns: str: Отформатированная информация о пользователе Example: >> format_user(message, UserFormat.USERNAME) '@johndoe' >> format_user(message, UserFormat.FULL_NAME) 'John Doe' >> format_user(message, UserFormat.MENTION) 'John Doe' >> format_user(message, UserFormat.DETAILED) '@johndoe (John Doe, ID: 123456789)' """ user = _extract_user(event) if not user: return default # USERNAME: @username или @id if format_type == UserFormat.USERNAME: if user.username: return f"@{user.username}" return f"@{user.id}" # FULL_NAME: Имя Фамилия elif format_type == UserFormat.FULL_NAME: return user.full_name or user.first_name or f"User {user.id}" # MENTION: HTML упоминание elif format_type == UserFormat.MENTION: display = user.full_name or user.first_name or f"User {user.id}" return f'{display}' # MENTION_MARKDOWN: Markdown упоминание elif format_type == UserFormat.MENTION_MARKDOWN: display = user.full_name or user.first_name or f"User {user.id}" display = display.replace('[', '\\[').replace(']', '\\]') return f'[{display}](tg://user?id={user.id})' # FIRST_NAME: Только имя elif format_type == UserFormat.FIRST_NAME: return user.first_name or f"User {user.id}" # ID_ONLY: Только ID elif format_type == UserFormat.ID_ONLY: return str(user.id) # DETAILED: Подробная информация elif format_type == UserFormat.DETAILED: parts = [] # Username if user.username: parts.append(f"@{user.username}") # Full name if user.full_name: parts.append(f"({user.full_name}") elif user.first_name: parts.append(f"({user.first_name}") # ID parts.append(f"ID: {user.id})") return ' '.join(parts) if parts else f"User {user.id}" # По умолчанию return default # ================= ДОПОЛНИТЕЛЬНЫЕ УТИЛИТЫ ================= def is_bot(event: EventType) -> bool: """ Проверяет, является ли пользователь ботом. Args: event: Объект события Returns: bool: True если бот """ user = _extract_user(event) return user.is_bot if user else False def has_username(event: EventType) -> bool: """ Проверяет, есть ли у пользователя username. Args: event: Объект события Returns: bool: True если есть username """ user = _extract_user(event) return bool(user and user.username) def is_premium(event: EventType) -> bool: """ Проверяет, есть ли у пользователя Telegram Premium. Args: event: Объект события Returns: bool: True если Premium """ user = _extract_user(event) return user.is_premium if user else False def get_language_code(event: EventType) -> Optional[str]: """ Возвращает код языка пользователя. Args: event: Объект события Returns: Optional[str]: Код языка ('ru', 'en', и т.д.) """ user = _extract_user(event) return user.language_code if user else None def compare_users(event1: EventType, event2: EventType) -> bool: """ Сравнивает двух пользователей по ID. Args: event1: Первое событие event2: Второе событие Returns: bool: True если это один и тот же пользователь """ user1 = _extract_user(event1) user2 = _extract_user(event2) if not user1 or not user2: return False return user1.id == user2.id def get_user_info_dict(event: EventType) -> dict: """ Возвращает всю информацию о пользователе в виде словаря. Args: event: Объект события Returns: dict: Словарь с информацией о пользователе """ user = _extract_user(event) if not user: return {} return { 'id': user.id, 'username': user.username, 'first_name': user.first_name, 'last_name': user.last_name, 'full_name': user.full_name, 'is_bot': user.is_bot, 'is_premium': user.is_premium, 'language_code': user.language_code, 'mention': get_user_mention(event), 'display_name': get_user_display_name(event) }