Управление информацией пользователей

This commit is contained in:
2026-02-23 14:36:36 +07:00
parent 91bae2612f
commit dfd537b96a

409
bot/utils/usernames.py Normal file
View File

@@ -0,0 +1,409 @@
"""
Утилиты для работы с информацией о пользователях
"""
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)
'<a href="tg://user?id=123456789">John Doe</a>'
>> get_user_mention(message, parse_mode='Markdown')
'[John Doe](tg://user?id=123456789)'
>> get_user_mention(message, show_username=True)
'<a href="tg://user?id=123456789">@johndoe</a>'
"""
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'<a href="{user_link}">{display_text}</a>'
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)
'<a href="tg://user?id=123">John Doe</a>'
>> 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'<a href="tg://user?id={user.id}">{display}</a>'
# 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)
}