60199fb47b
Меню Кибершколы БАЗА (4 кнопки): Подобрать тренировку / Летний интенсив CS2 / Посмотреть цены / Задать вопрос. Воронка «Подобрать тренировку» (FSM BAZA_*): для кого → возраст → игра → уровень → формат → групповые / индивидуальные / нужен совет. На каждом шаге — «Назад» и «В главное меню», ветки: младше 12 и «другая игра» уводят к менеджеру. Ветка «Летний интенсив CS2»: инфо → возраст → уровень → оплата. Запуск по кнопке и по кодовым словам (лето/интенсив), основные триггеры и приветствие — из config. Шаг оплаты: инлайн-кнопка «Перейти к оплате» (ссылки-плейсхолдеры из config.LINKS + UTM), «Проблема с оплатой» и «Задать вопрос менеджеру» уводят к оператору. Карточка менеджеру собирается из профиля воронки (пустые поля пропускаются) + метки; дублируется на почту. Данные не переспрашиваются, после передачи менеджеру бот не вмешивается (OPERATOR_CHAT). Тесты воронки — pytest (30 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
383 lines
18 KiB
Python
383 lines
18 KiB
Python
"""Клавиатуры интерфейса бота (VK)."""
|
||
from vk_api.keyboard import VkKeyboard, VkKeyboardColor
|
||
import config
|
||
|
||
|
||
# ==========================================
|
||
# КЛАВИАТУРЫ ДЛЯ ИНТЕРФЕЙСА (UX/UI ВКонтакте)
|
||
# ==========================================
|
||
|
||
def get_main_keyboard(is_admin: bool = False) -> str:
|
||
"""Главное меню Кибершколы БАЗА. Для админа добавляется кнопка «Админка ⚙️»."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Подобрать тренировку 🎯", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Летний интенсив CS2 🔥", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Посмотреть цены 💰", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Задать вопрос 💬", color=VkKeyboardColor.SECONDARY)
|
||
if is_admin:
|
||
keyboard.add_line()
|
||
keyboard.add_button("Админка ⚙️", color=VkKeyboardColor.POSITIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
# ==========================================
|
||
# КЛАВИАТУРЫ ВОРОНКИ «ПОДОБРАТЬ ТРЕНИРОВКУ» (БАЗА)
|
||
# ==========================================
|
||
|
||
def _kb(rows, back=False, ask_manager=False, back_menu=True):
|
||
"""Хелпер: строит нижнюю клавиатуру из строк кнопок + служебные кнопки снизу.
|
||
|
||
back — кнопка «Назад ⬅️» (к предыдущему вопросу воронки);
|
||
ask_manager — кнопка «Задать вопрос менеджеру 💬»;
|
||
back_menu — кнопка «В главное меню ↩️».
|
||
"""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
for i, row in enumerate(rows):
|
||
if i:
|
||
keyboard.add_line()
|
||
for label in row:
|
||
keyboard.add_button(label, color=VkKeyboardColor.SECONDARY)
|
||
if ask_manager:
|
||
keyboard.add_line()
|
||
keyboard.add_button("Задать вопрос менеджеру 💬", color=VkKeyboardColor.SECONDARY)
|
||
if back or back_menu:
|
||
keyboard.add_line()
|
||
if back:
|
||
keyboard.add_button("Назад ⬅️", color=VkKeyboardColor.SECONDARY)
|
||
if back_menu:
|
||
keyboard.add_button("В главное меню ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_baza_forwho_keyboard() -> str:
|
||
return _kb([["Для себя", "Для подростка"]])
|
||
|
||
|
||
def get_baza_age_keyboard() -> str:
|
||
return _kb([["12–13 лет", "14–15 лет"], ["16 лет и старше", "Младше 12 лет"]], back=True)
|
||
|
||
|
||
def get_baza_younger12_keyboard() -> str:
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Обсудить с менеджером 💬", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Назад ⬅️", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("В главное меню ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_baza_game_keyboard() -> str:
|
||
return _kb([["CS2", "Dota 2"], ["World of Tanks", "Другая игра"]], back=True)
|
||
|
||
|
||
def get_baza_level_keyboard() -> str:
|
||
return _kb([["Только начинает"], ["Играет для себя"],
|
||
["Есть рейтинг или ранг"], ["Играет в команде или турнирах"]], back=True)
|
||
|
||
|
||
def get_baza_format_keyboard() -> str:
|
||
return _kb([["Групповые тренировки"], ["Индивидуальные тренировки"],
|
||
["Пока не знаю, нужен совет"]], back=True)
|
||
|
||
|
||
def get_baza_group_keyboard() -> str:
|
||
return _kb([["Записаться на пробное"], ["Купить 4 тренировки", "Купить 8 тренировок"]],
|
||
back=True, ask_manager=True)
|
||
|
||
|
||
def get_baza_individual_keyboard() -> str:
|
||
return _kb([["Записаться на пробное"], ["Купить 1 тренировку"],
|
||
["Купить 4 тренировки", "Купить 8 тренировок"]],
|
||
back=True, ask_manager=True)
|
||
|
||
|
||
def get_baza_help_keyboard() -> str:
|
||
return _kb([["Записаться на пробное"], ["Подобрать заново 🎯"]], ask_manager=True)
|
||
|
||
|
||
def get_baza_intensive_keyboard() -> str:
|
||
return _kb([["Хочу участвовать 🔥"]], ask_manager=True)
|
||
|
||
|
||
def get_baza_prices_keyboard() -> str:
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Подобрать тренировку 🎯", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Записаться на пробное", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Выбрать абонемент", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Задать вопрос 💬", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_admin_keyboard(is_super: bool = False) -> str:
|
||
"""Клавиатура админ-панели с разделами. Супер-админу — раздел «Администраторы»."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Статистика 📊", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Кандидаты 👥", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Управление услугами ⚙️", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Вакансии 💼", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Быстрые ответы 💬", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
if is_super:
|
||
keyboard.add_button("Администраторы 👑", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("В главное меню ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_svc_admin_keyboard() -> str:
|
||
"""Клавиатура раздела управления услугами (направлениями)."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("➕ Добавить услугу", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("✏️ Изменить услугу", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("🖼 Сменить картинку", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("🗑 Удалить услугу", color=VkKeyboardColor.NEGATIVE)
|
||
keyboard.add_line()
|
||
keyboard.add_button("В админку ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_faq_admin_keyboard() -> str:
|
||
"""Клавиатура раздела управления быстрыми ответами."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("➕ Добавить ответ", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("✏️ Изменить ответ", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("🗑 Удалить ответ", color=VkKeyboardColor.NEGATIVE)
|
||
keyboard.add_line()
|
||
keyboard.add_button("В админку ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_pick_keyboard(items: list) -> str:
|
||
"""Клавиатура выбора из списка пар (id, label). Кнопка отправляет '<id>. <label>'."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
count = 0
|
||
for _id, label in items[:10]:
|
||
if count and count % 2 == 0:
|
||
keyboard.add_line()
|
||
short = label if len(label) <= 30 else label[:29] + "…"
|
||
keyboard.add_button(f"{_id}. {short}", color=VkKeyboardColor.SECONDARY)
|
||
count += 1
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отмена ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_cancel_admin_keyboard() -> str:
|
||
"""Кнопка отмены для админских мастеров ввода."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Отмена ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_vacancy_choice_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)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_vacancy_cancel_keyboard() -> str:
|
||
"""Клавиатура отмены отклика на свободном текстовом шаге."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Отменить ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_vacancy_back_keyboard() -> str:
|
||
"""Клавиатура с одной кнопкой «Назад в меню» — под карусель выбора в «Карьере»."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_faq_keyboard() -> str:
|
||
"""Клавиатура раздела Частых Вопросов (FAQ)."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Цены 💰", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Услуги ⚙️", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Контакты 📞", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("О компании 🏢", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Наши соцсети 🔗", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_consent_docs_keyboard() -> str:
|
||
"""Инлайн-кнопки со ссылками на оферту и политику обработки ПД."""
|
||
keyboard = VkKeyboard(inline=True)
|
||
keyboard.add_openlink_button("📄 Оферта", getattr(config, "OFFER_URL", "https://infocyber.pro/"))
|
||
keyboard.add_line()
|
||
keyboard.add_openlink_button("🔐 Политика обработки ПД", getattr(config, "POLICY_URL", "https://infocyber.pro/"))
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_socials_keyboard() -> str:
|
||
"""Клавиатура с кнопками-ссылками на площадки ИКП (открываются в один тап)."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_openlink_button("VK Live 📺", "https://live.vkvideo.ru/ikp")
|
||
keyboard.add_line()
|
||
keyboard.add_openlink_button("Telegram ✈️", "https://t.me/ikp_team")
|
||
keyboard.add_openlink_button("Lolka 🎮", "https://vk.com/away.php?to=https%3A%2F%2Flolka.gg%2FlNfM3F9DD&utf=1")
|
||
keyboard.add_line()
|
||
keyboard.add_openlink_button("ВКонтакте 🌐", "https://vk.com/ikpro")
|
||
keyboard.add_line()
|
||
keyboard.add_openlink_button("Наш сайт 🔗", "https://infocyber.pro/")
|
||
keyboard.add_line()
|
||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_cancel_keyboard() -> str:
|
||
"""Клавиатура отмены заполнения формы."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_operator_keyboard() -> str:
|
||
"""Клавиатура при активном диалоге с оператором."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Завершить диалог 🚪", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_binary_keyboard(show_skip: bool = False) -> str:
|
||
"""Клавиатура с кнопками Согласен / Не согласен."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Согласен(а) и подтверждаю ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_button("Не согласен(а) ❌", color=VkKeyboardColor.NEGATIVE)
|
||
if show_skip:
|
||
keyboard.add_line()
|
||
keyboard.add_button("Пропустить ➡️", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_returning_keyboard() -> str:
|
||
"""Нижняя клавиатура возвращающегося клиента: подтвердить или отменить."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Да, всё верно ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_returning_changes_keyboard() -> str:
|
||
"""Инлайн-кнопки под сообщением: сменить имя / телефон / заполнить заново."""
|
||
keyboard = VkKeyboard(inline=True)
|
||
keyboard.add_button("Сменить имя 👤", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Сменить телефон 📞", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Заполнить заново 🔄", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_calldate_keyboard() -> str:
|
||
"""Клавиатура выбора ДНЯ для звонка (сегодня/завтра + ближайшие даты)."""
|
||
from datetime import datetime, timedelta
|
||
wd = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
|
||
today = datetime.now()
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Сегодня", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Завтра", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
for i in (2, 3, 4):
|
||
d = today + timedelta(days=i)
|
||
keyboard.add_button(f"{wd[d.weekday()]} {d.strftime('%d.%m')}", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_calltime_keyboard() -> str:
|
||
"""Клавиатура выбора ВРЕМЕНИ (слоты в рабочие часы)."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("10:00", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("12:00", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("14:00", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("16:00", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("18:00", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("20:00", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Любое время", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_marketing_keyboard() -> str:
|
||
"""Клавиатура для необязательного согласия на маркетинговую рассылку."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Согласен на рассылку ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_button("Без рассылки ➡️", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_services_keyboard() -> str:
|
||
"""Клавиатура со списком доступных услуг."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
first = True
|
||
for key, svc in config.SERVICES.items():
|
||
if not first:
|
||
keyboard.add_line()
|
||
keyboard.add_button(svc["name"], color=VkKeyboardColor.SECONDARY)
|
||
first = False
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_confirm_service_keyboard() -> str:
|
||
"""Клавиатура для подтверждения выбранной услуги."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Да, подтверждаю ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_button("Нет, выбрать другую 🔄", color=VkKeyboardColor.NEGATIVE)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_review_keyboard() -> str:
|
||
"""Клавиатура для финальной проверки данных пользователем."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Да, всё верно! ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_button("Изменить данные 🔄", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_edit_menu_keyboard() -> str:
|
||
"""Клавиатура меню изменения полей."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Изменить Имя 👤", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Изменить Телефон 📞", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Изменить Услугу 🎮", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Изменить Время 🕒", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Назад к проверке ↩️", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|