Implement user-specific question numbering and update database schema. Added triggers for automatic question numbering and adjustments upon deletion. Enhanced CRUD operations to manage user_question_number effectively.
This commit is contained in:
7
handlers/__init__.py
Normal file
7
handlers/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Обработчики для бота анонимных вопросов
|
||||
"""
|
||||
|
||||
from . import start, questions, answers, admin, errors
|
||||
|
||||
__all__ = ['start', 'questions', 'answers', 'admin', 'errors']
|
||||
660
handlers/admin.py
Normal file
660
handlers/admin.py
Normal file
@@ -0,0 +1,660 @@
|
||||
"""
|
||||
Обработчики для администраторов
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from config import config
|
||||
from config.constants import DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, MIN_PAGE_NUMBER, PERCENTAGE_DECIMAL_PLACES, TIME_DECIMAL_PLACES
|
||||
from keyboards.inline import (
|
||||
get_admin_keyboard, get_ban_confirm_keyboard, get_ban_duration_keyboard,
|
||||
get_ban_user_keyboard, get_rate_limit_keyboard, get_stats_keyboard,
|
||||
get_superuser_assignment_keyboard, get_superuser_confirm_keyboard, get_unban_keyboard
|
||||
)
|
||||
from services.auth.auth_new import AuthService
|
||||
from services.infrastructure.database import DatabaseService
|
||||
from services.infrastructure.logger import get_logger
|
||||
from services.business.message_service import MessageService
|
||||
from services.business.pagination_service import PaginationService
|
||||
from services.permissions.decorators import require_admin_or_superuser, require_permission
|
||||
from services.rate_limiting.rate_limit_service import RateLimitService
|
||||
from services.business.user_service import UserService
|
||||
from services.utils import format_stats
|
||||
from dependencies import inject_admin_services
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def _format_users_list(
|
||||
users: list,
|
||||
page: int,
|
||||
per_page: int,
|
||||
pagination_service: PaginationService
|
||||
) -> str:
|
||||
"""Форматирование списка пользователей для отображения"""
|
||||
try:
|
||||
# Рассчитываем пагинацию
|
||||
page_users, total_users, current_page, total_pages = pagination_service.calculate_pagination(
|
||||
users, page, per_page
|
||||
)
|
||||
|
||||
# Формируем информацию о пагинации
|
||||
start_idx = current_page * per_page
|
||||
end_idx = min(start_idx + per_page, total_users)
|
||||
pagination_info = pagination_service.format_pagination_info(
|
||||
current_page, total_pages, start_idx, end_idx, total_users
|
||||
)
|
||||
|
||||
# Формируем текст сообщения
|
||||
users_text = f"🔍 <b>Управление суперпользователями</b>\n\n"
|
||||
users_text += pagination_info
|
||||
|
||||
# Добавляем информацию о пользователях
|
||||
for i, user in enumerate(page_users, start_idx + 1):
|
||||
status_emoji = "🔍" if user.is_superuser else "👤"
|
||||
users_text += f"{i}. {status_emoji} {user.display_name}\n"
|
||||
if user.username:
|
||||
users_text += f" @{user.username}\n"
|
||||
created_at_str = user.created_at.strftime('%d.%m.%Y') if user.created_at else 'Неизвестно'
|
||||
users_text += f" 📅 {created_at_str}\n\n"
|
||||
|
||||
users_text += "💡 Нажмите на пользователя для изменения его статуса."
|
||||
|
||||
return users_text
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при форматировании списка пользователей: {e}")
|
||||
return "❌ Ошибка при форматировании списка пользователей."
|
||||
|
||||
|
||||
# Функция is_admin теперь импортируется из services.auth
|
||||
|
||||
|
||||
@router.message(Command("stats"))
|
||||
@require_permission("view_stats", "❌ У вас нет прав для выполнения этой команды.")
|
||||
async def cmd_stats(message: Message, database: DatabaseService = None):
|
||||
"""Обработчик команды /stats"""
|
||||
await show_stats(message, database)
|
||||
|
||||
|
||||
@router.message(F.text == "👑 Админ панель")
|
||||
@require_permission("admin_panel", "❌ У вас нет прав для доступа к админ панели.")
|
||||
async def admin_panel_button(message: Message):
|
||||
"""Обработчик кнопки 'Админ панель'"""
|
||||
admin_text = "👑 <b>Админ панель</b>\n\n"
|
||||
admin_text += "Выберите действие:"
|
||||
|
||||
await message.answer(
|
||||
admin_text,
|
||||
reply_markup=get_admin_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "📊 Статистика")
|
||||
@require_permission("view_stats", "❌ У вас нет прав для просмотра статистики.")
|
||||
async def stats_button(message: Message, database: DatabaseService = None):
|
||||
"""Обработчик кнопки 'Статистика'"""
|
||||
await show_stats(message, database)
|
||||
|
||||
|
||||
async def show_stats(message: Message, database: DatabaseService = None):
|
||||
"""Показать статистику"""
|
||||
try:
|
||||
if not database:
|
||||
from dependencies import get_database
|
||||
database = get_database()
|
||||
|
||||
# Получаем статистику
|
||||
users_stats = await database.get_users_stats()
|
||||
questions_stats = await database.get_questions_stats()
|
||||
|
||||
# Объединяем статистику
|
||||
all_stats = {**users_stats, **questions_stats}
|
||||
|
||||
# Форматируем и отправляем
|
||||
stats_text = format_stats(all_stats)
|
||||
|
||||
await message.answer(
|
||||
stats_text,
|
||||
reply_markup=get_stats_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении статистики: {e}")
|
||||
await message.answer(
|
||||
"❌ Произошла ошибка при получении статистики. Попробуйте позже."
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@router.message(F.text == "📢 Рассылка")
|
||||
@require_permission("broadcast", "❌ У вас нет прав для рассылки.")
|
||||
async def broadcast_button(message: Message):
|
||||
"""Обработчик кнопки 'Рассылка'"""
|
||||
await message.answer(
|
||||
"📢 <b>Рассылка</b>\n\n"
|
||||
"Функция рассылки будет добавлена в следующих версиях.\n\n"
|
||||
"💡 Планируемые возможности:\n"
|
||||
"• Рассылка сообщений всем пользователям\n"
|
||||
"• Рассылка по группам пользователей\n"
|
||||
"• Планирование рассылок\n"
|
||||
"• Статистика доставки",
|
||||
reply_markup=get_admin_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "⚙️ Настройки")
|
||||
@require_permission("admin_panel", "❌ У вас нет прав для изменения настроек.")
|
||||
async def settings_button(message: Message):
|
||||
"""Обработчик кнопки 'Настройки'"""
|
||||
settings_text = "⚙️ <b>Настройки бота</b>\n\n"
|
||||
settings_text += f"🔧 <b>Текущие настройки:</b>\n"
|
||||
settings_text += f"• Режим отладки: {'Включен' if config.DEBUG else 'Выключен'}\n"
|
||||
settings_text += f"• Макс. длина вопроса: {config.MAX_QUESTION_LENGTH} символов\n"
|
||||
settings_text += f"• Макс. длина ответа: {config.MAX_ANSWER_LENGTH} символов\n"
|
||||
settings_text += f"• Путь к БД: {config.DATABASE_PATH}\n\n"
|
||||
settings_text += f"👑 <b>Администраторы:</b>\n"
|
||||
for admin_id in config.ADMINS:
|
||||
settings_text += f"• {admin_id}\n"
|
||||
|
||||
settings_text += "\n💡 Настройки изменяются в файле .env"
|
||||
|
||||
await message.answer(
|
||||
settings_text,
|
||||
reply_markup=get_admin_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin_stats")
|
||||
@require_permission("view_stats", "❌ У вас нет прав")
|
||||
async def admin_stats_callback(callback: CallbackQuery, database: DatabaseService = None):
|
||||
"""Обработчик кнопки 'Статистика' в админ панели"""
|
||||
await show_stats(callback.message, database)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin_broadcast")
|
||||
@require_permission("broadcast", "❌ У вас нет прав")
|
||||
async def admin_broadcast_callback(callback: CallbackQuery):
|
||||
"""Обработчик кнопки 'Рассылка' в админ панели"""
|
||||
await callback.message.edit_text(
|
||||
"📢 <b>Рассылка</b>\n\n"
|
||||
"Функция рассылки будет добавлена в следующих версиях.\n\n"
|
||||
"💡 Планируемые возможности:\n"
|
||||
"• Рассылка сообщений всем пользователям\n"
|
||||
"• Рассылка по группам пользователей\n"
|
||||
"• Планирование рассылок\n"
|
||||
"• Статистика доставки",
|
||||
reply_markup=get_admin_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "stats_general")
|
||||
@require_permission("view_stats", "❌ У вас нет прав")
|
||||
async def stats_general_callback(callback: CallbackQuery, database: DatabaseService = None):
|
||||
"""Обработчик кнопки 'Общая статистика'"""
|
||||
await show_stats(callback.message, database)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_admin")
|
||||
@require_permission("admin_panel", "❌ У вас нет прав")
|
||||
async def back_to_admin_callback(callback: CallbackQuery):
|
||||
"""Обработчик кнопки 'Назад' в админ панель"""
|
||||
admin_text = "👑 <b>Админ панель</b>\n\n"
|
||||
admin_text += "Выберите действие:"
|
||||
|
||||
await callback.message.edit_text(
|
||||
admin_text,
|
||||
reply_markup=get_admin_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin_assign_superuser")
|
||||
@require_permission("manage_users", "❌ У вас нет прав для управления пользователями.")
|
||||
async def admin_assign_superuser_callback(
|
||||
callback: CallbackQuery,
|
||||
user_service: UserService,
|
||||
message_service: MessageService
|
||||
):
|
||||
"""Обработчик кнопки 'Назначить суперпользователя'"""
|
||||
|
||||
try:
|
||||
# Получаем пользователей с пагинацией (оптимизированный запрос)
|
||||
users = await user_service.database.get_all_users(limit=MAX_PAGE_SIZE, offset=MIN_PAGE_NUMBER)
|
||||
|
||||
if not users:
|
||||
await message_service.edit_message(
|
||||
callback,
|
||||
"👥 <b>Управление суперпользователями</b>\n\n"
|
||||
"❌ Пользователи не найдены.",
|
||||
get_admin_keyboard()
|
||||
)
|
||||
await message_service.send_callback_answer(callback)
|
||||
return
|
||||
|
||||
# Показываем первую страницу
|
||||
await show_superuser_assignment_page(
|
||||
callback,
|
||||
users,
|
||||
page=MIN_PAGE_NUMBER,
|
||||
message_service=message_service
|
||||
)
|
||||
await message_service.send_callback_answer(callback)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка пользователей: {e}")
|
||||
await message_service.edit_message(
|
||||
callback,
|
||||
"❌ Произошла ошибка при загрузке списка пользователей.",
|
||||
get_admin_keyboard()
|
||||
)
|
||||
await message_service.send_callback_answer(callback)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("superuser_page_"))
|
||||
@require_permission("manage_users", "❌ У вас нет прав для управления пользователями.")
|
||||
async def superuser_page_callback(callback: CallbackQuery, database: DatabaseService = None):
|
||||
"""Обработчик пагинации списка пользователей для назначения суперпользователей"""
|
||||
|
||||
try:
|
||||
# Извлекаем номер страницы
|
||||
page = int(callback.data.split("_")[-1])
|
||||
|
||||
if not database:
|
||||
from dependencies import get_database
|
||||
database = get_database()
|
||||
|
||||
# Получаем всех пользователей
|
||||
users = await database.get_all_users(limit=MAX_PAGE_SIZE, offset=MIN_PAGE_NUMBER)
|
||||
|
||||
if not users:
|
||||
await callback.answer("❌ Пользователи не найдены", show_alert=True)
|
||||
return
|
||||
|
||||
# Показываем нужную страницу
|
||||
await show_superuser_assignment_page(callback, users, page)
|
||||
await callback.answer()
|
||||
|
||||
except ValueError:
|
||||
await callback.answer("❌ Неверный номер страницы", show_alert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при пагинации пользователей: {e}")
|
||||
await callback.answer("❌ Ошибка при загрузке страницы", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("assign_superuser_"))
|
||||
@require_permission("manage_users", "❌ У вас нет прав для управления пользователями.")
|
||||
async def assign_superuser_callback(callback: CallbackQuery, database: DatabaseService = None, validator = None):
|
||||
"""Обработчик выбора пользователя для назначения суперпользователем"""
|
||||
|
||||
try:
|
||||
# Извлекаем ID пользователя
|
||||
user_id_str = callback.data.split("_")[-1]
|
||||
|
||||
# Валидируем callback data
|
||||
if validator:
|
||||
validation_result = validator.validate_callback_data(callback.data)
|
||||
if not validation_result:
|
||||
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
|
||||
await callback.answer("❌ Неверные данные", show_alert=True)
|
||||
return
|
||||
|
||||
# Валидируем user_id
|
||||
try:
|
||||
user_id = int(user_id_str)
|
||||
if validator:
|
||||
user_id_validation = validator.validate_telegram_id(user_id)
|
||||
if not user_id_validation:
|
||||
logger.warning(f"⚠️ Невалидный user_id в callback: {user_id}")
|
||||
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
|
||||
return
|
||||
except ValueError:
|
||||
logger.warning(f"⚠️ Неверный формат user_id в callback: {user_id_str}")
|
||||
await callback.answer("❌ Неверный формат ID", show_alert=True)
|
||||
return
|
||||
|
||||
if not database:
|
||||
from dependencies import get_database
|
||||
database = get_database()
|
||||
|
||||
# Получаем информацию о пользователе
|
||||
user = await database.get_user(user_id)
|
||||
if not user:
|
||||
await callback.answer("❌ Пользователь не найден", show_alert=True)
|
||||
return
|
||||
|
||||
# Формируем текст с информацией о пользователе
|
||||
user_text = f"👤 <b>Информация о пользователе</b>\n\n"
|
||||
user_text += f"🆔 ID: {user.telegram_id}\n"
|
||||
user_text += f"👤 Имя: {user.display_name}\n"
|
||||
user_text += f"📝 Полное имя: {user.full_name}\n"
|
||||
user_text += f"🔗 Ссылка: {user.profile_link}\n"
|
||||
created_at_str = user.created_at.strftime('%d.%m.%Y %H:%M') if user.created_at else 'Неизвестно'
|
||||
user_text += f"📅 Регистрация: {created_at_str}\n"
|
||||
user_text += f"✅ Активен: {'Да' if user.is_active else 'Нет'}\n"
|
||||
user_text += f"🔍 Суперпользователь: {'Да' if user.is_superuser else 'Нет'}\n\n"
|
||||
|
||||
if user.is_superuser:
|
||||
user_text += "❓ <b>Хотите снять права суперпользователя?</b>"
|
||||
else:
|
||||
user_text += "❓ <b>Хотите назначить суперпользователем?</b>"
|
||||
|
||||
# Показываем информацию и кнопки подтверждения
|
||||
await callback.message.edit_text(
|
||||
user_text,
|
||||
reply_markup=get_superuser_confirm_keyboard(user_id, user.is_superuser),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
except ValueError:
|
||||
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении информации о пользователе: {e}")
|
||||
await callback.answer("❌ Ошибка при загрузке информации", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_superuser_"))
|
||||
@require_permission("manage_users", "❌ У вас нет прав для управления пользователями.")
|
||||
async def confirm_superuser_callback(callback: CallbackQuery, database: DatabaseService = None, validator = None):
|
||||
"""Обработчик подтверждения назначения суперпользователем"""
|
||||
|
||||
try:
|
||||
# Извлекаем ID пользователя
|
||||
user_id_str = callback.data.split("_")[-1]
|
||||
|
||||
# Валидируем callback data
|
||||
if validator:
|
||||
validation_result = validator.validate_callback_data(callback.data)
|
||||
if not validation_result:
|
||||
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
|
||||
await callback.answer("❌ Неверные данные", show_alert=True)
|
||||
return
|
||||
|
||||
# Валидируем user_id
|
||||
try:
|
||||
user_id = int(user_id_str)
|
||||
if validator:
|
||||
user_id_validation = validator.validate_telegram_id(user_id)
|
||||
if not user_id_validation:
|
||||
logger.warning(f"⚠️ Невалидный user_id в callback: {user_id}")
|
||||
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
|
||||
return
|
||||
except ValueError:
|
||||
logger.warning(f"⚠️ Неверный формат user_id в callback: {user_id_str}")
|
||||
await callback.answer("❌ Неверный формат ID", show_alert=True)
|
||||
return
|
||||
|
||||
if not database:
|
||||
from dependencies import get_database
|
||||
database = get_database()
|
||||
|
||||
# Получаем пользователя
|
||||
user = await database.get_user(user_id)
|
||||
if not user:
|
||||
await callback.answer("❌ Пользователь не найден", show_alert=True)
|
||||
return
|
||||
|
||||
# Назначаем суперпользователем
|
||||
user.is_superuser = True
|
||||
await database.update_user(user)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"✅ <b>Права назначены!</b>\n\n"
|
||||
f"👤 Пользователь {user.display_name} теперь является суперпользователем.\n\n"
|
||||
f"🔍 Суперпользователи могут видеть информацию об авторах вопросов.",
|
||||
reply_markup=get_superuser_confirm_keyboard(user_id, True),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer("✅ Права суперпользователя назначены!")
|
||||
|
||||
except ValueError:
|
||||
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при назначении суперпользователя: {e}")
|
||||
await callback.answer("❌ Ошибка при назначении прав", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("remove_superuser_"))
|
||||
@require_permission("manage_users", "❌ У вас нет прав для управления пользователями.")
|
||||
async def remove_superuser_callback(callback: CallbackQuery, database: DatabaseService = None, validator = None):
|
||||
"""Обработчик снятия прав суперпользователя"""
|
||||
|
||||
try:
|
||||
# Извлекаем ID пользователя
|
||||
user_id_str = callback.data.split("_")[-1]
|
||||
|
||||
# Валидируем callback data
|
||||
if validator:
|
||||
validation_result = validator.validate_callback_data(callback.data)
|
||||
if not validation_result:
|
||||
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
|
||||
await callback.answer("❌ Неверные данные", show_alert=True)
|
||||
return
|
||||
|
||||
# Валидируем user_id
|
||||
try:
|
||||
user_id = int(user_id_str)
|
||||
if validator:
|
||||
user_id_validation = validator.validate_telegram_id(user_id)
|
||||
if not user_id_validation:
|
||||
logger.warning(f"⚠️ Невалидный user_id в callback: {user_id}")
|
||||
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
|
||||
return
|
||||
except ValueError:
|
||||
logger.warning(f"⚠️ Неверный формат user_id в callback: {user_id_str}")
|
||||
await callback.answer("❌ Неверный формат ID", show_alert=True)
|
||||
return
|
||||
|
||||
if not database:
|
||||
from dependencies import get_database
|
||||
database = get_database()
|
||||
|
||||
# Получаем пользователя
|
||||
user = await database.get_user(user_id)
|
||||
if not user:
|
||||
await callback.answer("❌ Пользователь не найден", show_alert=True)
|
||||
return
|
||||
|
||||
# Снимаем права суперпользователя
|
||||
user.is_superuser = False
|
||||
await database.update_user(user)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"❌ <b>Права сняты!</b>\n\n"
|
||||
f"👤 Пользователь {user.display_name} больше не является суперпользователем.\n\n"
|
||||
f"👤 Теперь он видит анонимные вопросы без информации об авторах.",
|
||||
reply_markup=get_superuser_confirm_keyboard(user_id, False),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer("❌ Права суперпользователя сняты!")
|
||||
|
||||
except ValueError:
|
||||
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при снятии прав суперпользователя: {e}")
|
||||
await callback.answer("❌ Ошибка при снятии прав", show_alert=True)
|
||||
|
||||
|
||||
async def show_superuser_assignment_page(
|
||||
callback: CallbackQuery,
|
||||
users: list,
|
||||
page: int = MIN_PAGE_NUMBER,
|
||||
per_page: int = DEFAULT_PAGE_SIZE,
|
||||
pagination_service: PaginationService = None,
|
||||
message_service: MessageService = None
|
||||
):
|
||||
"""Показать страницу с пользователями для назначения суперпользователей"""
|
||||
try:
|
||||
# Используем сервисы если они переданы, иначе создаем временные
|
||||
if not pagination_service:
|
||||
from dependencies import get_pagination_service
|
||||
pagination_service = get_pagination_service()
|
||||
|
||||
if not message_service:
|
||||
from dependencies import get_message_service
|
||||
message_service = get_message_service()
|
||||
|
||||
# Формируем текст сообщения
|
||||
users_text = await _format_users_list(users, page, per_page, pagination_service)
|
||||
|
||||
# Создаем клавиатуру
|
||||
keyboard = get_superuser_assignment_keyboard(users, page, per_page)
|
||||
|
||||
# Отправляем или редактируем сообщение
|
||||
await message_service.edit_message(callback, users_text, keyboard)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отображении страницы пользователей: {e}")
|
||||
await message_service.edit_message(
|
||||
callback,
|
||||
"❌ Произошла ошибка при отображении списка пользователей.",
|
||||
get_admin_keyboard()
|
||||
)
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Rate Limiting callback обработчики
|
||||
# ===========================================
|
||||
|
||||
@router.callback_query(F.data == "admin_rate_limit")
|
||||
async def admin_rate_limit_menu(
|
||||
callback: CallbackQuery,
|
||||
message_service: MessageService,
|
||||
auth: AuthService
|
||||
):
|
||||
"""Показать меню rate limiting"""
|
||||
try:
|
||||
if not auth.is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Доступ запрещен. Только для администраторов.", show_alert=True)
|
||||
return
|
||||
|
||||
text = "🚦 <b>Управление Rate Limiting</b>\n\n"
|
||||
text += "Выберите действие для управления системой ограничения скорости отправки сообщений:"
|
||||
|
||||
await message_service.edit_message(callback, text, get_rate_limit_keyboard())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отображении меню rate limiting: {e}")
|
||||
await callback.answer("❌ Произошла ошибка при отображении меню.", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "rate_limit_stats")
|
||||
@inject_admin_services
|
||||
async def rate_limit_stats_callback(
|
||||
callback: CallbackQuery,
|
||||
rate_limit_service: RateLimitService,
|
||||
message_service: MessageService,
|
||||
auth: AuthService,
|
||||
**kwargs
|
||||
):
|
||||
"""Показать статистику rate limiting"""
|
||||
try:
|
||||
if not auth.is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Доступ запрещен. Только для администраторов.", show_alert=True)
|
||||
return
|
||||
|
||||
stats = rate_limit_service.get_stats()
|
||||
|
||||
stats_text = "📊 <b>Статистика Rate Limiting</b>\n\n"
|
||||
stats_text += "🔢 <b>Общая статистика:</b>\n"
|
||||
stats_text += f"• Всего запросов: {stats.get('total_requests', 0)}\n"
|
||||
stats_text += f"• Успешных запросов: {stats.get('successful_requests', 0)}\n"
|
||||
stats_text += f"• Неудачных запросов: {stats.get('failed_requests', 0)}\n"
|
||||
stats_text += f"• Процент успеха: {stats.get('success_rate', 0.0):.{PERCENTAGE_DECIMAL_PLACES}%}\n"
|
||||
stats_text += f"• Процент ошибок: {stats.get('error_rate', 0.0):.{PERCENTAGE_DECIMAL_PLACES}%}\n"
|
||||
stats_text += f"• Среднее время ожидания: {stats.get('average_wait_time', 0.0):.{TIME_DECIMAL_PLACES}f}с\n\n"
|
||||
|
||||
stats_text += "🔍 <b>Детальная статистика:</b>\n"
|
||||
stats_text += f"• RetryAfter ошибок: {stats.get('retry_after_errors', 0)}\n"
|
||||
stats_text += f"• Других ошибок: {stats.get('other_errors', 0)}\n"
|
||||
stats_text += f"• Процент RetryAfter: {stats.get('retry_after_rate', 0.0):.{PERCENTAGE_DECIMAL_PLACES}%}\n"
|
||||
stats_text += f"• Процент других ошибок: {stats.get('other_error_rate', 0.0):.{PERCENTAGE_DECIMAL_PLACES}%}\n"
|
||||
|
||||
await message_service.edit_message(callback, stats_text, get_rate_limit_keyboard())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении статистики rate limiting: {e}")
|
||||
await callback.answer("❌ Произошла ошибка при получении статистики.", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "rate_limit_reset_stats")
|
||||
@inject_admin_services
|
||||
async def reset_rate_limit_stats_callback(
|
||||
callback: CallbackQuery,
|
||||
rate_limit_service: RateLimitService,
|
||||
message_service: MessageService,
|
||||
auth: AuthService,
|
||||
**kwargs
|
||||
):
|
||||
"""Сбросить статистику rate limiting"""
|
||||
try:
|
||||
if not auth.is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Доступ запрещен. Только для администраторов.", show_alert=True)
|
||||
return
|
||||
|
||||
rate_limit_service.reset_stats()
|
||||
await callback.answer("✅ Статистика rate limiting сброшена.", show_alert=True)
|
||||
|
||||
# Обновляем сообщение
|
||||
text = "🚦 <b>Управление Rate Limiting</b>\n\n"
|
||||
text += "Выберите действие для управления системой ограничения скорости отправки сообщений:"
|
||||
await message_service.edit_message(callback, text, get_rate_limit_keyboard())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сбросе статистики rate limiting: {e}")
|
||||
await callback.answer("❌ Произошла ошибка при сбросе статистики.", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "rate_limit_adapt")
|
||||
@inject_admin_services
|
||||
async def adapt_rate_limit_config_callback(
|
||||
callback: CallbackQuery,
|
||||
rate_limit_service: RateLimitService,
|
||||
message_service: MessageService,
|
||||
auth: AuthService,
|
||||
**kwargs
|
||||
):
|
||||
"""Адаптировать конфигурацию rate limiting"""
|
||||
try:
|
||||
if not auth.is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Доступ запрещен. Только для администраторов.", show_alert=True)
|
||||
return
|
||||
|
||||
if rate_limit_service.should_adapt_config():
|
||||
await rate_limit_service.adapt_config_if_needed()
|
||||
await callback.answer("✅ Конфигурация rate limiting адаптирована на основе текущей производительности.", show_alert=True)
|
||||
else:
|
||||
await callback.answer("ℹ️ Адаптация конфигурации не требуется. Недостаточно данных или производительность в норме.", show_alert=True)
|
||||
|
||||
# Обновляем сообщение
|
||||
text = "🚦 <b>Управление Rate Limiting</b>\n\n"
|
||||
text += "Выберите действие для управления системой ограничения скорости отправки сообщений:"
|
||||
await message_service.edit_message(callback, text, get_rate_limit_keyboard())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при адаптации конфигурации rate limiting: {e}")
|
||||
await callback.answer("❌ Произошла ошибка при адаптации конфигурации.", show_alert=True)
|
||||
|
||||
|
||||
478
handlers/answers.py
Normal file
478
handlers/answers.py
Normal file
@@ -0,0 +1,478 @@
|
||||
"""
|
||||
Обработчики для работы с ответами на вопросы
|
||||
"""
|
||||
from datetime import datetime
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
from config import config
|
||||
from models.question import Question, QuestionStatus
|
||||
from services.infrastructure.database import DatabaseService
|
||||
from services.auth.auth_new import AuthService
|
||||
from services.utils import is_valid_answer_text, format_question_info, send_answer_to_author, escape_html
|
||||
from services.infrastructure.logger import get_logger
|
||||
from services.infrastructure.metrics import get_metrics_service, track_answer_processing
|
||||
from dependencies import inject_answer_services, inject_main_menu_services
|
||||
from keyboards.inline import get_question_view_keyboard
|
||||
from keyboards.reply import get_main_keyboard_for_user, get_cancel_keyboard
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
class AnswerStates(StatesGroup):
|
||||
"""Состояния для работы с ответами"""
|
||||
waiting_for_answer = State()
|
||||
editing_answer = State()
|
||||
confirming_delete = State()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("view_question_"))
|
||||
async def view_question_callback(callback: CallbackQuery):
|
||||
"""Обработчик просмотра конкретного вопроса"""
|
||||
try:
|
||||
question_id = int(callback.data.split("_")[2])
|
||||
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
# Получаем вопрос
|
||||
question = await db.get_question(question_id)
|
||||
|
||||
if not question:
|
||||
await callback.answer("❌ Вопрос не найден", show_alert=True)
|
||||
return
|
||||
|
||||
if question.to_user_id != callback.from_user.id:
|
||||
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
|
||||
return
|
||||
|
||||
# Формируем текст сообщения
|
||||
question_text = format_question_info(question, show_answer=True)
|
||||
|
||||
# Получаем клавиатуру
|
||||
keyboard = get_question_view_keyboard(question)
|
||||
|
||||
# Обновляем сообщение
|
||||
await callback.message.edit_text(
|
||||
question_text,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при просмотре вопроса: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("answer_"))
|
||||
async def answer_callback(callback: CallbackQuery, state: FSMContext):
|
||||
"""Обработчик создания нового ответа"""
|
||||
try:
|
||||
logger.info(f"Получен callback для ответа: {callback.data}")
|
||||
question_id = int(callback.data.split("_")[1])
|
||||
logger.info(f"Извлечен question_id: {question_id}")
|
||||
|
||||
# Получаем базу данных
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
# Получаем вопрос
|
||||
question = await db.get_question(question_id)
|
||||
if not question:
|
||||
await callback.answer("❌ Вопрос не найден", show_alert=True)
|
||||
return
|
||||
|
||||
# Проверяем права доступа
|
||||
if question.to_user_id != callback.from_user.id:
|
||||
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
|
||||
return
|
||||
|
||||
# Проверяем, что вопрос еще не отвечен
|
||||
if question.status == QuestionStatus.ANSWERED:
|
||||
await callback.answer("❌ На этот вопрос уже отвечено", show_alert=True)
|
||||
return
|
||||
|
||||
# Устанавливаем состояние ожидания ответа
|
||||
logger.info(f"Устанавливаем состояние waiting_for_answer для вопроса {question_id}")
|
||||
await state.set_state(AnswerStates.waiting_for_answer)
|
||||
await state.update_data(question_id=question_id)
|
||||
logger.info(f"Состояние установлено, данные сохранены")
|
||||
|
||||
# Отправляем сообщение с просьбой ввести ответ
|
||||
logger.info(f"Отправляем сообщение с просьбой ввести ответ для вопроса {question_id}")
|
||||
await callback.message.edit_text(
|
||||
f"✏️ <b>Ответ на вопрос #{question_id}</b>\n\n"
|
||||
f"❓ <b>Вопрос:</b>\n{escape_html(question.message_text)}\n\n"
|
||||
f"💬 <b>Введите ваш ответ:</b>",
|
||||
reply_markup=get_cancel_keyboard(),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
logger.info(f"Callback обработан успешно для вопроса {question_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании ответа: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("edit_answer_"))
|
||||
async def edit_answer_callback(callback: CallbackQuery, state: FSMContext):
|
||||
"""Обработчик редактирования ответа"""
|
||||
try:
|
||||
question_id = int(callback.data.split("_")[2])
|
||||
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
# Получаем вопрос
|
||||
question = await db.get_question(question_id)
|
||||
|
||||
if not question:
|
||||
await callback.answer("❌ Вопрос не найден", show_alert=True)
|
||||
return
|
||||
|
||||
if question.to_user_id != callback.from_user.id:
|
||||
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
|
||||
return
|
||||
|
||||
if question.status != QuestionStatus.ANSWERED:
|
||||
await callback.answer("❌ На этот вопрос еще не отвечено", show_alert=True)
|
||||
return
|
||||
|
||||
# Устанавливаем состояние редактирования ответа
|
||||
await state.set_state(AnswerStates.editing_answer)
|
||||
await state.update_data(question_id=question_id, current_answer=question.answer_text)
|
||||
|
||||
# Отправляем сообщение с просьбой ввести новый ответ
|
||||
await callback.message.edit_text(
|
||||
f"✏️ <b>Редактирование ответа на вопрос #{question_id}</b>\n\n"
|
||||
f"📝 <b>Вопрос:</b>\n{escape_html(question.message_text)}\n\n"
|
||||
f"💬 <b>Текущий ответ:</b>\n{escape_html(question.answer_text)}\n\n"
|
||||
f"✍️ <b>Введите новый ответ:</b>",
|
||||
reply_markup=None,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при редактировании ответа: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
|
||||
|
||||
@router.message(StateFilter(AnswerStates.waiting_for_answer))
|
||||
@inject_answer_services
|
||||
async def process_new_answer(message: Message, state: FSMContext, validator, **kwargs):
|
||||
"""Обработка нового ответа"""
|
||||
try:
|
||||
logger.info(f"Получено сообщение в состоянии waiting_for_answer: {message.text[:50]}...")
|
||||
# Получаем данные из состояния
|
||||
data = await state.get_data()
|
||||
question_id = data.get('question_id')
|
||||
logger.info(f"Получен question_id из состояния: {question_id}")
|
||||
|
||||
if not question_id:
|
||||
await message.answer(
|
||||
"❌ Ошибка: не найден вопрос для ответа.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Валидируем текст ответа
|
||||
validation_result = validator.validate_answer_text(
|
||||
message.text,
|
||||
config.MAX_ANSWER_LENGTH
|
||||
)
|
||||
|
||||
if not validation_result:
|
||||
logger.warning(f"⚠️ Невалидный ответ от пользователя {message.from_user.id}: {validation_result.error_message}")
|
||||
await message.answer(
|
||||
f"❌ {validation_result.error_message}\n\n"
|
||||
"Попробуйте отправить ответ еще раз:",
|
||||
reply_markup=get_cancel_keyboard()
|
||||
)
|
||||
return
|
||||
|
||||
# Используем санитизированный текст
|
||||
sanitized_answer_text = validation_result.sanitized_value
|
||||
|
||||
# Сохраняем ответ в БД
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
question = await db.get_question(question_id)
|
||||
if question:
|
||||
question.answer_text = sanitized_answer_text
|
||||
question.answered_at = datetime.now()
|
||||
question.mark_as_answered() # Устанавливаем статус ANSWERED
|
||||
await db.update_question(question)
|
||||
|
||||
# Отправляем ответ автору вопроса
|
||||
logger.info(f"Попытка отправить ответ автору вопроса {question.id}, from_user_id: {question.from_user_id}")
|
||||
await send_answer_to_author(message.bot, question, question.answer_text)
|
||||
|
||||
# Отправляем подтверждение
|
||||
await message.answer(
|
||||
"✅ <b>Ответ отправлен!</b>\n\n"
|
||||
"💬 Ваш ответ был успешно отправлен автору вопроса.\n\n"
|
||||
"📋 Используйте 'Мои вопросы' для просмотра всех вопросов и ответов.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке нового ответа: {e}")
|
||||
await message.answer(
|
||||
"❌ Произошла ошибка при отправке ответа. Попробуйте позже.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.message(StateFilter(AnswerStates.editing_answer))
|
||||
@inject_answer_services
|
||||
async def process_edited_answer(message: Message, state: FSMContext, validator, **kwargs):
|
||||
"""Обработка отредактированного ответа"""
|
||||
try:
|
||||
# Получаем данные из состояния
|
||||
data = await state.get_data()
|
||||
question_id = data.get('question_id')
|
||||
current_answer = data.get('current_answer')
|
||||
|
||||
if not question_id:
|
||||
await message.answer(
|
||||
"❌ Ошибка: не найден вопрос для редактирования.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Валидируем текст ответа
|
||||
validation_result = validator.validate_answer_text(
|
||||
message.text,
|
||||
config.MAX_ANSWER_LENGTH
|
||||
)
|
||||
|
||||
if not validation_result:
|
||||
logger.warning(f"⚠️ Невалидный отредактированный ответ от пользователя {message.from_user.id}: {validation_result.error_message}")
|
||||
await message.answer(
|
||||
f"❌ {validation_result.error_message}\n\n"
|
||||
"Попробуйте отправить ответ еще раз:",
|
||||
reply_markup=get_cancel_keyboard()
|
||||
)
|
||||
return
|
||||
|
||||
# Используем санитизированный текст
|
||||
sanitized_answer_text = validation_result.sanitized_value
|
||||
|
||||
# Сохраняем отредактированный ответ
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
question = await db.get_question(question_id)
|
||||
if question:
|
||||
question.answer_text = sanitized_answer_text
|
||||
question.answered_at = datetime.now() # Обновляем время ответа
|
||||
await db.update_question(question)
|
||||
|
||||
# Отправляем ответ автору вопроса
|
||||
logger.info(f"Попытка отправить ответ автору вопроса {question.id}, from_user_id: {question.from_user_id}")
|
||||
await send_answer_to_author(message.bot, question, question.answer_text)
|
||||
|
||||
# Отправляем подтверждение
|
||||
await message.answer(
|
||||
"✅ <b>Ответ обновлен!</b>\n\n"
|
||||
"💬 Ваш ответ был успешно обновлен.\n\n"
|
||||
"📋 Используйте 'Мои вопросы' для просмотра всех вопросов и ответов.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке отредактированного ответа: {e}")
|
||||
await message.answer(
|
||||
"❌ Произошла ошибка при обновлении ответа. Попробуйте позже.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete_"))
|
||||
async def confirm_delete_callback(callback: CallbackQuery):
|
||||
"""Обработчик подтверждения удаления вопроса"""
|
||||
try:
|
||||
question_id = int(callback.data.split("_")[2])
|
||||
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
# Получаем вопрос
|
||||
question = await db.get_question(question_id)
|
||||
|
||||
if not question:
|
||||
await callback.answer("❌ Вопрос не найден", show_alert=True)
|
||||
return
|
||||
|
||||
if question.to_user_id != callback.from_user.id:
|
||||
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
|
||||
return
|
||||
|
||||
# Удаляем вопрос
|
||||
question.mark_as_deleted()
|
||||
await db.update_question(question)
|
||||
|
||||
# Обновляем сообщение
|
||||
await callback.message.edit_text(
|
||||
f"🗑️ <b>Вопрос #{question.user_question_number if question.user_question_number is not None else question_id} удален</b>\n\n"
|
||||
f"📅 Удален: {question.answered_at.strftime('%d.%m.%Y %H:%M')}",
|
||||
reply_markup=None,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer("🗑️ Вопрос удален")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при подтверждении удаления: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("cancel_delete_"))
|
||||
async def cancel_delete_callback(callback: CallbackQuery):
|
||||
"""Обработчик отмены удаления вопроса"""
|
||||
try:
|
||||
question_id = int(callback.data.split("_")[2])
|
||||
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
# Получаем вопрос
|
||||
question = await db.get_question(question_id)
|
||||
|
||||
if not question:
|
||||
await callback.answer("❌ Вопрос не найден", show_alert=True)
|
||||
return
|
||||
|
||||
if question.to_user_id != callback.from_user.id:
|
||||
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
|
||||
return
|
||||
|
||||
# Возвращаемся к просмотру вопроса
|
||||
question_text = format_question_info(question, show_answer=True)
|
||||
keyboard = get_question_view_keyboard(question)
|
||||
|
||||
await callback.message.edit_text(
|
||||
question_text,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer("❌ Удаление отменено")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отмене удаления: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_questions")
|
||||
async def back_to_questions_callback(callback: CallbackQuery):
|
||||
"""Обработчик кнопки 'Назад к списку'"""
|
||||
try:
|
||||
from dependencies import get_database
|
||||
db = get_database()
|
||||
|
||||
# Получаем вопросы пользователя
|
||||
questions = await db.get_user_questions(callback.from_user.id, limit=10)
|
||||
|
||||
if not questions:
|
||||
await callback.message.edit_text(
|
||||
"📭 У вас пока нет вопросов.\n\n"
|
||||
"🔗 Поделитесь своей ссылкой, чтобы получать анонимные вопросы!",
|
||||
reply_markup=None
|
||||
)
|
||||
else:
|
||||
questions_text = f"📋 <b>Ваши вопросы ({len(questions)}):</b>\n\n"
|
||||
|
||||
for i, question in enumerate(questions, 1):
|
||||
status_emoji = {
|
||||
'pending': '⏳',
|
||||
'answered': '✅',
|
||||
'rejected': '❌',
|
||||
'deleted': '🗑️'
|
||||
}
|
||||
|
||||
emoji = status_emoji.get(question.status.value, '❓')
|
||||
preview = question.get_question_preview(50)
|
||||
|
||||
# Используем user_question_number для отображения, если он есть
|
||||
display_number = question.user_question_number if question.user_question_number is not None else i
|
||||
questions_text += f"{i}. {emoji} <b>#{display_number}</b>\n"
|
||||
questions_text += f" {preview}\n"
|
||||
questions_text += f" 📅 {question.created_at.strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
|
||||
questions_text += "💡 Нажмите на номер вопроса для просмотра деталей."
|
||||
|
||||
await callback.message.edit_text(
|
||||
questions_text,
|
||||
reply_markup=None,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при возврате к списку вопросов: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
F.data == "back_to_main",
|
||||
)
|
||||
@inject_main_menu_services
|
||||
async def back_to_main_callback(
|
||||
callback: CallbackQuery,
|
||||
auth: AuthService,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработчик кнопки 'Назад' в главное меню"""
|
||||
try:
|
||||
# Используем инжектированную систему авторизации
|
||||
is_admin = auth.is_admin(callback.from_user.id)
|
||||
|
||||
if is_admin:
|
||||
from keyboards.inline import get_admin_keyboard
|
||||
keyboard = get_admin_keyboard()
|
||||
text = "🏠 <b>Главное меню (Админ)</b>\n\nВыберите действие:"
|
||||
else:
|
||||
keyboard = get_main_keyboard_for_user(callback.from_user.id)
|
||||
text = "🏠 <b>Главное меню</b>\n\nВыберите действие:"
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=None,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
# Отправляем новое сообщение с клавиатурой
|
||||
await callback.message.answer(
|
||||
text,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при возврате в главное меню: {e}")
|
||||
await callback.answer("❌ Произошла ошибка", show_alert=True)
|
||||
217
handlers/errors.py
Normal file
217
handlers/errors.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
Глобальная обработка ошибок
|
||||
"""
|
||||
import traceback
|
||||
from aiogram import Router
|
||||
from aiogram.types import ErrorEvent, Message, CallbackQuery
|
||||
from aiogram.exceptions import TelegramBadRequest, TelegramNetworkError, TelegramRetryAfter
|
||||
|
||||
from config import config
|
||||
from services.infrastructure.logger import get_logger
|
||||
from services.infrastructure.metrics import get_metrics_service
|
||||
from keyboards.reply import get_main_keyboard_for_user
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.error()
|
||||
async def error_handler(event: ErrorEvent):
|
||||
"""Глобальный обработчик ошибок"""
|
||||
error = event.exception
|
||||
update = event.update
|
||||
|
||||
# Записываем метрику ошибки
|
||||
metrics_service = get_metrics_service()
|
||||
metrics_service.increment_errors(type(error).__name__, "global_handler")
|
||||
|
||||
# Логируем ошибку
|
||||
logger.error(f"💥 Ошибка в обработчике: {error}")
|
||||
logger.error(f"🔍 Тип ошибки: {type(error).__name__}")
|
||||
logger.error(f"📋 Детали: {traceback.format_exc()}")
|
||||
|
||||
# Определяем тип обновления
|
||||
if update.message:
|
||||
await handle_message_error(update.message, error)
|
||||
elif update.callback_query:
|
||||
await handle_callback_error(update.callback_query, error)
|
||||
else:
|
||||
logger.error(f"❓ Неизвестный тип обновления: {update}")
|
||||
|
||||
|
||||
async def handle_message_error(message: Message, error: Exception):
|
||||
"""Обработка ошибок в сообщениях"""
|
||||
try:
|
||||
# Определяем тип ошибки и отправляем соответствующее сообщение
|
||||
if isinstance(error, TelegramRetryAfter):
|
||||
# Ошибка rate limiting
|
||||
await message.answer(
|
||||
f"⏳ Слишком много запросов. Попробуйте через {error.retry_after} секунд.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
elif isinstance(error, TelegramBadRequest):
|
||||
# Некорректный запрос
|
||||
if "message is not modified" in str(error):
|
||||
# Сообщение не изменилось - это не критическая ошибка
|
||||
logger.warning("Сообщение не изменилось")
|
||||
elif "chat not found" in str(error):
|
||||
# Чат не найден
|
||||
logger.warning(f"Чат не найден: {message.chat.id}")
|
||||
else:
|
||||
await message.answer(
|
||||
"❌ Произошла ошибка при обработке запроса. Попробуйте позже.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
elif isinstance(error, TelegramNetworkError):
|
||||
# Сетевая ошибка
|
||||
await message.answer(
|
||||
"🌐 Проблемы с сетью. Проверьте подключение к интернету.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
elif isinstance(error, ValueError):
|
||||
# Ошибка валидации
|
||||
await message.answer(
|
||||
"❌ Некорректные данные. Проверьте введенную информацию.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
elif isinstance(error, KeyError):
|
||||
# Ошибка ключа (обычно в FSM)
|
||||
await message.answer(
|
||||
"❌ Ошибка состояния. Попробуйте начать заново с команды /start.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
else:
|
||||
# Неизвестная ошибка
|
||||
if config.DEBUG:
|
||||
# В режиме отладки показываем детали ошибки
|
||||
error_text = f"🐛 <b>Ошибка отладки:</b>\n\n"
|
||||
error_text += f"<code>{type(error).__name__}: {str(error)}</code>"
|
||||
|
||||
await message.answer(
|
||||
error_text,
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
# В продакшене показываем общее сообщение
|
||||
await message.answer(
|
||||
"❌ Произошла неожиданная ошибка. Попробуйте позже или обратитесь к администратору.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
|
||||
# Уведомляем администраторов о критических ошибках
|
||||
if not isinstance(error, (TelegramRetryAfter, TelegramBadRequest)):
|
||||
await notify_admins_about_error(error, message)
|
||||
|
||||
except Exception as e:
|
||||
# Если даже обработка ошибки не удалась
|
||||
logger.critical(f"Критическая ошибка в обработчике ошибок: {e}")
|
||||
try:
|
||||
await message.answer(
|
||||
"❌ Критическая ошибка. Бот временно недоступен.",
|
||||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
except:
|
||||
pass # Если даже это не удалось, ничего не делаем
|
||||
|
||||
|
||||
async def handle_callback_error(callback: CallbackQuery, error: Exception):
|
||||
"""Обработка ошибок в callback запросах"""
|
||||
try:
|
||||
# Определяем тип ошибки и отправляем соответствующее сообщение
|
||||
if isinstance(error, TelegramRetryAfter):
|
||||
# Ошибка rate limiting
|
||||
await callback.answer(
|
||||
f"⏳ Слишком много запросов. Попробуйте через {error.retry_after} секунд.",
|
||||
show_alert=True
|
||||
)
|
||||
elif isinstance(error, TelegramBadRequest):
|
||||
# Некорректный запрос
|
||||
if "message is not modified" in str(error):
|
||||
# Сообщение не изменилось - это не критическая ошибка
|
||||
logger.warning("Сообщение не изменилось в callback")
|
||||
elif "query is too old" in str(error):
|
||||
# Запрос слишком старый
|
||||
await callback.answer(
|
||||
"⏰ Запрос устарел. Обновите страницу и попробуйте снова.",
|
||||
show_alert=True
|
||||
)
|
||||
else:
|
||||
await callback.answer(
|
||||
"❌ Произошла ошибка при обработке запроса.",
|
||||
show_alert=True
|
||||
)
|
||||
elif isinstance(error, TelegramNetworkError):
|
||||
# Сетевая ошибка
|
||||
await callback.answer(
|
||||
"🌐 Проблемы с сетью. Проверьте подключение.",
|
||||
show_alert=True
|
||||
)
|
||||
else:
|
||||
# Неизвестная ошибка
|
||||
if config.DEBUG:
|
||||
# В режиме отладки показываем детали ошибки
|
||||
error_text = f"🐛 <b>Ошибка отладки:</b>\n\n"
|
||||
error_text += f"<code>{type(error).__name__}: {str(error)}</code>"
|
||||
|
||||
await callback.message.edit_text(
|
||||
error_text,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
# В продакшене показываем общее сообщение
|
||||
await callback.answer(
|
||||
"❌ Произошла неожиданная ошибка.",
|
||||
show_alert=True
|
||||
)
|
||||
|
||||
# Уведомляем администраторов о критических ошибках
|
||||
if not isinstance(error, (TelegramRetryAfter, TelegramBadRequest)):
|
||||
await notify_admins_about_error(error, callback.message)
|
||||
|
||||
except Exception as e:
|
||||
# Если даже обработка ошибки не удалась
|
||||
logger.critical(f"Критическая ошибка в обработчике callback ошибок: {e}")
|
||||
try:
|
||||
await callback.answer(
|
||||
"❌ Критическая ошибка.",
|
||||
show_alert=True
|
||||
)
|
||||
except:
|
||||
pass # Если даже это не удалось, ничего не делаем
|
||||
|
||||
|
||||
async def notify_admins_about_error(error: Exception, message: Message):
|
||||
"""Уведомление администраторов об ошибке"""
|
||||
if not config.ADMINS:
|
||||
return
|
||||
|
||||
try:
|
||||
error_text = f"🚨 <b>Ошибка в боте</b>\n\n"
|
||||
error_text += f"🐛 <b>Тип:</b> {type(error).__name__}\n"
|
||||
error_text += f"📝 <b>Сообщение:</b> {str(error)}\n"
|
||||
error_text += f"👤 <b>Пользователь:</b> {message.from_user.id}\n"
|
||||
error_text += f"💬 <b>Чат:</b> {message.chat.id}\n"
|
||||
error_text += f"📅 <b>Время:</b> {message.date.strftime('%d.%m.%Y %H:%M:%S')}\n\n"
|
||||
|
||||
if config.DEBUG:
|
||||
error_text += f"🔍 <b>Трассировка:</b>\n<code>{traceback.format_exc()}</code>"
|
||||
|
||||
# Отправляем уведомление всем администраторам
|
||||
from dependencies import get_message_service
|
||||
message_service = get_message_service()
|
||||
|
||||
for admin_id in config.ADMINS:
|
||||
try:
|
||||
await message_service.send_bot_message(
|
||||
message.bot,
|
||||
admin_id,
|
||||
error_text
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось отправить уведомление админу {admin_id}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при уведомлении администраторов: {e}")
|
||||
|
||||
|
||||
1272
handlers/questions.py
Normal file
1272
handlers/questions.py
Normal file
File diff suppressed because it is too large
Load Diff
328
handlers/start.py
Normal file
328
handlers/start.py
Normal file
@@ -0,0 +1,328 @@
|
||||
"""
|
||||
Обработчики команд /start и /help
|
||||
"""
|
||||
from datetime import datetime
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message
|
||||
from aiogram.filters import Command, CommandStart
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from config import config
|
||||
from models.user import User
|
||||
from services.infrastructure.database import DatabaseService
|
||||
from services.auth.auth_new import AuthService
|
||||
from services.utils import UtilsService
|
||||
from services.business.user_service import UserService
|
||||
from services.business.message_service import MessageService
|
||||
from services.infrastructure.logger import get_logger
|
||||
from services.infrastructure.metrics import get_metrics_service, track_message_processing
|
||||
from keyboards.reply import get_main_keyboard_for_user, get_admin_reply_keyboard
|
||||
from keyboards.inline import get_admin_keyboard
|
||||
from dependencies import inject_start_services, inject_link_services, inject_main_menu_services
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def _create_welcome_message(user: User, referral_link: str) -> str:
|
||||
"""Создание приветственного сообщения"""
|
||||
welcome_text = f"👋 <b>Добро пожаловать, {user.display_name}!</b>\n\n"
|
||||
welcome_text += "🤖 Я бот для анонимных вопросов.\n\n"
|
||||
welcome_text += "📝 <b>Как это работает:</b>\n"
|
||||
welcome_text += "• Поделитесь своей ссылкой с друзьями\n"
|
||||
welcome_text += "• Они смогут задать вам анонимные вопросы\n"
|
||||
welcome_text += "• Вы получите уведомления и сможете ответить\n\n"
|
||||
welcome_text += f"🔗 <b>Ваша персональная ссылка:</b>\n"
|
||||
welcome_text += f"<code>{referral_link}</code>\n\n"
|
||||
welcome_text += "💡 <b>Совет:</b> Скопируйте ссылку и поделитесь ею в социальных сетях!"
|
||||
|
||||
return welcome_text
|
||||
|
||||
|
||||
async def _process_start_command(
|
||||
message: Message,
|
||||
user_service: UserService,
|
||||
auth: AuthService,
|
||||
utils: UtilsService,
|
||||
message_service: MessageService,
|
||||
validator
|
||||
) -> User:
|
||||
"""Обработка команды /start без аргументов"""
|
||||
# Валидируем Telegram ID пользователя
|
||||
user_id_validation = validator.validate_telegram_id(message.from_user.id)
|
||||
if not user_id_validation:
|
||||
logger.error(f"❌ Невалидный Telegram ID: {message.from_user.id}")
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"❌ Ошибка: недопустимый ID пользователя.",
|
||||
get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
raise ValueError(f"Invalid Telegram ID: {message.from_user.id}")
|
||||
|
||||
# Создаем или обновляем пользователя
|
||||
user = await user_service.create_or_update_user(message.from_user, message.chat.id)
|
||||
|
||||
# Проверяем, является ли пользователь админом
|
||||
is_admin = auth.is_admin(user.telegram_id)
|
||||
|
||||
# Генерируем персональную ссылку
|
||||
bot_info = await message.bot.get_me()
|
||||
referral_link = user_service.generate_referral_link(bot_info.username, user)
|
||||
|
||||
# Создаем приветственное сообщение
|
||||
welcome_text = await _create_welcome_message(user, referral_link)
|
||||
|
||||
# Выбираем клавиатуру в зависимости от роли
|
||||
if is_admin:
|
||||
keyboard = get_admin_reply_keyboard()
|
||||
else:
|
||||
keyboard = get_main_keyboard_for_user(user.telegram_id)
|
||||
logger.info(f"⌨️ Создана клавиатура для пользователя {user.telegram_id}: {type(keyboard).__name__}")
|
||||
|
||||
# Отправляем сообщение
|
||||
await message_service.send_message(message, welcome_text, keyboard)
|
||||
logger.info(f"✅ Приветственное сообщение отправлено пользователю {user.telegram_id}")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
@track_message_processing("start_command")
|
||||
@inject_start_services
|
||||
async def cmd_start(
|
||||
message: Message,
|
||||
state: FSMContext,
|
||||
user_service: UserService,
|
||||
auth: AuthService,
|
||||
utils: UtilsService,
|
||||
message_service: MessageService,
|
||||
validator,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработчик команды /start"""
|
||||
try:
|
||||
logger.info(f"🚀 Команда /start от пользователя {message.from_user.id} ({message.from_user.first_name})")
|
||||
|
||||
# Сбрасываем состояние FSM при команде /start
|
||||
await state.clear()
|
||||
logger.info(f"🔄 Состояние FSM сброшено для пользователя {message.from_user.id}")
|
||||
|
||||
# Получаем аргументы команды
|
||||
args = message.text.split()[1:] if len(message.text.split()) > 1 else []
|
||||
|
||||
# Обрабатываем deep linking если есть аргументы
|
||||
if args:
|
||||
logger.info(f"🔗 Обработка deep link: {args[0]}")
|
||||
await handle_deep_link(message, args[0], user_service, state, message_service, validator)
|
||||
else:
|
||||
# Обрабатываем обычную команду /start
|
||||
await _process_start_command(message, user_service, auth, utils, message_service, validator)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"💥 Ошибка в обработчике /start: {e}")
|
||||
await message_service.send_error_message(
|
||||
message,
|
||||
"❌ Произошла ошибка при запуске бота. Попробуйте позже."
|
||||
)
|
||||
|
||||
|
||||
async def handle_deep_link(
|
||||
message: Message,
|
||||
ref_code: str,
|
||||
user_service: UserService,
|
||||
state: FSMContext,
|
||||
message_service: MessageService,
|
||||
validator
|
||||
):
|
||||
"""Обработка deep linking для анонимных вопросов"""
|
||||
try:
|
||||
# Валидируем deep link
|
||||
validation_result = validator.validate_deep_link(ref_code)
|
||||
if not validation_result:
|
||||
logger.warning(f"⚠️ Невалидный deep link от пользователя {message.from_user.id}: {ref_code}")
|
||||
await message_service.send_message(
|
||||
message,
|
||||
f"❌ {validation_result.error_message}",
|
||||
get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
return
|
||||
|
||||
# Используем санитизированное значение
|
||||
ref_code = validation_result.sanitized_value
|
||||
|
||||
if not ref_code.startswith('ref_'):
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"❌ Неверная ссылка.",
|
||||
get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
return
|
||||
|
||||
# Извлекаем анонимный ID из реферального кода
|
||||
anonymous_id = ref_code[4:] # Убираем 'ref_'
|
||||
|
||||
# Ищем пользователя по profile_link
|
||||
target_user = await user_service.get_user_by_profile_link(anonymous_id)
|
||||
if not target_user:
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"❌ Пользователь, на которого вы перешли, не найден.",
|
||||
get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
return
|
||||
|
||||
# Отправляем сообщение о переходе по ссылке
|
||||
deep_link_text = (
|
||||
f"👋 Вы перешли по ссылке пользователя {target_user.display_name}!\n\n"
|
||||
f"📝 Теперь вы можете задать анонимный вопрос.\n"
|
||||
f"Просто отправьте ваше сообщение, и оно будет передано получателю."
|
||||
)
|
||||
|
||||
await message_service.send_message(
|
||||
message,
|
||||
deep_link_text,
|
||||
get_main_keyboard_for_user(message.from_user.id)
|
||||
)
|
||||
|
||||
# Устанавливаем состояние ожидания вопроса
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
class QuestionStates(StatesGroup):
|
||||
waiting_for_question = State()
|
||||
|
||||
await state.set_state(QuestionStates.waiting_for_question)
|
||||
await state.update_data(target_user_id=target_user.telegram_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке deep link: {e}")
|
||||
await message_service.send_error_message(
|
||||
message,
|
||||
"❌ Произошла ошибка при обработке ссылки."
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("help"))
|
||||
async def cmd_help(message: Message):
|
||||
"""Обработчик команды /help"""
|
||||
help_text = "📖 <b>Справка по боту</b>\n\n"
|
||||
help_text += "🤖 <b>Основные команды:</b>\n"
|
||||
help_text += "/start - Запуск бота и получение персональной ссылки\n"
|
||||
help_text += "/help - Показать эту справку\n"
|
||||
help_text += "/stats - Показать статистику (только для админов)\n\n"
|
||||
help_text += "📝 <b>Как задать анонимный вопрос:</b>\n"
|
||||
help_text += "1. Перейдите по персональной ссылке пользователя\n"
|
||||
help_text += "2. Отправьте ваш вопрос боту\n"
|
||||
help_text += "3. Вопрос будет передан получателю анонимно\n\n"
|
||||
help_text += "💬 <b>Как ответить на вопрос:</b>\n"
|
||||
help_text += "1. Получите уведомление о новом вопросе\n"
|
||||
help_text += "2. Нажмите кнопку 'Ответить'\n"
|
||||
help_text += "3. Введите ваш ответ\n"
|
||||
help_text += "4. Ответ будет отправлен анонимно\n\n"
|
||||
help_text += "🔗 <b>Ваша персональная ссылка:</b>\n"
|
||||
help_text += "Используйте кнопку 'Моя ссылка' для получения ссылки\n\n"
|
||||
help_text += "❓ <b>Нужна помощь?</b>\n"
|
||||
help_text += "Обратитесь к администратору бота: @Kerrad1"
|
||||
|
||||
await message.answer(help_text, parse_mode="HTML")
|
||||
|
||||
|
||||
@router.message(F.text == "ℹ️ Помощь")
|
||||
async def help_button(message: Message):
|
||||
"""Обработчик кнопки 'Помощь'"""
|
||||
await cmd_help(message)
|
||||
|
||||
|
||||
@router.message(F.text == "🔗 Моя ссылка")
|
||||
@inject_link_services
|
||||
async def my_link_button(
|
||||
message: Message,
|
||||
user_service: UserService,
|
||||
message_service: MessageService,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработчик кнопки 'Моя ссылка'"""
|
||||
try:
|
||||
# Получаем пользователя из БД
|
||||
user = await user_service.get_user_by_telegram_id(message.from_user.id)
|
||||
|
||||
if not user:
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"❌ Пользователь не найден. Используйте /start для регистрации."
|
||||
)
|
||||
return
|
||||
|
||||
# Получаем информацию о боте
|
||||
bot_info = await message.bot.get_me()
|
||||
referral_link = user_service.generate_referral_link(bot_info.username, user)
|
||||
|
||||
link_text = "🔗 <b>Ваша персональная ссылка:</b>\n\n"
|
||||
link_text += f"<code>{referral_link}</code>\n\n"
|
||||
link_text += "📝 <b>Как использовать:</b>\n"
|
||||
link_text += "• Скопируйте ссылку\n"
|
||||
link_text += "• Поделитесь ею в социальных сетях\n"
|
||||
link_text += "• Друзья смогут задать вам анонимные вопросы\n\n"
|
||||
link_text += "💡 <b>Совет:</b> Добавьте ссылку в описание профиля или поделитесь в Stories!"
|
||||
|
||||
await message_service.send_message(message, link_text)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении ссылки: {e}")
|
||||
await message_service.send_error_message(
|
||||
message,
|
||||
"❌ Произошла ошибка при получении ссылки. Попробуйте позже."
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "⬅️ Главное меню")
|
||||
@inject_main_menu_services
|
||||
async def back_to_main(
|
||||
message: Message,
|
||||
state: FSMContext,
|
||||
auth: AuthService,
|
||||
message_service: MessageService,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработчик кнопки 'Главное меню'"""
|
||||
# Сбрасываем состояние FSM при возврате в главное меню
|
||||
await state.clear()
|
||||
logger.info(f"🔄 Состояние FSM сброшено для пользователя {message.from_user.id} (кнопка 'Главное меню')")
|
||||
|
||||
is_admin = auth.is_admin(message.from_user.id)
|
||||
if is_admin:
|
||||
keyboard = get_admin_reply_keyboard()
|
||||
else:
|
||||
keyboard = get_main_keyboard_for_user(message.from_user.id)
|
||||
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"🏠 <b>Главное меню</b>\n\nВыберите действие:",
|
||||
keyboard
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "⚙️ Админ панель")
|
||||
@inject_main_menu_services
|
||||
async def admin_panel_button(
|
||||
message: Message,
|
||||
auth: AuthService,
|
||||
message_service: MessageService,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработчик кнопки 'Админ панель'"""
|
||||
# Проверяем, является ли пользователь админом
|
||||
if not auth.is_admin(message.from_user.id):
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"❌ У вас нет прав для доступа к админ панели."
|
||||
)
|
||||
return
|
||||
|
||||
# Получаем inline клавиатуру для админов
|
||||
admin_keyboard = get_admin_keyboard()
|
||||
|
||||
await message_service.send_message(
|
||||
message,
|
||||
"👑 <b>Админ панель</b>\n\nВыберите действие:",
|
||||
admin_keyboard
|
||||
)
|
||||
Reference in New Issue
Block a user