Настройка переменных через /settings
This commit is contained in:
330
bot/handlers/commands/users/bot_settings.py
Normal file
330
bot/handlers/commands/users/bot_settings.py
Normal file
@@ -0,0 +1,330 @@
|
||||
"""
|
||||
Команда /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("✅ Настройки отменены")
|
||||
Reference in New Issue
Block a user