style: isort + black

This commit is contained in:
2026-02-02 00:13:33 +03:00
parent 5f66c86d99
commit 561c9074dd
86 changed files with 8459 additions and 5793 deletions

View File

@@ -1,22 +1,30 @@
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.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)
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,
)
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
@@ -30,23 +38,19 @@ admin_router.message.middleware(AdminAccessMiddleware())
# ХЕНДЛЕРЫ МЕНЮ
# ============================================================================
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
Command('admin')
)
@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
):
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)
await message.answer(
"Добро пожаловать в админку. Выбери что хочешь:", reply_markup=markup
)
except Exception as e:
await handle_admin_error(message, e, state, "admin_panel")
@@ -55,18 +59,20 @@ async def admin_panel(
# ХЕНДЛЕР ОТМЕНЫ
# ============================================================================
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"),
F.text == 'Отменить'
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
):
async def cancel_ban_process(message: types.Message, state: FSMContext, **kwargs):
"""Отмена процесса блокировки"""
try:
current_state = await state.get_state()
@@ -79,32 +85,31 @@ async def cancel_ban_process(
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
StateFilter("ADMIN"),
F.text == 'Бан (Список)'
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")
):
message: types.Message, state: FSMContext, bot_db: MagicData("bot_db")
):
"""Получение списка последних пользователей"""
try:
logger.info(f"Получение списка последних пользователей. Пользователь: {message.from_user.full_name}")
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')
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
reply_markup=keyboard,
)
except Exception as e:
await handle_admin_error(message, e, state, "get_last_users")
@@ -113,27 +118,31 @@ async def get_last_users(
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
StateFilter("ADMIN"),
F.text == 'Разбан (список)'
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")
):
message: types.Message, state: FSMContext, bot_db: MagicData("bot_db")
):
"""Получение списка заблокированных пользователей"""
try:
logger.info(f"Получение списка заблокированных пользователей. Пользователь: {message.from_user.full_name}")
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')
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="В списке заблокированных пользователей никого нет")
await message.answer(
text="В списке заблокированных пользователей никого нет"
)
except Exception as e:
await handle_admin_error(message, e, state, "get_banned_users")
@@ -141,85 +150,95 @@ async def get_banned_users(
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
StateFilter("ADMIN"),
F.text == '📊 ML Статистика'
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
):
async def get_ml_stats(message: types.Message, state: FSMContext, **kwargs):
"""Получение статистики ML-скоринга"""
try:
logger.info(f"Запрос ML статистики от пользователя: {message.from_user.full_name}")
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")
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 ''}")
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')}")
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')}")
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:
lines.append(f" • Статус: {'✅ Включен' if rag.get('enabled') else '❌ Отключен'}")
lines.append(
f" • Статус: {'✅ Включен' if rag.get('enabled') else '❌ Отключен'}"
)
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" • Статус: {'✅ Включен' 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)}")
@@ -229,68 +248,80 @@ async def get_ml_stats(
# ХЕНДЛЕРЫ ПРОЦЕССА БАНА
# ============================================================================
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
StateFilter("ADMIN"),
F.text.in_(['Бан по нику', 'Бан по ID'])
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
):
async def start_ban_process(message: types.Message, state: FSMContext, **kwargs):
"""Начало процесса блокировки пользователя"""
try:
ban_type = "username" if message.text == 'Бан по нику' else "id"
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 блокируемого пользователя"
prompt_text = (
"Пришли мне username блокируемого пользователя"
if ban_type == "username"
else "Пришли мне ID блокируемого пользователя"
)
await message.answer(prompt_text)
await state.set_state('AWAIT_BAN_TARGET')
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")
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")
):
message: types.Message, state: FSMContext, bot_db: MagicData("bot_db")
):
"""Обработка введенного username/ID для блокировки"""
logger.info(f"process_ban_target: === НАЧАЛО ОБРАБОТКИ === Получено сообщение от {message.from_user.id}: {message.text}")
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')
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}")
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)}' не найден.")
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}")
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} не найден в базе данных.")
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:
@@ -298,115 +329,117 @@ async def process_ban_target(
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}")
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
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}")
logger.info(
f"process_ban_target: Отправка сообщения с причиной бана, user_info: {user_info}"
)
await message.answer(
text=f"{user_info}\n\nВыбери причину бана из списка или напиши ее в чат",
reply_markup=markup
reply_markup=markup,
)
await state.set_state('AWAIT_BAN_DETAILS')
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")
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
):
async def process_ban_reason(message: types.Message, state: FSMContext, **kwargs):
"""Обработка причины блокировки"""
logger.info(f"process_ban_reason: === НАЧАЛО ОБРАБОТКИ === Получено сообщение от {message.from_user.id}: {message.text}")
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}")
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}")
logger.info(
f"process_ban_reason: Отправка сообщения с выбором срока бана, причина: {safe_reason}"
)
await message.answer(
f"Выбрана причина: {safe_reason}. Выбери срок бана в днях или напиши его в чат",
reply_markup=markup
reply_markup=markup,
)
await state.set_state('AWAIT_BAN_DURATION')
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")
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
):
async def process_ban_duration(message: types.Message, state: FSMContext, **kwargs):
"""Обработка срока блокировки"""
try:
user_data = await state.get_data()
# Определяем срок блокировки
if message.text == 'Навсегда':
if message.text == "Навсегда":
ban_days = None
else:
try:
ban_days = int(message.text)
if ban_days <= 0:
await message.answer("Срок блокировки должен быть положительным числом.")
await message.answer(
"Срок блокировки должен быть положительным числом."
)
return
except ValueError:
await message.answer("Пожалуйста, введите корректное число дней или выберите 'Навсегда'.")
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
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')
await state.set_state("BAN_CONFIRMATION")
except Exception as e:
await handle_admin_error(message, e, state, "process_ban_duration")
@@ -414,35 +447,31 @@ async def process_ban_duration(
@admin_router.message(
ChatTypeFilter(chat_type=["private"]),
StateFilter("BAN_CONFIRMATION"),
F.text == 'Подтвердить'
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
):
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'],
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'])
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)