Files
PrimoGuardBot-/bot/handlers/commands/users/admins.py

435 lines
17 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Обработчики команд управления администраторами
"""
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot.filters.admin import IsSuperAdmin
from configs import settings, COMMANDS
from database import get_manager
from middleware.loggers import logger
from bot.utils.decorators import log_action
__all__ = ("router",)
router: Router = Router(name="admin_management_router")
# ================= ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ =================
def parse_user_id(text: str, command: str) -> tuple[bool, str | int]:
"""
Парсит ID пользователя из команды.
Args:
text: Полный текст сообщения
command: Название команды
Returns:
(success, result): result это либо user_id (int), либо текст ошибки (str)
"""
parts = text.split(maxsplit=1)
if len(parts) < 2:
return False, f"❌ Использование: <code>/{command} <ID></code>"
user_id_str = parts[1].strip()
# Валидация ID
try:
user_id = int(user_id_str)
if user_id <= 0:
return False, "❌ ID должен быть положительным числом"
if user_id > 9999999999: # Максимальный Telegram ID
return False, "❌ Некорректный ID пользователя"
return True, user_id
except ValueError:
return False, "❌ ID должен быть числом"
def format_admin_info(user_id: int, username: str | None = None) -> str:
"""Форматирует информацию об админе"""
if username:
return f"<code>{user_id}</code> (@{username})"
return f"<code>{user_id}</code>"
def get_refresh_admins_kb():
"""Клавиатура для обновления списка админов"""
ikb = InlineKeyboardBuilder()
ikb.button(text="🔄 Обновить", callback_data="listadmins:refresh")
ikb.button(text=" Добавить", callback_data="admin:help_add")
ikb.adjust(2)
return ikb.as_markup()
# ================= ДОБАВЛЕНИЕ АДМИНИСТРАТОРА =================
@router.message(Command(*COMMANDS.get("addadmin", ["addadmin"]), prefix=settings.PREFIX, ignore_case=True),
IsSuperAdmin())
@log_action(action_name="ADD_ADMIN", log_args=True)
async def add_admin_cmd(message: Message) -> None:
"""
Добавляет нового администратора бота.
Доступно только владельцам бота (OWNER_ID).
Использование: /addadmin <ID>
Пример: /addadmin 123456789
"""
success, result = parse_user_id(message.text, "addadmin")
if not success:
await message.answer(result, parse_mode="HTML")
return
user_id = result
# Проверка: нельзя добавить самого себя
if user_id == message.from_user.id:
await message.answer(
"⚠️ <b>Вы уже владелец бота</b>\n\n"
"Вам не нужно добавлять себя в администраторы",
parse_mode="HTML"
)
return
# Проверка: нельзя добавить другого владельца
if user_id in settings.OWNER_ID:
await message.answer(
"⚠️ <b>Этот пользователь уже владелец бота</b>\n\n"
"Владельцы имеют полные права автоматически",
parse_mode="HTML"
)
return
manager = get_manager()
try:
# Проверяем, уже админ ли
is_already_admin = await manager.is_admin(user_id)
if is_already_admin:
await message.answer(
f"⚠️ Пользователь {format_admin_info(user_id)} уже является администратором",
parse_mode="HTML"
)
return
# Добавляем администратора
added = await manager.add_admin(
user_id=user_id,
added_by=message.from_user.id
)
if added:
text = (
f"✅ <b>Администратор добавлен</b>\n\n"
f"👤 ID: {format_admin_info(user_id)}\n"
f"👑 Добавил: {format_admin_info(message.from_user.id, message.from_user.username)}\n\n"
f"📋 Права администратора:\n"
f"├─ Управление банвордами\n"
f"├─ Просмотр статистики\n"
f"├─ Активация режимов модерации\n"
f"└─ Все команды бота\n\n"
f"⚠️ <i>Не может управлять другими админами</i>\n"
f"Список админов: /listadmins"
)
logger.info(
f"Администратор добавлен: {user_id} (добавил: {message.from_user.id})",
log_type="ADMIN_MGMT"
)
else:
text = "❌ <b>Ошибка добавления администратора</b>\n\nПопробуйте позже"
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления администратора: {e}", log_type="ADMIN_MGMT")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
# ================= УДАЛЕНИЕ АДМИНИСТРАТОРА =================
@router.message(Command(*COMMANDS.get("remadmin", ["remadmin"]), prefix=settings.PREFIX, ignore_case=True),
IsSuperAdmin())
@log_action(action_name="REMOVE_ADMIN", log_args=True)
async def remove_admin_cmd(message: Message) -> None:
"""
Удаляет администратора бота.
Доступно только владельцам бота (OWNER_ID).
Использование: /remadmin <ID>
Пример: /remadmin 123456789
"""
success, result = parse_user_id(message.text, "remadmin")
if not success:
await message.answer(result, parse_mode="HTML")
return
user_id = result
# Проверка: нельзя удалить владельца
if user_id in settings.OWNER_ID:
await message.answer(
"⚠️ <b>Нельзя удалить владельца</b>\n\n"
"Владельцы имеют права постоянно",
parse_mode="HTML"
)
return
# Проверка: нельзя удалить самого себя (если вы владелец)
if user_id == message.from_user.id:
await message.answer(
"⚠️ <b>Нельзя удалить самого себя</b>",
parse_mode="HTML"
)
return
manager = get_manager()
try:
# Проверяем, является ли администратором
is_admin = await manager.is_admin(user_id)
if not is_admin:
await message.answer(
f"⚠️ Пользователь {format_admin_info(user_id)} не является администратором",
parse_mode="HTML"
)
return
# Удаляем администратора
removed = await manager.remove_admin(user_id=user_id)
if removed:
text = (
f"🗑 <b>Администратор удалён</b>\n\n"
f"👤 ID: {format_admin_info(user_id)}\n"
f"👑 Удалил: {format_admin_info(message.from_user.id, message.from_user.username)}\n\n"
f"⚠️ <i>Пользователь больше не имеет доступа к командам бота</i>"
)
logger.info(
f"Администратор удалён: {user_id} (удалил: {message.from_user.id})",
log_type="ADMIN_MGMT"
)
else:
text = "❌ <b>Ошибка удаления администратора</b>\n\nПопробуйте позже"
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления администратора: {e}", log_type="ADMIN_MGMT")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
# ================= СПИСОК АДМИНИСТРАТОРОВ =================
@router.callback_query(F.data == "listadmins:refresh")
@router.message(Command(*COMMANDS.get("listadmins", ["listadmins"]), prefix=settings.PREFIX, ignore_case=True),
IsSuperAdmin())
@log_action(action_name="LIST_ADMINS")
async def list_admins_cmd(update: Message | CallbackQuery) -> None:
"""
Показывает список всех администраторов бота.
Доступно только владельцам бота (OWNER_ID).
Использование: /listadmins
"""
# Определяем тип update
if isinstance(update, CallbackQuery):
message = update.message
is_callback = True
else:
message = update
is_callback = False
manager = get_manager()
try:
# Получаем всех админов из БД
db_admins = await manager.repo.get_admins()
# Получаем статистику
stats = await manager.get_stats()
# === ФОРМИРУЕМ ВЫВОД ===
output = "👥 <b>СПИСОК АДМИНИСТРАТОРОВ</b>\n\n"
# Владельцы (OWNER_ID)
output += "👑 <b>Владельцы бота</b> (полные права):\n"
for owner_id in settings.OWNER_ID:
output += f'├─ <a href="tg://user?id={owner_id}">{owner_id}</a>\n'
output += "\n"
# Администраторы из БД
if db_admins:
output += f"⚙️ <b>Администраторы</b> ({len(db_admins)}):\n"
for admin_id in sorted(db_admins):
output += f'├─ <a href="tg://user?id={admin_id}">{admin_id}</a>\n'
output += "\n"
output += "📋 <b>Права администраторов:</b>\n"
output += "├─ Управление банвордами\n"
output += "├─ Просмотр статистики\n"
output += "├─ Активация режимов модерации\n"
output += "└─ Все команды бота (кроме управления админами)\n\n"
else:
output += "⚙️ <b>Администраторы:</b>\n"
output += "└─ <i>Нет дополнительных администраторов</i>\n\n"
# Общая статистика
total_admins = len(settings.OWNER_ID) + len(db_admins)
output += f"📊 <b>Итого:</b> {total_admins} администратор(ов)\n\n"
# Команды управления
output += "🔧 <b>Управление:</b>\n"
output += "• /addadmin <code>ID</code> — добавить админа\n"
output += "• /remadmin <code>ID</code> — удалить админа\n\n"
output += "💡 <i>Только владельцы могут управлять администраторами</i>"
# Клавиатура
keyboard = get_refresh_admins_kb()
# Отправка
if is_callback:
await message.edit_text(
text=output,
parse_mode="HTML",
reply_markup=keyboard
)
await update.answer("✅ Список обновлён")
else:
await message.answer(
text=output,
parse_mode="HTML",
reply_markup=keyboard
)
except Exception as e:
logger.error(f"Ошибка получения списка администраторов: {e}", log_type="ADMIN_MGMT")
error_text = "❌ <b>Ошибка загрузки списка</b>\n\nПопробуйте позже"
if is_callback:
await update.answer("❌ Ошибка загрузки", show_alert=True)
else:
await message.answer(error_text, parse_mode="HTML")
# ================= ВСПОМОГАТЕЛЬНЫЕ CALLBACK =================
@router.callback_query(F.data == "admin:help_add")
async def admin_help_add_callback(callback: CallbackQuery) -> None:
"""Показывает помощь по добавлению админа"""
text = (
" <b>Как добавить администратора?</b>\n\n"
"1⃣ Узнайте Telegram ID пользователя\n"
" • Используйте бота @userinfobot\n"
" • Или попросите пользователя написать /start\n\n"
"2⃣ Выполните команду:\n"
" <code>/addadmin ID</code>\n\n"
"Пример:\n"
"<code>/addadmin 123456789</code>"
)
await callback.answer()
await callback.message.answer(text, parse_mode="HTML")
@router.message(Command(*COMMANDS.get("adminhelp", ["adminhelp"]), prefix=settings.PREFIX, ignore_case=True),
IsSuperAdmin())
async def admin_help_cmd(message: Message) -> None:
"""
Показывает подробную справку по управлению администраторами.
Использование: /adminhelp
"""
text = (
"👥 <b>УПРАВЛЕНИЕ АДМИНИСТРАТОРАМИ</b>\n\n"
"🔐 <b>Уровни доступа:</b>\n\n"
"👑 <b>Владельцы</b> (OWNER_ID):\n"
"├─ Все права администратора\n"
"├─ Управление другими админами\n"
"└─ Указываются в конфигурации\n\n"
"⚙️ <b>Администраторы:</b>\n"
"├─ Управление банвордами\n"
"├─ Просмотр статистики\n"
"├─ Активация режимов модерации\n"
"└─ НЕ могут управлять админами\n\n"
"📝 <b>Команды:</b>\n"
"• /listadmins — список всех админов\n"
"• /addadmin <code>ID</code> — добавить админа\n"
"• /remadmin <code>ID</code> — удалить админа\n\n"
"💡 <b>Как узнать ID пользователя?</b>\n"
"• Используйте бота @userinfobot\n"
"• Попросите пользователя написать боту\n"
"• ID отображается в логах бота\n\n"
"⚠️ <b>Важно:</b>\n"
"├─ Нельзя удалить владельца\n"
"├─ Нельзя удалить самого себя\n"
"└─ Все действия логируются"
)
await message.answer(text, parse_mode="HTML")
@router.message(Command(*COMMANDS.get("checkadmin", ["checkadmin"]), prefix=settings.PREFIX, ignore_case=True),
IsSuperAdmin())
@log_action(action_name="CHECK_ADMIN")
async def check_admin_cmd(message: Message) -> None:
"""
Проверяет, является ли пользователь администратором.
Использование: /checkadmin <ID>
"""
success, result = parse_user_id(message.text, "checkadmin")
if not success:
await message.answer(result, parse_mode="HTML")
return
user_id = result
manager = get_manager()
try:
# Проверяем статус
is_owner = user_id in settings.OWNER_ID
is_db_admin = await manager.is_admin(user_id)
text = f"🔍 <b>Проверка пользователя</b>\n\n"
text += f"👤 ID: <code>{user_id}</code>\n\n"
if is_owner:
text += "👑 Статус: <b>Владелец бота</b>\n"
text += "✅ Полные права администратора\n"
text += "✅ Может управлять админами"
elif is_db_admin:
text += "⚙️ Статус: <b>Администратор</b>\n"
text += "✅ Доступ к командам бота\n"
text += "Не может управлять админами"
else:
text += "👤 Статус: <b>Обычный пользователь</b>\n"
text += "❌ Нет прав администратора\n\n"
text += f"Добавить в админы: <code>/addadmin {user_id}</code>"
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка проверки администратора: {e}", log_type="ADMIN_MGMT")
await message.answer("❌ <b>Ошибка проверки</b>", parse_mode="HTML")