fix quality code
This commit is contained in:
@@ -1,27 +1,37 @@
|
||||
from .admin_handlers import admin_router
|
||||
from .dependencies import AdminAccessMiddleware, BotDB, Settings
|
||||
from .exceptions import (AdminAccessDeniedError, AdminError, InvalidInputError,
|
||||
UserAlreadyBannedError, UserNotFoundError)
|
||||
from .exceptions import (
|
||||
AdminAccessDeniedError,
|
||||
AdminError,
|
||||
InvalidInputError,
|
||||
UserAlreadyBannedError,
|
||||
UserNotFoundError,
|
||||
)
|
||||
from .services import AdminService, BannedUser, User
|
||||
from .utils import (escape_html, format_ban_confirmation, format_user_info,
|
||||
handle_admin_error, return_to_admin_menu)
|
||||
from .utils import (
|
||||
escape_html,
|
||||
format_ban_confirmation,
|
||||
format_user_info,
|
||||
handle_admin_error,
|
||||
return_to_admin_menu,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'admin_router',
|
||||
'AdminAccessMiddleware',
|
||||
'BotDB',
|
||||
'Settings',
|
||||
'AdminService',
|
||||
'User',
|
||||
'BannedUser',
|
||||
'AdminError',
|
||||
'AdminAccessDeniedError',
|
||||
'UserNotFoundError',
|
||||
'InvalidInputError',
|
||||
'UserAlreadyBannedError',
|
||||
'return_to_admin_menu',
|
||||
'handle_admin_error',
|
||||
'format_user_info',
|
||||
'format_ban_confirmation',
|
||||
'escape_html'
|
||||
]
|
||||
"admin_router",
|
||||
"AdminAccessMiddleware",
|
||||
"BotDB",
|
||||
"Settings",
|
||||
"AdminService",
|
||||
"User",
|
||||
"BannedUser",
|
||||
"AdminError",
|
||||
"AdminAccessDeniedError",
|
||||
"UserNotFoundError",
|
||||
"InvalidInputError",
|
||||
"UserAlreadyBannedError",
|
||||
"return_to_admin_menu",
|
||||
"handle_admin_error",
|
||||
"format_user_info",
|
||||
"format_ban_confirmation",
|
||||
"escape_html",
|
||||
]
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
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,
|
||||
)
|
||||
|
||||
# Local imports - metrics
|
||||
from helper_bot.utils.metrics import db_query_time, track_errors, track_time
|
||||
from logs.custom_logger import logger
|
||||
@@ -29,23 +37,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")
|
||||
|
||||
@@ -54,18 +58,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()
|
||||
@@ -78,32 +84,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")
|
||||
@@ -112,27 +117,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,68 +150,80 @@ async def get_banned_users(
|
||||
# ХЕНДЛЕРЫ ПРОЦЕССА БАНА
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@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:
|
||||
@@ -210,115 +231,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")
|
||||
|
||||
@@ -326,35 +349,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)
|
||||
|
||||
@@ -9,7 +9,7 @@ ADMIN_BUTTON_TEXTS: Final[Dict[str, str]] = {
|
||||
"BAN_BY_ID": "Бан по ID",
|
||||
"UNBAN_LIST": "Разбан (список)",
|
||||
"RETURN_TO_BOT": "Вернуться в бота",
|
||||
"CANCEL": "Отменить"
|
||||
"CANCEL": "Отменить",
|
||||
}
|
||||
|
||||
# Admin button to command mapping for metrics
|
||||
@@ -19,11 +19,11 @@ ADMIN_BUTTON_COMMAND_MAPPING: Final[Dict[str, str]] = {
|
||||
"Бан по ID": "admin_ban_by_id",
|
||||
"Разбан (список)": "admin_unban_list",
|
||||
"Вернуться в бота": "admin_return_to_bot",
|
||||
"Отменить": "admin_cancel"
|
||||
"Отменить": "admin_cancel",
|
||||
}
|
||||
|
||||
# Admin commands
|
||||
ADMIN_COMMANDS: Final[Dict[str, str]] = {
|
||||
"ADMIN": "admin",
|
||||
"TEST_METRICS": "test_metrics"
|
||||
"TEST_METRICS": "test_metrics",
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ except ImportError:
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from helper_bot.utils.base_dependency_factory import get_global_instance
|
||||
from helper_bot.utils.helper_func import check_access
|
||||
from logs.custom_logger import logger
|
||||
@@ -14,36 +15,46 @@ from logs.custom_logger import logger
|
||||
|
||||
class AdminAccessMiddleware(BaseMiddleware):
|
||||
"""Middleware для проверки административного доступа"""
|
||||
|
||||
async def __call__(self, handler, event: TelegramObject, data: Dict[str, Any]) -> Any:
|
||||
if hasattr(event, 'from_user'):
|
||||
|
||||
async def __call__(
|
||||
self, handler, event: TelegramObject, data: Dict[str, Any]
|
||||
) -> Any:
|
||||
if hasattr(event, "from_user"):
|
||||
user_id = event.from_user.id
|
||||
username = getattr(event.from_user, 'username', 'Unknown')
|
||||
|
||||
logger.info(f"AdminAccessMiddleware: проверка доступа для пользователя {username} (ID: {user_id})")
|
||||
|
||||
username = getattr(event.from_user, "username", "Unknown")
|
||||
|
||||
logger.info(
|
||||
f"AdminAccessMiddleware: проверка доступа для пользователя {username} (ID: {user_id})"
|
||||
)
|
||||
|
||||
# Получаем bot_db из data (внедренного DependenciesMiddleware)
|
||||
bot_db = data.get('bot_db')
|
||||
bot_db = data.get("bot_db")
|
||||
if not bot_db:
|
||||
# Fallback: получаем напрямую если middleware не сработала
|
||||
bdf = get_global_instance()
|
||||
bot_db = bdf.get_db()
|
||||
|
||||
|
||||
is_admin_result = await check_access(user_id, bot_db)
|
||||
logger.info(f"AdminAccessMiddleware: результат проверки для {username}: {is_admin_result}")
|
||||
|
||||
logger.info(
|
||||
f"AdminAccessMiddleware: результат проверки для {username}: {is_admin_result}"
|
||||
)
|
||||
|
||||
if not is_admin_result:
|
||||
logger.warning(f"AdminAccessMiddleware: доступ запрещен для пользователя {username} (ID: {user_id})")
|
||||
if hasattr(event, 'answer'):
|
||||
await event.answer('Доступ запрещен!')
|
||||
logger.warning(
|
||||
f"AdminAccessMiddleware: доступ запрещен для пользователя {username} (ID: {user_id})"
|
||||
)
|
||||
if hasattr(event, "answer"):
|
||||
await event.answer("Доступ запрещен!")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
# Вызываем хендлер с data
|
||||
return await handler(event, data)
|
||||
except TypeError as e:
|
||||
if "missing 1 required positional argument: 'data'" in str(e):
|
||||
logger.error(f"Ошибка в AdminAccessMiddleware: {e}. Хендлер не принимает параметр 'data'")
|
||||
logger.error(
|
||||
f"Ошибка в AdminAccessMiddleware: {e}. Хендлер не принимает параметр 'data'"
|
||||
)
|
||||
# Пытаемся вызвать хендлер без data (для совместимости с MagicData)
|
||||
return await handler(event)
|
||||
else:
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
class AdminError(Exception):
|
||||
"""Базовое исключение для административных операций"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AdminAccessDeniedError(AdminError):
|
||||
"""Исключение при отказе в административном доступе"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UserNotFoundError(AdminError):
|
||||
"""Исключение при отсутствии пользователя"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class InvalidInputError(AdminError):
|
||||
"""Исключение при некорректном вводе данных"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UserAlreadyBannedError(AdminError):
|
||||
"""Исключение при попытке забанить уже заблокированного пользователя"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
"""
|
||||
Обработчики команд для мониторинга rate limiting
|
||||
"""
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.filters import Command, MagicData
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import FSInputFile
|
||||
|
||||
from helper_bot.filters.main import ChatTypeFilter
|
||||
from helper_bot.middlewares.dependencies_middleware import \
|
||||
DependenciesMiddleware
|
||||
from helper_bot.middlewares.dependencies_middleware import DependenciesMiddleware
|
||||
|
||||
# Local imports - metrics
|
||||
from helper_bot.utils.metrics import track_errors, track_time
|
||||
from helper_bot.utils.rate_limit_metrics import (
|
||||
get_rate_limit_metrics_summary, update_rate_limit_gauges)
|
||||
from helper_bot.utils.rate_limit_monitor import (get_rate_limit_summary,
|
||||
rate_limit_monitor)
|
||||
get_rate_limit_metrics_summary,
|
||||
update_rate_limit_gauges,
|
||||
)
|
||||
from helper_bot.utils.rate_limit_monitor import (
|
||||
get_rate_limit_summary,
|
||||
rate_limit_monitor,
|
||||
)
|
||||
from logs.custom_logger import logger
|
||||
|
||||
|
||||
class RateLimitHandlers:
|
||||
def __init__(self, db, settings):
|
||||
self.db = db.get_db() if hasattr(db, 'get_db') else db
|
||||
self.db = db.get_db() if hasattr(db, "get_db") else db
|
||||
self.settings = settings
|
||||
self.router = Router()
|
||||
self._setup_handlers()
|
||||
@@ -33,38 +39,38 @@ class RateLimitHandlers:
|
||||
self.router.message.register(
|
||||
self.rate_limit_stats_handler,
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
Command("ratelimit_stats")
|
||||
Command("ratelimit_stats"),
|
||||
)
|
||||
|
||||
|
||||
# Команда для сброса статистики rate limiting
|
||||
self.router.message.register(
|
||||
self.reset_rate_limit_stats_handler,
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
Command("reset_ratelimit_stats")
|
||||
Command("reset_ratelimit_stats"),
|
||||
)
|
||||
|
||||
|
||||
# Команда для просмотра ошибок rate limiting
|
||||
self.router.message.register(
|
||||
self.rate_limit_errors_handler,
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
Command("ratelimit_errors")
|
||||
Command("ratelimit_errors"),
|
||||
)
|
||||
|
||||
|
||||
# Команда для просмотра Prometheus метрик
|
||||
self.router.message.register(
|
||||
self.rate_limit_prometheus_handler,
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
Command("ratelimit_prometheus")
|
||||
Command("ratelimit_prometheus"),
|
||||
)
|
||||
|
||||
@track_time("rate_limit_stats_handler", "rate_limit_handlers")
|
||||
@track_errors("rate_limit_handlers", "rate_limit_stats_handler")
|
||||
async def rate_limit_stats_handler(
|
||||
self,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
bot_db: MagicData("bot_db"),
|
||||
settings: MagicData("settings")
|
||||
settings: MagicData("settings"),
|
||||
):
|
||||
"""Показывает статистику rate limiting"""
|
||||
try:
|
||||
@@ -72,11 +78,11 @@ class RateLimitHandlers:
|
||||
if not await bot_db.is_admin(message.from_user.id):
|
||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||
return
|
||||
|
||||
|
||||
# Получаем сводку
|
||||
summary = get_rate_limit_summary()
|
||||
global_stats = rate_limit_monitor.get_global_stats()
|
||||
|
||||
|
||||
# Формируем сообщение со статистикой
|
||||
stats_text = (
|
||||
f"📊 <b>Статистика Rate Limiting</b>\n\n"
|
||||
@@ -89,15 +95,17 @@ class RateLimitHandlers:
|
||||
f"• Активных чатов: {summary['active_chats']}\n"
|
||||
f"• Ошибок за час: {summary['recent_errors_count']}\n\n"
|
||||
)
|
||||
|
||||
|
||||
# Добавляем детальную статистику
|
||||
stats_text += f"🔍 <b>Детальная статистика:</b>\n"
|
||||
stats_text += f"• Успешных запросов: {global_stats.successful_requests}\n"
|
||||
stats_text += f"• Неудачных запросов: {global_stats.failed_requests}\n"
|
||||
stats_text += f"• RetryAfter ошибок: {global_stats.retry_after_errors}\n"
|
||||
stats_text += f"• Других ошибок: {global_stats.other_errors}\n"
|
||||
stats_text += f"• Общее время ожидания: {global_stats.total_wait_time:.2f}с\n\n"
|
||||
|
||||
stats_text += (
|
||||
f"• Общее время ожидания: {global_stats.total_wait_time:.2f}с\n\n"
|
||||
)
|
||||
|
||||
# Добавляем топ чатов по запросам
|
||||
top_chats = rate_limit_monitor.get_top_chats_by_requests(5)
|
||||
if top_chats:
|
||||
@@ -105,16 +113,16 @@ class RateLimitHandlers:
|
||||
for i, (chat_id, chat_stats) in enumerate(top_chats, 1):
|
||||
stats_text += f"{i}. Chat {chat_id}: {chat_stats.total_requests} запросов ({chat_stats.success_rate:.1%} успех)\n"
|
||||
stats_text += "\n"
|
||||
|
||||
|
||||
# Добавляем чаты с высоким процентом ошибок
|
||||
high_error_chats = rate_limit_monitor.get_chats_with_high_error_rate(0.1)
|
||||
if high_error_chats:
|
||||
stats_text += f"⚠️ <b>Чаты с высоким процентом ошибок (>10%):</b>\n"
|
||||
for chat_id, chat_stats in high_error_chats[:3]:
|
||||
stats_text += f"• Chat {chat_id}: {chat_stats.error_rate:.1%} ошибок ({chat_stats.failed_requests}/{chat_stats.total_requests})\n"
|
||||
|
||||
await message.answer(stats_text, parse_mode='HTML')
|
||||
|
||||
|
||||
await message.answer(stats_text, parse_mode="HTML")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении статистики rate limiting: {e}")
|
||||
await message.answer("Произошла ошибка при получении статистики.")
|
||||
@@ -123,10 +131,10 @@ class RateLimitHandlers:
|
||||
@track_errors("rate_limit_handlers", "reset_rate_limit_stats_handler")
|
||||
async def reset_rate_limit_stats_handler(
|
||||
self,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
bot_db: MagicData("bot_db"),
|
||||
settings: MagicData("settings")
|
||||
settings: MagicData("settings"),
|
||||
):
|
||||
"""Сбрасывает статистику rate limiting"""
|
||||
try:
|
||||
@@ -134,12 +142,12 @@ class RateLimitHandlers:
|
||||
if not await bot_db.is_admin(message.from_user.id):
|
||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||
return
|
||||
|
||||
|
||||
# Сбрасываем статистику
|
||||
rate_limit_monitor.reset_stats()
|
||||
|
||||
|
||||
await message.answer("✅ Статистика rate limiting сброшена.")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сбросе статистики rate limiting: {e}")
|
||||
await message.answer("Произошла ошибка при сбросе статистики.")
|
||||
@@ -148,10 +156,10 @@ class RateLimitHandlers:
|
||||
@track_errors("rate_limit_handlers", "rate_limit_errors_handler")
|
||||
async def rate_limit_errors_handler(
|
||||
self,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
bot_db: MagicData("bot_db"),
|
||||
settings: MagicData("settings")
|
||||
settings: MagicData("settings"),
|
||||
):
|
||||
"""Показывает недавние ошибки rate limiting"""
|
||||
try:
|
||||
@@ -159,29 +167,34 @@ class RateLimitHandlers:
|
||||
if not await bot_db.is_admin(message.from_user.id):
|
||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||
return
|
||||
|
||||
|
||||
# Получаем ошибки за последний час
|
||||
recent_errors = rate_limit_monitor.get_recent_errors(60)
|
||||
error_summary = rate_limit_monitor.get_error_summary(60)
|
||||
|
||||
|
||||
if not recent_errors:
|
||||
await message.answer("✅ Ошибок rate limiting за последний час не было.")
|
||||
await message.answer(
|
||||
"✅ Ошибок rate limiting за последний час не было."
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
# Формируем сообщение с ошибками
|
||||
errors_text = f"🚨 <b>Ошибки Rate Limiting (последний час)</b>\n\n"
|
||||
errors_text += f"📊 <b>Сводка ошибок:</b>\n"
|
||||
for error_type, count in error_summary.items():
|
||||
errors_text += f"• {error_type}: {count}\n"
|
||||
errors_text += f"\nВсего ошибок: {len(recent_errors)}\n\n"
|
||||
|
||||
|
||||
# Показываем последние 10 ошибок
|
||||
errors_text += f"🔍 <b>Последние ошибки:</b>\n"
|
||||
for i, error in enumerate(recent_errors[-10:], 1):
|
||||
from datetime import datetime
|
||||
timestamp = datetime.fromtimestamp(error['timestamp']).strftime("%H:%M:%S")
|
||||
|
||||
timestamp = datetime.fromtimestamp(error["timestamp"]).strftime(
|
||||
"%H:%M:%S"
|
||||
)
|
||||
errors_text += f"{i}. {timestamp} - Chat {error['chat_id']} - {error['error_type']}\n"
|
||||
|
||||
|
||||
# Если сообщение слишком длинное, разбиваем на части
|
||||
if len(errors_text) > 4000:
|
||||
# Отправляем сводку
|
||||
@@ -190,32 +203,37 @@ class RateLimitHandlers:
|
||||
for error_type, count in error_summary.items():
|
||||
summary_text += f"• {error_type}: {count}\n"
|
||||
summary_text += f"\nВсего ошибок: {len(recent_errors)}"
|
||||
|
||||
await message.answer(summary_text, parse_mode='HTML')
|
||||
|
||||
|
||||
await message.answer(summary_text, parse_mode="HTML")
|
||||
|
||||
# Отправляем детали отдельным сообщением
|
||||
details_text = f"🔍 <b>Последние ошибки:</b>\n"
|
||||
for i, error in enumerate(recent_errors[-10:], 1):
|
||||
from datetime import datetime
|
||||
timestamp = datetime.fromtimestamp(error['timestamp']).strftime("%H:%M:%S")
|
||||
|
||||
timestamp = datetime.fromtimestamp(error["timestamp"]).strftime(
|
||||
"%H:%M:%S"
|
||||
)
|
||||
details_text += f"{i}. {timestamp} - Chat {error['chat_id']} - {error['error_type']}\n"
|
||||
|
||||
await message.answer(details_text, parse_mode='HTML')
|
||||
|
||||
await message.answer(details_text, parse_mode="HTML")
|
||||
else:
|
||||
await message.answer(errors_text, parse_mode='HTML')
|
||||
|
||||
await message.answer(errors_text, parse_mode="HTML")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении ошибок rate limiting: {e}")
|
||||
await message.answer("Произошла ошибка при получении информации об ошибках.")
|
||||
await message.answer(
|
||||
"Произошла ошибка при получении информации об ошибках."
|
||||
)
|
||||
|
||||
@track_time("rate_limit_prometheus_handler", "rate_limit_handlers")
|
||||
@track_errors("rate_limit_handlers", "rate_limit_prometheus_handler")
|
||||
async def rate_limit_prometheus_handler(
|
||||
self,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
bot_db: MagicData("bot_db"),
|
||||
settings: MagicData("settings")
|
||||
settings: MagicData("settings"),
|
||||
):
|
||||
"""Показывает Prometheus метрики rate limiting"""
|
||||
try:
|
||||
@@ -223,13 +241,13 @@ class RateLimitHandlers:
|
||||
if not await bot_db.is_admin(message.from_user.id):
|
||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||
return
|
||||
|
||||
|
||||
# Обновляем gauge метрики
|
||||
update_rate_limit_gauges()
|
||||
|
||||
|
||||
# Получаем сводку метрик
|
||||
metrics_summary = get_rate_limit_metrics_summary()
|
||||
|
||||
|
||||
# Формируем сообщение с метриками
|
||||
metrics_text = (
|
||||
f"📊 <b>Prometheus метрики Rate Limiting</b>\n\n"
|
||||
@@ -241,30 +259,40 @@ class RateLimitHandlers:
|
||||
f"• rate_limit_avg_wait_time: {metrics_summary['average_wait_time']:.3f}s\n"
|
||||
f"• rate_limit_active_chats: {metrics_summary['active_chats']}\n\n"
|
||||
)
|
||||
|
||||
|
||||
# Добавляем детальные метрики
|
||||
metrics_text += f"🔍 <b>Детальные метрики:</b>\n"
|
||||
metrics_text += f"• Успешных запросов: {metrics_summary['successful_requests']}\n"
|
||||
metrics_text += f"• Неудачных запросов: {metrics_summary['failed_requests']}\n"
|
||||
metrics_text += f"• RetryAfter ошибок: {metrics_summary['retry_after_errors']}\n"
|
||||
metrics_text += (
|
||||
f"• Успешных запросов: {metrics_summary['successful_requests']}\n"
|
||||
)
|
||||
metrics_text += (
|
||||
f"• Неудачных запросов: {metrics_summary['failed_requests']}\n"
|
||||
)
|
||||
metrics_text += (
|
||||
f"• RetryAfter ошибок: {metrics_summary['retry_after_errors']}\n"
|
||||
)
|
||||
metrics_text += f"• Других ошибок: {metrics_summary['other_errors']}\n"
|
||||
metrics_text += f"• Общее время ожидания: {metrics_summary['total_wait_time']:.2f}s\n\n"
|
||||
|
||||
metrics_text += (
|
||||
f"• Общее время ожидания: {metrics_summary['total_wait_time']:.2f}s\n\n"
|
||||
)
|
||||
|
||||
# Добавляем информацию о доступных метриках
|
||||
metrics_text += f"📈 <b>Доступные Prometheus метрики:</b>\n"
|
||||
metrics_text += f"• rate_limit_requests_total - общее количество запросов\n"
|
||||
metrics_text += f"• rate_limit_errors_total - количество ошибок по типам\n"
|
||||
metrics_text += f"• rate_limit_wait_duration_seconds - время ожидания\n"
|
||||
metrics_text += f"• rate_limit_request_interval_seconds - интервалы между запросами\n"
|
||||
metrics_text += (
|
||||
f"• rate_limit_request_interval_seconds - интервалы между запросами\n"
|
||||
)
|
||||
metrics_text += f"• rate_limit_active_chats - количество активных чатов\n"
|
||||
metrics_text += f"• rate_limit_success_rate - процент успеха по чатам\n"
|
||||
metrics_text += f"• rate_limit_requests_per_minute - запросов в минуту\n"
|
||||
metrics_text += f"• rate_limit_total_requests - общее количество запросов\n"
|
||||
metrics_text += f"• rate_limit_total_errors - количество ошибок\n"
|
||||
metrics_text += f"• rate_limit_avg_wait_time - среднее время ожидания\n"
|
||||
|
||||
await message.answer(metrics_text, parse_mode='HTML')
|
||||
|
||||
|
||||
await message.answer(metrics_text, parse_mode="HTML")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении Prometheus метрик: {e}")
|
||||
await message.answer("Произошла ошибка при получении метрик.")
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from helper_bot.handlers.admin.exceptions import (InvalidInputError,
|
||||
UserAlreadyBannedError)
|
||||
from helper_bot.utils.helper_func import (add_days_to_date,
|
||||
get_banned_users_buttons,
|
||||
get_banned_users_list)
|
||||
from helper_bot.handlers.admin.exceptions import (
|
||||
InvalidInputError,
|
||||
UserAlreadyBannedError,
|
||||
)
|
||||
from helper_bot.utils.helper_func import (
|
||||
add_days_to_date,
|
||||
get_banned_users_buttons,
|
||||
get_banned_users_list,
|
||||
)
|
||||
|
||||
# Local imports - metrics
|
||||
from helper_bot.utils.metrics import track_errors, track_time
|
||||
from logs.custom_logger import logger
|
||||
@@ -13,6 +18,7 @@ from logs.custom_logger import logger
|
||||
|
||||
class User:
|
||||
"""Модель пользователя"""
|
||||
|
||||
def __init__(self, user_id: int, username: str, full_name: str):
|
||||
self.user_id = user_id
|
||||
self.username = username
|
||||
@@ -21,7 +27,10 @@ class User:
|
||||
|
||||
class BannedUser:
|
||||
"""Модель заблокированного пользователя"""
|
||||
def __init__(self, user_id: int, username: str, reason: str, unban_date: Optional[datetime]):
|
||||
|
||||
def __init__(
|
||||
self, user_id: int, username: str, reason: str, unban_date: Optional[datetime]
|
||||
):
|
||||
self.user_id = user_id
|
||||
self.username = username
|
||||
self.reason = reason
|
||||
@@ -30,10 +39,10 @@ class BannedUser:
|
||||
|
||||
class AdminService:
|
||||
"""Сервис для административных операций"""
|
||||
|
||||
|
||||
def __init__(self, bot_db):
|
||||
self.bot_db = bot_db
|
||||
|
||||
|
||||
@track_time("get_last_users", "admin_service")
|
||||
@track_errors("admin_service", "get_last_users")
|
||||
async def get_last_users(self) -> List[User]:
|
||||
@@ -41,17 +50,13 @@ class AdminService:
|
||||
try:
|
||||
users_data = await self.bot_db.get_last_users(30)
|
||||
return [
|
||||
User(
|
||||
user_id=user[1],
|
||||
username='Неизвестно',
|
||||
full_name=user[0]
|
||||
)
|
||||
User(user_id=user[1], username="Неизвестно", full_name=user[0])
|
||||
for user in users_data
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка последних пользователей: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@track_time("get_banned_users", "admin_service")
|
||||
@track_errors("admin_service", "get_banned_users")
|
||||
async def get_banned_users(self) -> List[BannedUser]:
|
||||
@@ -65,18 +70,22 @@ class AdminService:
|
||||
username = await self.bot_db.get_username(user_id)
|
||||
full_name = await self.bot_db.get_full_name_by_id(user_id)
|
||||
user_name = username or full_name or f"User_{user_id}"
|
||||
|
||||
banned_users.append(BannedUser(
|
||||
user_id=user_id,
|
||||
username=user_name,
|
||||
reason=reason,
|
||||
unban_date=unban_date
|
||||
))
|
||||
|
||||
banned_users.append(
|
||||
BannedUser(
|
||||
user_id=user_id,
|
||||
username=user_name,
|
||||
reason=reason,
|
||||
unban_date=unban_date,
|
||||
)
|
||||
)
|
||||
return banned_users
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка заблокированных пользователей: {e}")
|
||||
logger.error(
|
||||
f"Ошибка при получении списка заблокированных пользователей: {e}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
@track_time("get_user_by_username", "admin_service")
|
||||
@track_errors("admin_service", "get_user_by_username")
|
||||
async def get_user_by_username(self, username: str) -> Optional[User]:
|
||||
@@ -85,17 +94,15 @@ class AdminService:
|
||||
user_id = await self.bot_db.get_user_id_by_username(username)
|
||||
if not user_id:
|
||||
return None
|
||||
|
||||
|
||||
full_name = await self.bot_db.get_full_name_by_id(user_id)
|
||||
return User(
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
full_name=full_name or 'Неизвестно'
|
||||
user_id=user_id, username=username, full_name=full_name or "Неизвестно"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при поиске пользователя по username {username}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@track_time("get_user_by_id", "admin_service")
|
||||
@track_errors("admin_service", "get_user_by_id")
|
||||
async def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||
@@ -104,39 +111,50 @@ class AdminService:
|
||||
user_info = await self.bot_db.get_user_by_id(user_id)
|
||||
if not user_info:
|
||||
return None
|
||||
|
||||
|
||||
return User(
|
||||
user_id=user_id,
|
||||
username=user_info.username or 'Неизвестно',
|
||||
full_name=user_info.full_name or 'Неизвестно'
|
||||
username=user_info.username or "Неизвестно",
|
||||
full_name=user_info.full_name or "Неизвестно",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при поиске пользователя по ID {user_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@track_time("ban_user", "admin_service")
|
||||
@track_errors("admin_service", "ban_user")
|
||||
async def ban_user(self, user_id: int, username: str, reason: str, ban_days: Optional[int], ban_author_id: int) -> None:
|
||||
async def ban_user(
|
||||
self,
|
||||
user_id: int,
|
||||
username: str,
|
||||
reason: str,
|
||||
ban_days: Optional[int],
|
||||
ban_author_id: int,
|
||||
) -> None:
|
||||
"""Заблокировать пользователя"""
|
||||
try:
|
||||
# Проверяем, не заблокирован ли уже пользователь
|
||||
if await self.bot_db.check_user_in_blacklist(user_id):
|
||||
raise UserAlreadyBannedError(f"Пользователь {user_id} уже заблокирован")
|
||||
|
||||
|
||||
# Рассчитываем дату разблокировки
|
||||
date_to_unban = None
|
||||
if ban_days is not None:
|
||||
date_to_unban = add_days_to_date(ban_days)
|
||||
|
||||
|
||||
# Сохраняем в БД (username больше не передается, так как не используется в новой схеме)
|
||||
await self.bot_db.set_user_blacklist(user_id, None, reason, date_to_unban, ban_author=ban_author_id)
|
||||
|
||||
logger.info(f"Пользователь {user_id} ({username}) заблокирован. Причина: {reason}, срок: {ban_days} дней")
|
||||
|
||||
await self.bot_db.set_user_blacklist(
|
||||
user_id, None, reason, date_to_unban, ban_author=ban_author_id
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Пользователь {user_id} ({username}) заблокирован. Причина: {reason}, срок: {ban_days} дней"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при блокировке пользователя {user_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@track_time("unban_user", "admin_service")
|
||||
@track_errors("admin_service", "unban_user")
|
||||
async def unban_user(self, user_id: int) -> None:
|
||||
@@ -147,7 +165,7 @@ class AdminService:
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при разблокировке пользователя {user_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@track_time("validate_user_input", "admin_service")
|
||||
@track_errors("admin_service", "validate_user_input")
|
||||
async def validate_user_input(self, input_text: str) -> int:
|
||||
@@ -155,11 +173,13 @@ class AdminService:
|
||||
try:
|
||||
user_id = int(input_text.strip())
|
||||
if user_id <= 0:
|
||||
raise InvalidInputError("ID пользователя должен быть положительным числом")
|
||||
raise InvalidInputError(
|
||||
"ID пользователя должен быть положительным числом"
|
||||
)
|
||||
return user_id
|
||||
except ValueError:
|
||||
raise InvalidInputError("ID пользователя должен быть числом")
|
||||
|
||||
|
||||
@track_time("get_banned_users_for_display", "admin_service")
|
||||
@track_errors("admin_service", "get_banned_users_for_display")
|
||||
async def get_banned_users_for_display(self, page: int = 0) -> tuple[str, list]:
|
||||
@@ -170,5 +190,7 @@ class AdminService:
|
||||
buttons_list = await get_banned_users_buttons(self.bot_db)
|
||||
return message_text, buttons_list
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении данных заблокированных пользователей: {e}")
|
||||
logger.error(
|
||||
f"Ошибка при получении данных заблокированных пользователей: {e}"
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Optional
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from helper_bot.handlers.admin.exceptions import AdminError
|
||||
from helper_bot.keyboards.keyboards import get_reply_keyboard_admin
|
||||
from logs.custom_logger import logger
|
||||
@@ -13,33 +14,41 @@ def escape_html(text: str) -> str:
|
||||
return html.escape(str(text)) if text else ""
|
||||
|
||||
|
||||
async def return_to_admin_menu(message: types.Message, state: FSMContext,
|
||||
additional_message: Optional[str] = None) -> None:
|
||||
async def return_to_admin_menu(
|
||||
message: types.Message, state: FSMContext, additional_message: Optional[str] = None
|
||||
) -> None:
|
||||
"""Универсальная функция для возврата в админ-меню"""
|
||||
logger.info(f"return_to_admin_menu: Возврат в админ-меню для пользователя {message.from_user.id}")
|
||||
|
||||
logger.info(
|
||||
f"return_to_admin_menu: Возврат в админ-меню для пользователя {message.from_user.id}"
|
||||
)
|
||||
|
||||
await state.set_data({})
|
||||
await state.set_state("ADMIN")
|
||||
markup = get_reply_keyboard_admin()
|
||||
|
||||
|
||||
if additional_message:
|
||||
logger.info(f"return_to_admin_menu: Отправка дополнительного сообщения: {additional_message}")
|
||||
logger.info(
|
||||
f"return_to_admin_menu: Отправка дополнительного сообщения: {additional_message}"
|
||||
)
|
||||
await message.answer(additional_message)
|
||||
|
||||
await message.answer('Вернулись в меню', reply_markup=markup)
|
||||
logger.info(f"return_to_admin_menu: Пользователь {message.from_user.id} успешно возвращен в админ-меню")
|
||||
|
||||
await message.answer("Вернулись в меню", reply_markup=markup)
|
||||
logger.info(
|
||||
f"return_to_admin_menu: Пользователь {message.from_user.id} успешно возвращен в админ-меню"
|
||||
)
|
||||
|
||||
|
||||
async def handle_admin_error(message: types.Message, error: Exception,
|
||||
state: FSMContext, error_context: str = "") -> None:
|
||||
async def handle_admin_error(
|
||||
message: types.Message, error: Exception, state: FSMContext, error_context: str = ""
|
||||
) -> None:
|
||||
"""Централизованная обработка ошибок административных операций"""
|
||||
logger.error(f"Ошибка в {error_context}: {error}")
|
||||
|
||||
|
||||
if isinstance(error, AdminError):
|
||||
await message.answer(f"Ошибка: {str(error)}")
|
||||
else:
|
||||
await message.answer("Произошла внутренняя ошибка. Попробуйте позже.")
|
||||
|
||||
|
||||
await return_to_admin_menu(message, state)
|
||||
|
||||
|
||||
@@ -47,19 +56,23 @@ def format_user_info(user_id: int, username: str, full_name: str) -> str:
|
||||
"""Форматирование информации о пользователе для отображения"""
|
||||
safe_username = escape_html(username)
|
||||
safe_full_name = escape_html(full_name)
|
||||
|
||||
return (f"<b>Выбран пользователь:</b>\n"
|
||||
f"<b>ID:</b> {user_id}\n"
|
||||
f"<b>Username:</b> {safe_username}\n"
|
||||
f"<b>Имя:</b> {safe_full_name}")
|
||||
|
||||
return (
|
||||
f"<b>Выбран пользователь:</b>\n"
|
||||
f"<b>ID:</b> {user_id}\n"
|
||||
f"<b>Username:</b> {safe_username}\n"
|
||||
f"<b>Имя:</b> {safe_full_name}"
|
||||
)
|
||||
|
||||
|
||||
def format_ban_confirmation(user_id: int, reason: str, ban_days: Optional[int]) -> str:
|
||||
"""Форматирование подтверждения бана"""
|
||||
safe_reason = escape_html(reason)
|
||||
ban_text = "Навсегда" if ban_days is None else f"{ban_days} дней"
|
||||
|
||||
return (f"<b>Необходимо подтверждение:</b>\n"
|
||||
f"<b>Пользователь:</b> {user_id}\n"
|
||||
f"<b>Причина бана:</b> {safe_reason}\n"
|
||||
f"<b>Срок бана:</b> {ban_text}")
|
||||
|
||||
return (
|
||||
f"<b>Необходимо подтверждение:</b>\n"
|
||||
f"<b>Пользователь:</b> {user_id}\n"
|
||||
f"<b>Причина бана:</b> {safe_reason}\n"
|
||||
f"<b>Срок бана:</b> {ban_text}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user