7a59b035ce
Закрыты расхождения с ТЗ: - Метки (§12) переименованы под спецификацию: role_player, game_dota2, format_help, interest_trial, interest_summer_intensive, manager_requested, source_main_menu, source_keyword_summer; убраны служебные age_*/level_* метки. - Сохранённые данные (§13): кодовое слово, дата запуска бота (started_at), дата передачи менеджеру (manager_at), интересы (пробное/интенсив). Карточка менеджеру дополнена этими полями, заголовок «Новый запрос из бота». - «Пока не знаю, нужен совет» (§7): показ собранных данных + свободный текст → менеджеру. - «Задать вопрос» (§9): промпт «напишите одним сообщением» → BAZA_QUESTION → передача. - Летний интенсив: свой набор уровней (§10.3), экран младше-12 (Передать менеджеру/ Другие тренировки), двухшаговая оплата (Оплатить интенсив → ссылка) (§10.4), различение запуска по кодовому слову vs меню. - Индивидуальная ветка без «пробного» (§6). Fallback-текст дословно по §15. - «Выбрать абонемент» ведёт на страницу тарифов ссылкой (§8). Тексты веток — по ТЗ. Тесты воронки обновлены и дополнены (интенсив-оплата, младше-12 интенсив, свободный вопрос, «нужен совет»). pytest 30 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
217 lines
10 KiB
Python
217 lines
10 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_menu_only_keyboard() -> str:
|
||
"""Только «В главное меню» — для шагов со свободным вводом (совет/вопрос)."""
|
||
return _kb([])
|
||
|
||
|
||
def get_baza_intensive_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_baza_int_level_keyboard() -> str:
|
||
return _kb([["Только начинаю"], ["Уже знаю основы"],
|
||
["Играю регулярно"], ["Есть рейтинг или командный опыт"]], back=True)
|
||
|
||
|
||
def get_baza_int_pay_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_baza_int_young_keyboard() -> str:
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Передать менеджеру 💬", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Посмотреть другие тренировки", color=VkKeyboardColor.SECONDARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
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_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_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_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_operator_keyboard() -> str:
|
||
"""Клавиатура при активном диалоге с оператором."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Завершить диалог 🚪", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|