Files
PrimoGuardBot-/bot/handlers/commands/users/bot_settings.py
2026-02-20 03:12:47 +07:00

331 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Команда /settings - управление настройками БЕЗ .env
ADMIN_CHAT_ID, ADMIN_THREAD_ID, REPORT_CHAT_ID, REPORT_THREAD_ID
"""
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.filters import Command
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.exceptions import TelegramBadRequest
from middleware.loggers import logger
from bot.filters.admin import IsAdmin
from database import get_manager
__all__ = ("router",)
router: Router = Router(name="bot_settings_router")
# ======================================================================
# FSM STATES
# ======================================================================
class BotSettingsStates(StatesGroup):
"""Состояния для редактирования настроек бота"""
waiting_admin_chat = State()
waiting_admin_thread = State()
waiting_report_chat = State()
waiting_report_thread = State()
# ======================================================================
# MAIN MENU
# ======================================================================
def _format_chat_id(chat_id: str | None) -> str:
"""Форматирует ID чата для отображения"""
if chat_id is None:
return "Не установлен"
return f"✅ <code>{chat_id}</code>"
def create_settings_menu() -> InlineKeyboardBuilder:
"""Главное меню настроек"""
ikb = InlineKeyboardBuilder()
ikb.button(text="📢 Админ-чат", callback_data="settings:admin_chat")
ikb.button(text="🧵 Топик админ-чата", callback_data="settings:admin_thread")
ikb.button(text="📊 Чат репортов", callback_data="settings:report_chat")
ikb.button(text="🧵 Топик репортов", callback_data="settings:report_thread")
ikb.button(text="🔄 Обновить", callback_data="settings:refresh")
ikb.button(text="❌ Закрыть", callback_data="settings:close")
ikb.adjust(2)
return ikb
def cancel_keyboard():
"""Клавиатура с кнопкой 'Назад' для окон ввода"""
ikb = InlineKeyboardBuilder()
ikb.button(text="◀️ Назад", callback_data="settings:cancel")
return ikb.as_markup()
# ======================================================================
# MAIN HANDLER
# ======================================================================
@router.message(Command("settings"), IsAdmin())
async def settings_cmd(message: Message, state: FSMContext) -> None:
"""Главная команда /settings"""
await state.clear()
await show_settings_menu(message)
async def show_settings_menu(message_or_callback: Message | CallbackQuery) -> None:
"""Показывает меню настроек (отправляет новое сообщение или редактирует существующее)"""
manager = get_manager()
current = await manager.get_bot_settings()
text = (
"⚙️ <b>НАСТРОЙКИ БОТА</b>\n\n"
"📢 <b>Админ-чат:</b> " + _format_chat_id(current.get('admin_chat_id')) + "\n"
"🧵 <b>Топик админ:</b> " + _format_chat_id(current.get('admin_thread_id')) + "\n\n"
"📊 <b>Чат репортов:</b> " + _format_chat_id(current.get('report_chat_id')) + "\n"
"🧵 <b>Топик репортов:</b> " + _format_chat_id(current.get('report_thread_id')) + "\n\n"
"💡 Используйте @userinfobot для получения ID чатов\n"
"💡 Для топиков: ID из сообщения в топике"
)
markup = create_settings_menu().as_markup()
if isinstance(message_or_callback, Message):
await message_or_callback.answer(text, reply_markup=markup, parse_mode="HTML")
else:
try:
await message_or_callback.message.edit_text(text, reply_markup=markup, parse_mode="HTML")
except TelegramBadRequest as e:
if "message is not modified" in str(e):
await message_or_callback.answer("🔄 Нет изменений")
else:
raise
# ======================================================================
# CALLBACK HANDLERS
# ======================================================================
@router.callback_query(F.data == "settings:refresh")
async def refresh_settings(callback: CallbackQuery, state: FSMContext) -> None:
"""Обновляет меню (с защитой от MessageNotModified)"""
await show_settings_menu(callback)
@router.callback_query(F.data == "settings:close")
async def close_settings(callback: CallbackQuery, state: FSMContext) -> None:
"""Закрывает меню"""
await state.clear()
try:
await callback.message.delete()
except:
pass
await callback.answer("❌ Закрыто")
@router.callback_query(F.data == "settings:cancel")
async def cancel_edit(callback: CallbackQuery, state: FSMContext) -> None:
"""Возврат в главное меню без сохранения"""
await state.clear()
await show_settings_menu(callback)
@router.callback_query(F.data == "settings:admin_chat")
async def edit_admin_chat(callback: CallbackQuery, state: FSMContext) -> None:
"""Редактирование админ-чата"""
await state.set_state(BotSettingsStates.waiting_admin_chat)
await callback.message.edit_text(
"📢 <b>АДМИН-ЧАТ</b>\n\n"
"Отправьте ID чата для уведомлений:\n"
"<code>Пример: -1003764219200</code>\n\n"
"Для отключения: <code>null</code>\n\n"
"Или нажмите кнопку ниже для возврата в меню.",
parse_mode="HTML",
reply_markup=cancel_keyboard()
)
await callback.answer()
@router.callback_query(F.data == "settings:admin_thread")
async def edit_admin_thread(callback: CallbackQuery, state: FSMContext) -> None:
"""Редактирование топика админ-чата"""
await state.set_state(BotSettingsStates.waiting_admin_thread)
await callback.message.edit_text(
"🧵 <b>ТОПИК АДМИН-ЧАТА</b>\n\n"
"Отправьте ID топика:\n"
"<code>Пример: 1</code>\n\n"
"Для отключения: <code>null</code>\n\n"
"Или нажмите кнопку ниже для возврата в меню.",
parse_mode="HTML",
reply_markup=cancel_keyboard()
)
await callback.answer()
@router.callback_query(F.data == "settings:report_chat")
async def edit_report_chat(callback: CallbackQuery, state: FSMContext) -> None:
"""Редактирование чата репортов"""
await state.set_state(BotSettingsStates.waiting_report_chat)
await callback.message.edit_text(
"📊 <b>ЧАТ РЕПОРТОВ</b>\n\n"
"Отправьте ID чата для репортов:\n"
"<code>Пример: -1003764219200</code>\n\n"
"Для отключения: <code>null</code>\n\n"
"Или нажмите кнопку ниже для возврата в меню.",
parse_mode="HTML",
reply_markup=cancel_keyboard()
)
await callback.answer()
@router.callback_query(F.data == "settings:report_thread")
async def edit_report_thread(callback: CallbackQuery, state: FSMContext) -> None:
"""Редактирование топика репортов"""
await state.set_state(BotSettingsStates.waiting_report_thread)
await callback.message.edit_text(
"🧵 <b>ТОПИК РЕПОРТОВ</b>\n\n"
"Отправьте ID топика:\n"
"<code>Пример: 1</code>\n\n"
"Для отключения: <code>null</code>\n\n"
"Или нажмите кнопку ниже для возврата в меню.",
parse_mode="HTML",
reply_markup=cancel_keyboard()
)
await callback.answer()
# ======================================================================
# MESSAGE HANDLERS (FSM)
# ======================================================================
@router.message(BotSettingsStates.waiting_admin_chat, IsAdmin())
async def process_admin_chat(message: Message, state: FSMContext) -> None:
text = message.text.strip()
if text == "/cancel":
await state.clear()
await message.answer("❌ Отменено")
return
if text == "null":
value = None
else:
try:
value = int(text)
if not str(value).startswith('-'):
raise ValueError("ID чата должен начинаться с минуса")
except ValueError:
await message.answer("❌ Неверный формат. Пример: <code>-1003764219200</code>", parse_mode="HTML")
return
manager = get_manager()
success = await manager.set_bot_setting("admin_chat_id", str(value) if value else None)
await state.clear()
if success:
# Показываем обновлённое главное меню
await show_settings_menu(message)
# Удаляем сообщение с вводом
try:
await message.delete()
except:
pass
else:
await message.answer("❌ Ошибка сохранения", parse_mode="HTML")
@router.message(BotSettingsStates.waiting_admin_thread, IsAdmin())
async def process_admin_thread(message: Message, state: FSMContext) -> None:
text = message.text.strip()
if text == "/cancel":
await state.clear()
await message.answer("❌ Отменено")
return
if text == "null":
value = None
else:
try:
value = int(text)
if value < 1:
raise ValueError("ID топика должен быть > 0")
except ValueError:
await message.answer("❌ Неверный формат. Пример: <code>1</code>", parse_mode="HTML")
return
manager = get_manager()
success = await manager.set_bot_setting("admin_thread_id", str(value) if value else None)
await state.clear()
if success:
await show_settings_menu(message)
try:
await message.delete()
except:
pass
else:
await message.answer("❌ Ошибка сохранения", parse_mode="HTML")
@router.message(BotSettingsStates.waiting_report_chat, IsAdmin())
async def process_report_chat(message: Message, state: FSMContext) -> None:
text = message.text.strip()
if text == "/cancel":
await state.clear()
await message.answer("❌ Отменено")
return
if text == "null":
value = None
else:
try:
value = int(text)
if not str(value).startswith('-'):
raise ValueError("ID чата должен начинаться с минуса")
except ValueError:
await message.answer("❌ Неверный формат. Пример: <code>-1003764219200</code>", parse_mode="HTML")
return
manager = get_manager()
success = await manager.set_bot_setting("report_chat_id", str(value) if value else None)
await state.clear()
if success:
await show_settings_menu(message)
try:
await message.delete()
except:
pass
else:
await message.answer("❌ Ошибка сохранения", parse_mode="HTML")
@router.message(BotSettingsStates.waiting_report_thread, IsAdmin())
async def process_report_thread(message: Message, state: FSMContext) -> None:
text = message.text.strip()
if text == "/cancel":
await state.clear()
await message.answer("❌ Отменено")
return
if text == "null":
value = None
else:
try:
value = int(text)
if value < 1:
raise ValueError("ID топика должен быть > 0")
except ValueError:
await message.answer("❌ Неверный формат. Пример: <code>1</code>", parse_mode="HTML")
return
manager = get_manager()
success = await manager.set_bot_setting("report_thread_id", str(value) if value else None)
await state.clear()
if success:
await show_settings_menu(message)
try:
await message.delete()
except:
pass
else:
await message.answer("❌ Ошибка сохранения", parse_mode="HTML")
@router.message(Command("cancel"))
async def cancel_settings(message: Message, state: FSMContext) -> None:
"""Глобальный cancel"""
await state.clear()
await message.answer("✅ Настройки отменены")