Files
telegram-helper-bot/helper_bot/handlers/admin/admin_handlers.py
Andrey 1ab427a7ba Enhance admin handlers with improved logging and error handling
- Added detailed logging for user ban processing in `process_ban_target` and `process_ban_reason` functions, including user data and error messages.
- Improved error handling for user input validation and database interactions.
- Updated `return_to_admin_menu` function to log user return actions.
- Enhanced media group handling in `PostPublishService` with better error logging and author ID retrieval.
- Added new button options in voice handlers and updated keyboard layouts for improved user interaction.
- Refactored album middleware to better handle media group messages and added documentation for clarity.
2025-09-02 22:20:34 +03:00

394 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 для блокировки"""
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")
)
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")
)
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}")