Управление информацией пользователей
This commit is contained in:
409
bot/utils/usernames.py
Normal file
409
bot/utils/usernames.py
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user