Files
telegram-helper-bot/helper_bot/handlers/admin/admin_handlers.py
Andrey 1c6a37bc12 Enhance bot functionality and refactor database interactions
- Added `ca-certificates` installation to Dockerfile for improved network security.
- Updated health check command in Dockerfile to include better timeout handling.
- Refactored `run_helper.py` to implement proper signal handling and logging during shutdown.
- Transitioned database operations to an asynchronous model in `async_db.py`, improving performance and responsiveness.
- Updated database schema to support new foreign key relationships and optimized indexing for better query performance.
- Enhanced various bot handlers to utilize async database methods, improving overall efficiency and user experience.
- Removed obsolete database and fix scripts to streamline the project structure.
2025-09-02 18:22:02 +03:00

363 lines
14 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 для блокировки"""
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}")