from aiogram import F, Router, types from aiogram.filters import Command, MagicData, StateFilter from aiogram.fsm.context import FSMContext from helper_bot.filters.main import ChatTypeFilter from helper_bot.handlers.admin.dependencies import AdminAccessMiddleware from helper_bot.handlers.admin.exceptions import (InvalidInputError, UserAlreadyBannedError) from helper_bot.handlers.admin.services import AdminService from helper_bot.handlers.admin.utils import (escape_html, format_ban_confirmation, format_user_info, handle_admin_error, return_to_admin_menu) from helper_bot.keyboards.keyboards import (create_keyboard_for_approve_ban, create_keyboard_for_ban_days, create_keyboard_for_ban_reason, create_keyboard_with_pagination, get_reply_keyboard_admin) # Local imports - metrics from helper_bot.utils.metrics import db_query_time, track_errors, track_time from logs.custom_logger import logger # Создаем роутер с middleware для проверки доступа admin_router = Router() admin_router.message.middleware(AdminAccessMiddleware()) # ============================================================================ # ХЕНДЛЕРЫ МЕНЮ # ============================================================================ @admin_router.message( ChatTypeFilter(chat_type=["private"]), Command('admin') ) @track_time("admin_panel", "admin_handlers") @track_errors("admin_handlers", "admin_panel") 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("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"), F.text == 'Отменить' ) @track_time("cancel_ban_process", "admin_handlers") @track_errors("admin_handlers", "cancel_ban_process") 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( ChatTypeFilter(chat_type=["private"]), StateFilter("ADMIN"), F.text == 'Бан (Список)' ) @track_time("get_last_users", "admin_handlers") @track_errors("admin_handlers", "get_last_users") @db_query_time("get_last_users", "users", "select") 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 == 'Разбан (список)' ) @track_time("get_banned_users", "admin_handlers") @track_errors("admin_handlers", "get_banned_users") @db_query_time("get_banned_users", "users", "select") 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']) ) @track_time("start_ban_process", "admin_handlers") @track_errors("admin_handlers", "start_ban_process") 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") ) @track_time("process_ban_target", "admin_handlers") @track_errors("admin_handlers", "process_ban_target") async def process_ban_target( message: types.Message, state: FSMContext, bot_db: MagicData("bot_db") ): """Обработка введенного username/ID для блокировки""" logger.info(f"process_ban_target: === НАЧАЛО ОБРАБОТКИ === Получено сообщение от {message.from_user.id}: {message.text}") try: user_data = await state.get_data() ban_type = user_data.get('ban_type') admin_service = AdminService(bot_db) logger.info(f"process_ban_target: ban_type={ban_type}, user_data={user_data}") # Определяем пользователя if ban_type == "username": logger.info(f"process_ban_target: Поиск пользователя по username: {message.text}") user = await admin_service.get_user_by_username(message.text) if not user: logger.warning(f"process_ban_target: Пользователь с username '{message.text}' не найден") await message.answer(f"Пользователь с username '{escape_html(message.text)}' не найден.") await return_to_admin_menu(message, state) return else: # ban_type == "id" try: logger.info(f"process_ban_target: Валидация и поиск пользователя по ID: {message.text}") user_id = await admin_service.validate_user_input(message.text) user = await admin_service.get_user_by_id(user_id) if not user: logger.warning(f"process_ban_target: Пользователь с ID {user_id} не найден в базе данных") await message.answer(f"Пользователь с ID {user_id} не найден в базе данных.") await return_to_admin_menu(message, state) return except InvalidInputError as e: logger.error(f"process_ban_target: Ошибка валидации ID: {e}") await message.answer(str(e)) await return_to_admin_menu(message, state) return logger.info(f"process_ban_target: Найден пользователь: {user.user_id}, {user.username}, {user.full_name}") # Сохраняем данные пользователя 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() logger.info(f"process_ban_target: Отправка сообщения с причиной бана, user_info: {user_info}") await message.answer( text=f"{user_info}\n\nВыбери причину бана из списка или напиши ее в чат", reply_markup=markup ) await state.set_state('AWAIT_BAN_DETAILS') logger.info("process_ban_target: Состояние изменено на AWAIT_BAN_DETAILS") except Exception as e: logger.error(f"process_ban_target: Неожиданная ошибка: {e}", exc_info=True) await handle_admin_error(message, e, state, "process_ban_target") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("AWAIT_BAN_DETAILS") ) @track_time("process_ban_reason", "admin_handlers") @track_errors("admin_handlers", "process_ban_reason") async def process_ban_reason( message: types.Message, state: FSMContext, **kwargs ): """Обработка причины блокировки""" logger.info(f"process_ban_reason: === НАЧАЛО ОБРАБОТКИ === Получено сообщение от {message.from_user.id}: {message.text}") try: # Проверяем текущее состояние current_state = await state.get_state() logger.info(f"process_ban_reason: Текущее состояние: {current_state}") # Проверяем данные состояния state_data = await state.get_data() logger.info(f"process_ban_reason: Данные состояния: {state_data}") logger.info(f"process_ban_reason: Обновление данных состояния с причиной: {message.text}") await state.update_data(ban_reason=message.text) markup = create_keyboard_for_ban_days() safe_reason = escape_html(message.text) logger.info(f"process_ban_reason: Отправка сообщения с выбором срока бана, причина: {safe_reason}") await message.answer( f"Выбрана причина: {safe_reason}. Выбери срок бана в днях или напиши его в чат", reply_markup=markup ) await state.set_state('AWAIT_BAN_DURATION') logger.info("process_ban_reason: Состояние изменено на AWAIT_BAN_DURATION") except Exception as e: logger.error(f"process_ban_reason: Неожиданная ошибка: {e}", exc_info=True) await handle_admin_error(message, e, state, "process_ban_reason") @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("AWAIT_BAN_DURATION") ) @track_time("process_ban_duration", "admin_handlers") @track_errors("admin_handlers", "process_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 == 'Подтвердить' ) @track_time("confirm_ban", "admin_handlers") @track_errors("admin_handlers", "confirm_ban") 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'], ban_author_id=message.from_user.id, ) 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")