from aiogram import Router, types, F from aiogram.filters import Command, StateFilter, MagicData from aiogram.fsm.context import FSMContext from helper_bot.filters.main import ChatTypeFilter from helper_bot.keyboards.keyboards import ( get_reply_keyboard_admin, create_keyboard_with_pagination, create_keyboard_for_ban_days, create_keyboard_for_approve_ban, create_keyboard_for_ban_reason ) from helper_bot.handlers.admin.dependencies import AdminAccessMiddleware from helper_bot.handlers.admin.services import AdminService from helper_bot.handlers.admin.exceptions import ( UserAlreadyBannedError, InvalidInputError ) from helper_bot.handlers.admin.utils import ( return_to_admin_menu, handle_admin_error, format_user_info, format_ban_confirmation, escape_html ) from logs.custom_logger import logger # Создаем роутер с middleware для проверки доступа admin_router = Router() admin_router.message.middleware(AdminAccessMiddleware()) # ============================================================================ # ХЕНДЛЕРЫ МЕНЮ # ============================================================================ @admin_router.message( ChatTypeFilter(chat_type=["private"]), Command('admin') ) async def admin_panel( message: types.Message, state: FSMContext, **kwargs ): """Главное меню администратора""" try: await state.set_state("ADMIN") logger.info(f"Запуск админ панели для пользователя: {message.from_user.id}") markup = get_reply_keyboard_admin() await message.answer("Добро пожаловать в админку. Выбери что хочешь:", reply_markup=markup) except Exception as e: await handle_admin_error(message, e, state, "admin_panel") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("ADMIN"), F.text == 'Бан (Список)' ) async def get_last_users( message: types.Message, state: FSMContext, bot_db: MagicData("bot_db") ): """Получение списка последних пользователей""" try: logger.info(f"Получение списка последних пользователей. Пользователь: {message.from_user.full_name}") admin_service = AdminService(bot_db) users = await admin_service.get_last_users() # Преобразуем в формат для клавиатуры (кортежи как ожидает create_keyboard_with_pagination) users_data = [ (user.full_name, user.user_id) for user in users ] keyboard = create_keyboard_with_pagination(1, len(users_data), users_data, 'ban') await message.answer( text="Список пользователей которые последними обращались к боту", reply_markup=keyboard ) except Exception as e: await handle_admin_error(message, e, state, "get_last_users") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("ADMIN"), F.text == 'Разбан (список)' ) async def get_banned_users( message: types.Message, state: FSMContext, bot_db: MagicData("bot_db") ): """Получение списка заблокированных пользователей""" try: logger.info(f"Получение списка заблокированных пользователей. Пользователь: {message.from_user.full_name}") admin_service = AdminService(bot_db) message_text, buttons_list = await admin_service.get_banned_users_for_display(0) if buttons_list: keyboard = create_keyboard_with_pagination(1, len(buttons_list), buttons_list, 'unlock') await message.answer(text=message_text, reply_markup=keyboard) else: await message.answer(text="В списке заблокированных пользователей никого нет") except Exception as e: await handle_admin_error(message, e, state, "get_banned_users") # ============================================================================ # ХЕНДЛЕРЫ ПРОЦЕССА БАНА # ============================================================================ @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("ADMIN"), F.text.in_(['Бан по нику', 'Бан по ID']) ) async def start_ban_process( message: types.Message, state: FSMContext, **kwargs ): """Начало процесса блокировки пользователя""" try: ban_type = "username" if message.text == 'Бан по нику' else "id" await state.update_data(ban_type=ban_type) prompt_text = "Пришли мне username блокируемого пользователя" if ban_type == "username" else "Пришли мне ID блокируемого пользователя" await message.answer(prompt_text) await state.set_state('AWAIT_BAN_TARGET') except Exception as e: await handle_admin_error(message, e, state, "start_ban_process") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("AWAIT_BAN_TARGET") ) async def process_ban_target( message: types.Message, state: FSMContext, bot_db: MagicData("bot_db") ): """Обработка введенного username/ID для блокировки""" try: user_data = await state.get_data() ban_type = user_data.get('ban_type') admin_service = AdminService(bot_db) # Определяем пользователя if ban_type == "username": user = await admin_service.get_user_by_username(message.text) if not user: await message.answer(f"Пользователь с username '{escape_html(message.text)}' не найден.") await return_to_admin_menu(message, state) return else: # ban_type == "id" try: user_id = await admin_service.validate_user_input(message.text) user = await admin_service.get_user_by_id(user_id) if not user: await message.answer(f"Пользователь с ID {user_id} не найден в базе данных.") await return_to_admin_menu(message, state) return except InvalidInputError as e: await message.answer(str(e)) await return_to_admin_menu(message, state) return # Сохраняем данные пользователя await state.update_data( target_user_id=user.user_id, target_username=user.username, target_full_name=user.full_name ) # Показываем информацию о пользователе и запрашиваем причину user_info = format_user_info(user.user_id, user.username, user.full_name) markup = create_keyboard_for_ban_reason() await message.answer( text=f"{user_info}\n\nВыбери причину бана из списка или напиши ее в чат", reply_markup=markup ) await state.set_state('AWAIT_BAN_DETAILS') except Exception as e: await handle_admin_error(message, e, state, "process_ban_target") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("AWAIT_BAN_DETAILS") ) async def process_ban_reason( message: types.Message, state: FSMContext, **kwargs ): """Обработка причины блокировки""" try: await state.update_data(ban_reason=message.text) markup = create_keyboard_for_ban_days() safe_reason = escape_html(message.text) await message.answer( f"Выбрана причина: {safe_reason}. Выбери срок бана в днях или напиши его в чат", reply_markup=markup ) await state.set_state('AWAIT_BAN_DURATION') except Exception as e: await handle_admin_error(message, e, state, "process_ban_reason") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("AWAIT_BAN_DURATION") ) async def process_ban_duration( message: types.Message, state: FSMContext, **kwargs ): """Обработка срока блокировки""" try: user_data = await state.get_data() # Определяем срок блокировки if message.text == 'Навсегда': ban_days = None else: try: ban_days = int(message.text) if ban_days <= 0: await message.answer("Срок блокировки должен быть положительным числом.") return except ValueError: await message.answer("Пожалуйста, введите корректное число дней или выберите 'Навсегда'.") return await state.update_data(ban_days=ban_days) # Показываем подтверждение confirmation_text = format_ban_confirmation( user_data['target_user_id'], user_data['ban_reason'], ban_days ) markup = create_keyboard_for_approve_ban() await message.answer(confirmation_text, reply_markup=markup) await state.set_state('BAN_CONFIRMATION') except Exception as e: await handle_admin_error(message, e, state, "process_ban_duration") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("BAN_CONFIRMATION"), F.text == 'Подтвердить' ) async def confirm_ban( message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), **kwargs ): """Подтверждение блокировки пользователя""" try: user_data = await state.get_data() admin_service = AdminService(bot_db) # Выполняем блокировку await admin_service.ban_user( user_id=user_data['target_user_id'], username=user_data['target_username'], reason=user_data['ban_reason'], ban_days=user_data['ban_days'] ) safe_username = escape_html(user_data['target_username']) await message.reply(f"Пользователь {safe_username} успешно заблокирован.") await return_to_admin_menu(message, state) except UserAlreadyBannedError as e: await message.reply(str(e)) await return_to_admin_menu(message, state) except Exception as e: await handle_admin_error(message, e, state, "confirm_ban") # ============================================================================ # ХЕНДЛЕРЫ ОТМЕНЫ И НАВИГАЦИИ # ============================================================================ @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"), F.text == 'Отменить' ) async def cancel_ban_process( message: types.Message, state: FSMContext, **kwargs ): """Отмена процесса блокировки""" try: current_state = await state.get_state() logger.info(f"Отмена процедуры блокировки из состояния: {current_state}") await return_to_admin_menu(message, state) except Exception as e: await handle_admin_error(message, e, state, "cancel_ban_process") @admin_router.message(Command("test_metrics")) async def test_metrics_handler( message: types.Message, bot_db: MagicData("bot_db"), **kwargs ): """Тестовый хендлер для проверки метрик""" from helper_bot.utils.metrics import metrics try: # Принудительно записываем тестовые метрики metrics.record_command("test_metrics", "admin_handler", "admin", "success") metrics.record_message("text", "private", "admin_handler") metrics.record_error("TestError", "admin_handler", "test_metrics_handler") # Проверяем активных пользователей if hasattr(bot_db, 'connect') and hasattr(bot_db, 'cursor'): # Используем UNIX timestamp для сравнения с date_changed import time current_timestamp = int(time.time()) one_day_ago = current_timestamp - (24 * 60 * 60) # 24 часа назад active_users_query = """ SELECT COUNT(DISTINCT user_id) as active_users FROM our_users WHERE date_changed > ? """ try: await bot_db.connect() await bot_db.cursor.execute(active_users_query, (one_day_ago,)) result = await bot_db.cursor.fetchone() active_users = result[0] if result else 0 finally: await bot_db.close() else: active_users = "N/A" await message.answer( f"✅ Тестовые метрики записаны\n" f"📊 Активных пользователей: {active_users}\n" f"🔧 Проверьте Prometheus метрики" ) except Exception as e: await message.answer(f"❌ Ошибка тестирования метрик: {e}")