Настройка переменных через /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