Админ-панель в боте: кнопка «Админка» и разделы кнопками

- В главном меню у администраторов появляется кнопка «Админка ⚙️»
  (видна только в сообщениях админу; не-админа код всё равно не пускает).
- Админ-панель с кнопками: Статистика, Управление услугами, Рассылка,
  В главное меню — вместо запоминания текстовых команд.
- Доступ строго по config.ADMIN_IDS на сервере.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-06-22 07:40:04 +03:00
parent 40479c8b7a
commit 377188a015
+60 -14
View File
@@ -71,8 +71,8 @@ def is_valid_name(text: str) -> bool:
# КЛАВИАТУРЫ ДЛЯ ИНТЕРФЕЙСА (UX/UI ВКонтакте)
# ==========================================
def get_main_keyboard() -> str:
"""Главное меню бота."""
def get_main_keyboard(is_admin: bool = False) -> str:
"""Главное меню бота. Для админа добавляется кнопка «Админка ⚙️»."""
keyboard = VkKeyboard(one_time=False)
# Кнопка начать опрос (зеленая)
keyboard.add_button("Заполнить заявку 📝", color=VkKeyboardColor.PRIMARY)
@@ -83,6 +83,23 @@ def get_main_keyboard() -> str:
# Кнопки FAQ и оператора
keyboard.add_button("Частые вопросы ❓", color=VkKeyboardColor.SECONDARY)
keyboard.add_button("Позвать оператора 🧑‍💼", color=VkKeyboardColor.NEGATIVE)
# Кнопка админ-панели — только для администраторов (отдельной строкой)
if is_admin:
keyboard.add_line()
keyboard.add_button("Админка ⚙️", color=VkKeyboardColor.PRIMARY)
return keyboard.get_keyboard()
def get_admin_keyboard() -> str:
"""Клавиатура админ-панели с разделами."""
keyboard = VkKeyboard(one_time=False)
keyboard.add_button("Статистика 📊", color=VkKeyboardColor.SECONDARY)
keyboard.add_line()
keyboard.add_button("Управление услугами ⚙️", color=VkKeyboardColor.SECONDARY)
keyboard.add_line()
keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY)
keyboard.add_line()
keyboard.add_button("В главное меню ↩️", color=VkKeyboardColor.PRIMARY)
return keyboard.get_keyboard()
@@ -343,6 +360,22 @@ class VKBotApplication:
self._msg_times[user_id] = times
return len(times) > FLOOD_MAX_MSGS
def _is_admin(self, user_id: int) -> bool:
"""Является ли пользователь администратором (по config.ADMIN_IDS)."""
return hasattr(config, "ADMIN_IDS") and user_id in config.ADMIN_IDS
def _send_admin_panel(self, user_id: int):
"""Главный экран админ-панели с кнопками разделов."""
self.send_message(
user_id,
"⚙️ Админ-панель ИКП\n\n"
"Выберите раздел на кнопках ниже:\n"
"• 📊 Статистика — заявки, отклики, диалоги\n"
"• ⚙️ Управление услугами — добавить/изменить/удалить\n"
"• 📣 Рассылка — сообщение подписчикам",
keyboard=get_admin_keyboard()
)
def _notify_allowed(self, user_id: int) -> bool:
"""Антиспам: не чаще раза в NOTIFY_COOLDOWN_SEC уведомляем оператора об одном пользователе."""
now = time.time()
@@ -377,11 +410,24 @@ class VKBotApplication:
user_data = db.get_user_data(user_id)
state = user_data["state"]
# Глобальная обработка админ-команд (доступна только пользователям из ADMIN_IDS)
if hasattr(config, "ADMIN_IDS") and user_id in config.ADMIN_IDS:
if text_lower in ["админка", "/admin", "услуги"]:
# Глобальная обработка админ-команд и кнопок (только для ADMIN_IDS)
if self._is_admin(user_id):
if text_lower in ["админка", "админка ⚙️", "/admin"]:
self._send_admin_panel(user_id)
return
elif text_lower in ["статистика 📊", "стата", "статистика", "/stats"]:
self._handle_admin_stats(user_id)
return
elif text_lower in ["управление услугами ⚙️", "управление услугами", "услуги"]:
self._send_admin_menu(user_id)
return
elif text_lower in ["рассылка 📣", "рассылка"]:
# Кнопка без текста — показываем инструкцию
self._handle_admin_broadcast(user_id, "рассылка")
return
elif text_lower.startswith("рассылка"):
self._handle_admin_broadcast(user_id, text_clean)
return
elif text_lower.startswith("ред "):
self._handle_admin_edit(user_id, text_clean)
return
@@ -391,11 +437,11 @@ class VKBotApplication:
elif text_lower.startswith("удал "):
self._handle_admin_delete(user_id, text_clean)
return
elif text_lower in ["стата", "статистика", "/stats"]:
self._handle_admin_stats(user_id)
return
elif text_lower.startswith("рассылка"):
self._handle_admin_broadcast(user_id, text_clean)
elif text_lower in ["в главное меню ↩️"]:
self.send_message(
user_id, "🏠 Главное меню.",
keyboard=get_main_keyboard(is_admin=True)
)
return
# Глобальная отписка от рассылки (доступна в любом состоянии)
@@ -542,11 +588,11 @@ class VKBotApplication:
return
# 3. Возврат в главное меню
if text_lower in ["назад в меню ↩️", "меню", "назад"]:
if text_lower in ["назад в меню ↩️", "меню", "назад", "в главное меню ↩️"]:
self.send_message(
user_id,
"🏠 Вы вернулись в главное меню. Выберите интересующее действие:",
keyboard=get_main_keyboard()
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
)
return
@@ -576,7 +622,7 @@ class VKBotApplication:
"Я помогу быстро оформить заявку на тренировки, рассказать об услугах или связать вас с командой. "
"Также у нас открыт набор игроков и сотрудников — загляните в раздел «Карьера в ИКП 💼».\n\n"
"Выберите действие на кнопках ниже 👇",
keyboard=get_main_keyboard(),
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id)),
attachment=self.get_logo_attachment(user_id)
)
return
@@ -586,7 +632,7 @@ class VKBotApplication:
user_id,
"🤔 Я пока только учусь и не смог полностью распознать ваш запрос.\n\n"
"Пожалуйста, воспользуйтесь кнопками меню ниже или напишите 'оператор', чтобы я пригласил специалиста.",
keyboard=get_main_keyboard()
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
)
def _send_returning_prompt(self, user_id: int, user_data: Dict[str, Any]):