331 lines
13 KiB
Python
331 lines
13 KiB
Python
"""
|
||
Команда /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("✅ Настройки отменены")
|