748 lines
32 KiB
Python
748 lines
32 KiB
Python
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_auto_moderation_keyboard,
|
||
get_reply_keyboard_admin,
|
||
)
|
||
from helper_bot.utils.base_dependency_factory import get_global_instance
|
||
|
||
# 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, parse_mode="HTML")
|
||
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 == "📊 ML Статистика",
|
||
)
|
||
@track_time("get_ml_stats", "admin_handlers")
|
||
@track_errors("admin_handlers", "get_ml_stats")
|
||
async def get_ml_stats(message: types.Message, state: FSMContext, **kwargs):
|
||
"""Получение статистики ML-скоринга"""
|
||
try:
|
||
logger.info(
|
||
f"Запрос ML статистики от пользователя: {message.from_user.full_name}"
|
||
)
|
||
|
||
bdf = get_global_instance()
|
||
scoring_manager = bdf.get_scoring_manager()
|
||
|
||
if not scoring_manager:
|
||
await message.answer(
|
||
"📊 ML Scoring отключен\n\nДля включения установите RAG_ENABLED=true или DEEPSEEK_ENABLED=true в .env"
|
||
)
|
||
return
|
||
|
||
stats = await scoring_manager.get_stats()
|
||
|
||
# Формируем текст статистики
|
||
lines = ["📊 <b>ML Scoring Статистика</b>\n"]
|
||
|
||
# RAG статистика
|
||
if "rag" in stats:
|
||
rag = stats["rag"]
|
||
lines.append("🤖 <b>RAG API:</b>")
|
||
|
||
# Проверяем, есть ли данные из API (новый контракт содержит model_loaded и vector_store)
|
||
if "model_loaded" in rag or "vector_store" in rag:
|
||
# Данные из API /stats
|
||
if "model_loaded" in rag:
|
||
model_loaded = rag.get("model_loaded", False)
|
||
lines.append(
|
||
f" • Модель загружена: {'✅' if model_loaded else '❌'}"
|
||
)
|
||
if "model_name" in rag:
|
||
lines.append(f" • Модель: {rag.get('model_name', 'N/A')}")
|
||
if "device" in rag:
|
||
lines.append(f" • Устройство: {rag.get('device', 'N/A')}")
|
||
|
||
# Статистика из vector_store
|
||
if "vector_store" in rag:
|
||
vector_store = rag["vector_store"]
|
||
positive_count = vector_store.get("positive_count", 0)
|
||
negative_count = vector_store.get("negative_count", 0)
|
||
total_count = vector_store.get("total_count", 0)
|
||
|
||
lines.append(f" • Положительных примеров: {positive_count}")
|
||
lines.append(f" • Отрицательных примеров: {negative_count}")
|
||
lines.append(f" • Всего примеров: {total_count}")
|
||
|
||
if "vector_dim" in vector_store:
|
||
lines.append(
|
||
f" • Размерность векторов: {vector_store.get('vector_dim', 'N/A')}"
|
||
)
|
||
if "max_examples" in vector_store:
|
||
lines.append(
|
||
f" • Макс. примеров: {vector_store.get('max_examples', 'N/A')}"
|
||
)
|
||
else:
|
||
# Fallback на синхронные данные (если API недоступен)
|
||
lines.append(f" • API URL: {rag.get('api_url', 'N/A')}")
|
||
if "enabled" in rag:
|
||
if rag.get("enabled"):
|
||
lines.append(
|
||
f" • Статус: ⚠️ Включен, но API не отвечает"
|
||
)
|
||
lines.append(
|
||
f" • Проверьте доступность сервиса и API ключ"
|
||
)
|
||
else:
|
||
lines.append(f" • Статус: ❌ Отключен")
|
||
|
||
lines.append("")
|
||
|
||
# DeepSeek статистика
|
||
if "deepseek" in stats:
|
||
ds = stats["deepseek"]
|
||
lines.append("🔮 <b>DeepSeek API:</b>")
|
||
lines.append(
|
||
f" • Статус: {'✅ Включен' if ds.get('enabled') else '❌ Отключен'}"
|
||
)
|
||
lines.append(f" • Модель: {ds.get('model', 'N/A')}")
|
||
lines.append(f" • Таймаут: {ds.get('timeout', 'N/A')}с")
|
||
lines.append("")
|
||
|
||
# Если ничего не включено
|
||
if "rag" not in stats and "deepseek" not in stats:
|
||
lines.append("⚠️ Ни один сервис не настроен")
|
||
|
||
await message.answer("\n".join(lines), parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка получения ML статистики: {e}")
|
||
await message.answer(f"❌ Ошибка получения статистики: {str(e)}")
|
||
|
||
|
||
# ============================================================================
|
||
# ХЕНДЛЕРЫ АВТО-МОДЕРАЦИИ
|
||
# ============================================================================
|
||
|
||
|
||
@admin_router.message(
|
||
ChatTypeFilter(chat_type=["private"]),
|
||
StateFilter("ADMIN"),
|
||
F.text == "⚙️ Авто-модерация",
|
||
)
|
||
@track_time("auto_moderation_menu", "admin_handlers")
|
||
@track_errors("admin_handlers", "auto_moderation_menu")
|
||
async def auto_moderation_menu(
|
||
message: types.Message, state: FSMContext, bot_db: MagicData("bot_db")
|
||
):
|
||
"""Меню управления авто-модерацией"""
|
||
try:
|
||
logger.info(
|
||
f"Открытие меню авто-модерации пользователем: {message.from_user.full_name}"
|
||
)
|
||
|
||
settings = await bot_db.get_auto_moderation_settings()
|
||
|
||
text = _format_auto_moderation_status(settings)
|
||
keyboard = get_auto_moderation_keyboard(settings)
|
||
|
||
await message.answer(text, reply_markup=keyboard, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка открытия меню авто-модерации: {e}")
|
||
await message.answer(f"❌ Ошибка: {str(e)}")
|
||
|
||
|
||
def _format_auto_moderation_status(settings: dict) -> str:
|
||
"""Форматирует текст статуса авто-модерации."""
|
||
auto_publish = settings.get("auto_publish_enabled", False)
|
||
auto_decline = settings.get("auto_decline_enabled", False)
|
||
publish_threshold = settings.get("auto_publish_threshold", 0.8)
|
||
decline_threshold = settings.get("auto_decline_threshold", 0.4)
|
||
|
||
publish_status = "✅ Включена" if auto_publish else "❌ Выключена"
|
||
decline_status = "✅ Включено" if auto_decline else "❌ Выключено"
|
||
|
||
return (
|
||
"⚙️ <b>Авто-модерация постов</b>\n\n"
|
||
f"🤖 <b>Авто-публикация:</b> {publish_status}\n"
|
||
f" Порог: RAG score ≥ <b>{publish_threshold}</b>\n\n"
|
||
f"🚫 <b>Авто-отклонение:</b> {decline_status}\n"
|
||
f" Порог: RAG score ≤ <b>{decline_threshold}</b>"
|
||
)
|
||
|
||
|
||
@admin_router.callback_query(F.data == "auto_mod_toggle_publish")
|
||
@track_time("toggle_auto_publish", "admin_handlers")
|
||
@track_errors("admin_handlers", "toggle_auto_publish")
|
||
async def toggle_auto_publish(call: types.CallbackQuery, bot_db: MagicData("bot_db")):
|
||
"""Переключение авто-публикации"""
|
||
try:
|
||
new_state = await bot_db.toggle_auto_publish()
|
||
logger.info(
|
||
f"Авто-публикация {'включена' if new_state else 'выключена'} "
|
||
f"пользователем {call.from_user.full_name}"
|
||
)
|
||
|
||
settings = await bot_db.get_auto_moderation_settings()
|
||
text = _format_auto_moderation_status(settings)
|
||
keyboard = get_auto_moderation_keyboard(settings)
|
||
|
||
await call.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
|
||
await call.answer(
|
||
f"Авто-публикация {'включена ✅' if new_state else 'выключена ❌'}"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка переключения авто-публикации: {e}")
|
||
await call.answer(f"❌ Ошибка: {str(e)}", show_alert=True)
|
||
|
||
|
||
@admin_router.callback_query(F.data == "auto_mod_toggle_decline")
|
||
@track_time("toggle_auto_decline", "admin_handlers")
|
||
@track_errors("admin_handlers", "toggle_auto_decline")
|
||
async def toggle_auto_decline(call: types.CallbackQuery, bot_db: MagicData("bot_db")):
|
||
"""Переключение авто-отклонения"""
|
||
try:
|
||
new_state = await bot_db.toggle_auto_decline()
|
||
logger.info(
|
||
f"Авто-отклонение {'включено' if new_state else 'выключено'} "
|
||
f"пользователем {call.from_user.full_name}"
|
||
)
|
||
|
||
settings = await bot_db.get_auto_moderation_settings()
|
||
text = _format_auto_moderation_status(settings)
|
||
keyboard = get_auto_moderation_keyboard(settings)
|
||
|
||
await call.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
|
||
await call.answer(
|
||
f"Авто-отклонение {'включено ✅' if new_state else 'выключено ❌'}"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка переключения авто-отклонения: {e}")
|
||
await call.answer(f"❌ Ошибка: {str(e)}", show_alert=True)
|
||
|
||
|
||
@admin_router.callback_query(F.data == "auto_mod_refresh")
|
||
@track_time("refresh_auto_moderation", "admin_handlers")
|
||
@track_errors("admin_handlers", "refresh_auto_moderation")
|
||
async def refresh_auto_moderation(
|
||
call: types.CallbackQuery, bot_db: MagicData("bot_db")
|
||
):
|
||
"""Обновление статуса авто-модерации"""
|
||
try:
|
||
settings = await bot_db.get_auto_moderation_settings()
|
||
text = _format_auto_moderation_status(settings)
|
||
keyboard = get_auto_moderation_keyboard(settings)
|
||
|
||
try:
|
||
await call.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
|
||
except Exception as edit_error:
|
||
if "message is not modified" in str(edit_error):
|
||
pass # Сообщение не изменилось - это нормально
|
||
else:
|
||
raise
|
||
await call.answer("🔄 Обновлено")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка обновления статуса авто-модерации: {e}")
|
||
await call.answer(f"❌ Ошибка: {str(e)}", show_alert=True)
|
||
|
||
|
||
@admin_router.callback_query(F.data == "auto_mod_threshold_publish")
|
||
@track_time("change_publish_threshold", "admin_handlers")
|
||
@track_errors("admin_handlers", "change_publish_threshold")
|
||
async def change_publish_threshold(
|
||
call: types.CallbackQuery, state: FSMContext, bot_db: MagicData("bot_db")
|
||
):
|
||
"""Начало изменения порога авто-публикации"""
|
||
try:
|
||
await state.set_state("AWAIT_PUBLISH_THRESHOLD")
|
||
await call.message.answer(
|
||
"📈 <b>Изменение порога авто-публикации</b>\n\n"
|
||
"Введите новое значение порога (от 0.0 до 1.0).\n"
|
||
"Посты с RAG score ≥ этого значения будут автоматически публиковаться.\n\n"
|
||
"Текущее рекомендуемое значение: <b>0.8</b>",
|
||
parse_mode="HTML",
|
||
)
|
||
await call.answer()
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка начала изменения порога публикации: {e}")
|
||
await call.answer(f"❌ Ошибка: {str(e)}", show_alert=True)
|
||
|
||
|
||
@admin_router.callback_query(F.data == "auto_mod_threshold_decline")
|
||
@track_time("change_decline_threshold", "admin_handlers")
|
||
@track_errors("admin_handlers", "change_decline_threshold")
|
||
async def change_decline_threshold(
|
||
call: types.CallbackQuery, state: FSMContext, bot_db: MagicData("bot_db")
|
||
):
|
||
"""Начало изменения порога авто-отклонения"""
|
||
try:
|
||
await state.set_state("AWAIT_DECLINE_THRESHOLD")
|
||
await call.message.answer(
|
||
"📉 <b>Изменение порога авто-отклонения</b>\n\n"
|
||
"Введите новое значение порога (от 0.0 до 1.0).\n"
|
||
"Посты с RAG score ≤ этого значения будут автоматически отклоняться.\n\n"
|
||
"Текущее рекомендуемое значение: <b>0.4</b>",
|
||
parse_mode="HTML",
|
||
)
|
||
await call.answer()
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка начала изменения порога отклонения: {e}")
|
||
await call.answer(f"❌ Ошибка: {str(e)}", show_alert=True)
|
||
|
||
|
||
@admin_router.message(
|
||
ChatTypeFilter(chat_type=["private"]),
|
||
StateFilter("AWAIT_PUBLISH_THRESHOLD"),
|
||
)
|
||
@track_time("process_publish_threshold", "admin_handlers")
|
||
@track_errors("admin_handlers", "process_publish_threshold")
|
||
async def process_publish_threshold(
|
||
message: types.Message, state: FSMContext, bot_db: MagicData("bot_db")
|
||
):
|
||
"""Обработка нового порога авто-публикации"""
|
||
try:
|
||
value = float(message.text.strip().replace(",", "."))
|
||
if not 0.0 <= value <= 1.0:
|
||
raise ValueError("Значение должно быть от 0.0 до 1.0")
|
||
|
||
await bot_db.set_float_setting("auto_publish_threshold", value)
|
||
logger.info(
|
||
f"Порог авто-публикации изменен на {value} "
|
||
f"пользователем {message.from_user.full_name}"
|
||
)
|
||
|
||
await state.set_state("ADMIN")
|
||
await message.answer(
|
||
f"✅ Порог авто-публикации изменен на <b>{value}</b>",
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
settings = await bot_db.get_auto_moderation_settings()
|
||
text = _format_auto_moderation_status(settings)
|
||
keyboard = get_auto_moderation_keyboard(settings)
|
||
await message.answer(text, reply_markup=keyboard, parse_mode="HTML")
|
||
|
||
except ValueError as e:
|
||
await message.answer(
|
||
f"❌ Неверное значение: {e}\n"
|
||
"Введите число от 0.0 до 1.0 (например: 0.8)"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка изменения порога публикации: {e}")
|
||
await state.set_state("ADMIN")
|
||
await message.answer(f"❌ Ошибка: {str(e)}")
|
||
|
||
|
||
@admin_router.message(
|
||
ChatTypeFilter(chat_type=["private"]),
|
||
StateFilter("AWAIT_DECLINE_THRESHOLD"),
|
||
)
|
||
@track_time("process_decline_threshold", "admin_handlers")
|
||
@track_errors("admin_handlers", "process_decline_threshold")
|
||
async def process_decline_threshold(
|
||
message: types.Message, state: FSMContext, bot_db: MagicData("bot_db")
|
||
):
|
||
"""Обработка нового порога авто-отклонения"""
|
||
try:
|
||
value = float(message.text.strip().replace(",", "."))
|
||
if not 0.0 <= value <= 1.0:
|
||
raise ValueError("Значение должно быть от 0.0 до 1.0")
|
||
|
||
await bot_db.set_float_setting("auto_decline_threshold", value)
|
||
logger.info(
|
||
f"Порог авто-отклонения изменен на {value} "
|
||
f"пользователем {message.from_user.full_name}"
|
||
)
|
||
|
||
await state.set_state("ADMIN")
|
||
await message.answer(
|
||
f"✅ Порог авто-отклонения изменен на <b>{value}</b>",
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
settings = await bot_db.get_auto_moderation_settings()
|
||
text = _format_auto_moderation_status(settings)
|
||
keyboard = get_auto_moderation_keyboard(settings)
|
||
await message.answer(text, reply_markup=keyboard, parse_mode="HTML")
|
||
|
||
except ValueError as e:
|
||
await message.answer(
|
||
f"❌ Неверное значение: {e}\n"
|
||
"Введите число от 0.0 до 1.0 (например: 0.4)"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка изменения порога отклонения: {e}")
|
||
await state.set_state("ADMIN")
|
||
await message.answer(f"❌ Ошибка: {str(e)}")
|
||
|
||
|
||
# ============================================================================
|
||
# ХЕНДЛЕРЫ ПРОЦЕССА БАНА
|
||
# ============================================================================
|
||
|
||
|
||
@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")
|