Тексты воронки по ТЗ + смена ссылок из админки
Тексты: - «Сколько лет игроку?» (было «…будущему ученику?») во всех местах; - сообщение для «Младше 12 лет» приведено дословно к ТЗ (§4); - убраны отсебятины-преамбулы, которых нет в ТЗ (приветствие, старт воронки, цены «Что дальше?», интенсив «Хотите записаться?»); кнопка младше-12 «Вернуться назад». Смена ссылок в админке: - таблица bot_links (key, url) + методы get_link/get_all_links/set_link; - _link(key): БД-переопределение → дефолт из config/.env; используется на шагах оплаты; - раздел «Ссылки 🔗» в админ-панели: список текущих адресов + «✏️ Изменить ссылку» (выбор из 8 кнопок → ввод URL с валидацией http/https). UTM добавляются автоматически. Тесты: добавлен test_admin_edit_link. pytest 31 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -247,7 +247,8 @@ class VKBotApplication:
|
||||
"Выберите раздел на кнопках ниже:\n"
|
||||
"• 📊 Статистика — заявки и диалоги\n"
|
||||
"• 💬 Быстрые ответы — авто-ответы по ключевым словам\n"
|
||||
"• 📣 Рассылка — сообщение подписчикам"
|
||||
"• 📣 Рассылка — сообщение подписчикам\n"
|
||||
"• 🔗 Ссылки — куда ведут кнопки записи/оплаты"
|
||||
+ extra,
|
||||
keyboard=get_admin_keyboard(is_super=is_super)
|
||||
)
|
||||
@@ -444,6 +445,14 @@ class VKBotApplication:
|
||||
elif text_lower.startswith("рассылка"):
|
||||
self._handle_admin_broadcast(user_id, text_clean)
|
||||
return
|
||||
elif text_lower in ["ссылки 🔗", "ссылки", "ссылка"]:
|
||||
self._send_links_admin(user_id)
|
||||
return
|
||||
elif text_lower in ["✏️ изменить ссылку", "изменить ссылку"]:
|
||||
items = [(k, config.LINK_LABELS.get(k, k)) for k in config.LINK_LABELS]
|
||||
db.update_user_state(user_id, "ADMIN_LINK_PICK")
|
||||
self.send_message(user_id, "✏️ Какую ссылку изменить? Выберите:", keyboard=get_pick_keyboard(items))
|
||||
return
|
||||
elif text_lower in ["быстрые ответы 💬", "быстрые ответы", "фак"]:
|
||||
self._send_faq_admin(user_id)
|
||||
return
|
||||
@@ -663,10 +672,14 @@ class VKBotApplication:
|
||||
result = f"{base}{sep}{utm}"
|
||||
return f"{result}#{frag}" if sep_frag else result
|
||||
|
||||
def _link(self, key: str) -> str:
|
||||
"""Актуальный URL кнопки: переопределение из БД (админка) или дефолт из config/.env."""
|
||||
return db.get_link(key) or config.LINKS.get(key, config.LINKS["tariffs"])
|
||||
|
||||
def _baza_send_openlink(self, user_id: int, link_key: str, message: str, button: str,
|
||||
campaign: str = "main"):
|
||||
"""Отправляет сообщение с инлайн-кнопкой-ссылкой (без перевода на оплату/лид)."""
|
||||
link = self._utm(config.LINKS.get(link_key, config.LINKS["tariffs"]), campaign)
|
||||
link = self._utm(self._link(link_key), campaign)
|
||||
kb = VkKeyboard(inline=True)
|
||||
kb.add_openlink_button(button, link)
|
||||
self.send_message(user_id, message, keyboard=kb.get_keyboard())
|
||||
@@ -682,7 +695,7 @@ class VKBotApplication:
|
||||
self._baza_started(user_id)
|
||||
self.send_message(
|
||||
user_id,
|
||||
config.GREETING_BAZA + "\n\nВыберите действие на кнопках ниже 👇",
|
||||
config.GREETING_BAZA,
|
||||
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id)),
|
||||
attachment=self.get_logo_attachment(user_id)
|
||||
)
|
||||
@@ -712,7 +725,6 @@ class VKBotApplication:
|
||||
db.update_user_state(user_id, "BAZA_FORWHO")
|
||||
self.send_message(
|
||||
user_id,
|
||||
"Отлично! Задам пару коротких вопросов и подберу подходящий формат 🎯\n\n"
|
||||
"Для кого подбираем тренировки?",
|
||||
keyboard=get_baza_forwho_keyboard()
|
||||
)
|
||||
@@ -730,7 +742,7 @@ class VKBotApplication:
|
||||
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_forwho_keyboard())
|
||||
return
|
||||
db.update_user_state(user_id, "BAZA_AGE")
|
||||
self.send_message(user_id, "Сколько лет будущему ученику?", keyboard=get_baza_age_keyboard())
|
||||
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||
|
||||
def _handle_baza_age(self, user_id: int, text: str, text_lower: str):
|
||||
if self._baza_nav(user_id, text_lower):
|
||||
@@ -741,12 +753,12 @@ class VKBotApplication:
|
||||
return
|
||||
if "младше 12" in text_lower or "меньше 12" in text_lower:
|
||||
db.set_profile_field(user_id, "age", "Младше 12 лет")
|
||||
db.add_metka(user_id, "age_under12")
|
||||
db.update_user_state(user_id, "BAZA_YOUNG")
|
||||
self.send_message(
|
||||
user_id,
|
||||
"Обычно мы занимаемся с ребятами от 12 лет. Если ученику чуть меньше — это не проблема, "
|
||||
"лучше подобрать формат вместе с менеджером.",
|
||||
"Основные программы Кибершколы БАЗА рассчитаны на игроков от 12 лет. Но мы можем "
|
||||
"отдельно посмотреть, получится ли подобрать подходящий формат.\n"
|
||||
"Передать запрос менеджеру?",
|
||||
keyboard=get_baza_younger12_keyboard()
|
||||
)
|
||||
return
|
||||
@@ -769,7 +781,7 @@ class VKBotApplication:
|
||||
return
|
||||
if text_lower.startswith("назад") or text_lower.startswith("вернуться"):
|
||||
db.update_user_state(user_id, "BAZA_AGE")
|
||||
self.send_message(user_id, "Сколько лет будущему ученику?", keyboard=get_baza_age_keyboard())
|
||||
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||
return
|
||||
if self._baza_nav(user_id, text_lower):
|
||||
return
|
||||
@@ -780,7 +792,7 @@ class VKBotApplication:
|
||||
return
|
||||
if text_lower.startswith("назад"):
|
||||
db.update_user_state(user_id, "BAZA_AGE")
|
||||
self.send_message(user_id, "Сколько лет будущему ученику?", keyboard=get_baza_age_keyboard())
|
||||
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||
return
|
||||
if "cs" in text_lower or "кс" in text_lower or "контр" in text_lower:
|
||||
game, metka = "CS2", "game_cs2"
|
||||
@@ -939,7 +951,7 @@ class VKBotApplication:
|
||||
|
||||
def _baza_send_payment(self, user_id: int, link_key: str, tariff_label: str, campaign: str = "main"):
|
||||
"""Показывает ссылку на оплату/запись + кнопки «проблема с оплатой» / «вопрос менеджеру»."""
|
||||
link = self._utm(config.LINKS.get(link_key, config.LINKS["tariffs"]), campaign)
|
||||
link = self._utm(self._link(link_key), campaign)
|
||||
db.set_profile_field(user_id, "tariff", tariff_label)
|
||||
db.add_metka(user_id, "clicked_payment")
|
||||
db.add_metka(user_id, "lead_hot")
|
||||
@@ -987,7 +999,7 @@ class VKBotApplication:
|
||||
db.update_user_state(user_id, "BAZA_INT_INFO")
|
||||
self.send_message(
|
||||
user_id,
|
||||
config.INTENSIVE_INFO + "\n\nХотите записаться на интенсив?",
|
||||
config.INTENSIVE_INFO,
|
||||
keyboard=get_baza_intensive_keyboard()
|
||||
)
|
||||
|
||||
@@ -996,7 +1008,7 @@ class VKBotApplication:
|
||||
return
|
||||
if "участвовать" in text_lower or "записаться" in text_lower or "хочу" in text_lower or "да" == text_lower:
|
||||
db.update_user_state(user_id, "BAZA_INT_AGE")
|
||||
self.send_message(user_id, "Отлично! Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||
return
|
||||
self.send_message(user_id, "Выберите действие на кнопках ниже 👇", keyboard=get_baza_intensive_keyboard())
|
||||
|
||||
@@ -1005,7 +1017,7 @@ class VKBotApplication:
|
||||
return
|
||||
if text_lower.startswith("назад"):
|
||||
db.update_user_state(user_id, "BAZA_INT_INFO")
|
||||
self.send_message(user_id, "Хотите записаться на интенсив?", keyboard=get_baza_intensive_keyboard())
|
||||
self.send_message(user_id, config.INTENSIVE_INFO, keyboard=get_baza_intensive_keyboard())
|
||||
return
|
||||
if "младше 12" in text_lower or "меньше 12" in text_lower:
|
||||
db.set_profile_field(user_id, "age", "Младше 12 лет")
|
||||
@@ -1113,7 +1125,7 @@ class VKBotApplication:
|
||||
|
||||
def _baza_show_prices(self, user_id: int):
|
||||
db.update_user_state(user_id, "FREE_CHAT")
|
||||
self.send_message(user_id, config.PRICES_TEXT + "\n\nЧто дальше?", keyboard=get_baza_prices_keyboard())
|
||||
self.send_message(user_id, config.PRICES_TEXT, keyboard=get_baza_prices_keyboard())
|
||||
|
||||
def _baza_manager_card(self, user_id: int, reason: str = "") -> str:
|
||||
p = db.get_profile(user_id)
|
||||
@@ -1383,6 +1395,41 @@ class VKBotApplication:
|
||||
back_to_faq(f"✅ Ответ №{fid} удалён.")
|
||||
return
|
||||
|
||||
# ---------- Ссылки кнопок ----------
|
||||
if state == "ADMIN_LINK_PICK":
|
||||
key = pick_id(text)
|
||||
if key not in config.LINK_LABELS:
|
||||
self.send_message(user_id, "⚠️ Выберите ссылку кнопкой ниже.")
|
||||
return
|
||||
self._admin_ctx[user_id] = {"link_key": key}
|
||||
db.update_user_state(user_id, "ADMIN_LINK_VAL")
|
||||
self.send_message(
|
||||
user_id,
|
||||
f"✏️ «{config.LINK_LABELS[key]}»\nТекущий адрес:\n{self._link(key)}\n\n"
|
||||
"Пришлите новый URL (начинается с http:// или https://). UTM добавится автоматически.",
|
||||
keyboard=get_cancel_admin_keyboard()
|
||||
)
|
||||
return
|
||||
|
||||
if state == "ADMIN_LINK_VAL":
|
||||
key = self._admin_ctx.get(user_id, {}).get("link_key")
|
||||
url = text.strip()
|
||||
if not key or key not in config.LINK_LABELS:
|
||||
self._admin_ctx.pop(user_id, None)
|
||||
db.update_user_state(user_id, "FREE_CHAT")
|
||||
self._send_links_admin(user_id)
|
||||
return
|
||||
if not re.match(r"^https?://", url, re.I):
|
||||
self.send_message(user_id, "⚠️ Ссылка должна начинаться с http:// или https://",
|
||||
keyboard=get_cancel_admin_keyboard())
|
||||
return
|
||||
db.set_link(key, url)
|
||||
self._admin_ctx.pop(user_id, None)
|
||||
db.update_user_state(user_id, "FREE_CHAT")
|
||||
self.send_message(user_id, f"✅ Ссылка «{config.LINK_LABELS[key]}» обновлена.")
|
||||
self._send_links_admin(user_id)
|
||||
return
|
||||
|
||||
# ---------- Администраторы (супер-админ) ----------
|
||||
if state == "ADMIN_ADMIN_ADD":
|
||||
if not self._is_super_admin(user_id):
|
||||
@@ -1446,6 +1493,23 @@ class VKBotApplication:
|
||||
)
|
||||
self.send_message(user_id, msg)
|
||||
|
||||
def _send_links_admin(self, user_id: int):
|
||||
"""Раздел «Ссылки»: текущие адреса кнопок записи/оплаты + кнопка изменения."""
|
||||
lines = []
|
||||
for key, label in config.LINK_LABELS.items():
|
||||
overridden = " (изменена)" if db.get_link(key) else ""
|
||||
lines.append(f"🔗 {label}{overridden}\n {self._link(key)}")
|
||||
kb = VkKeyboard(inline=True)
|
||||
kb.add_button("✏️ Изменить ссылку", color=VkKeyboardColor.PRIMARY)
|
||||
kb.add_line()
|
||||
kb.add_button("В админку ↩️", color=VkKeyboardColor.SECONDARY)
|
||||
self.send_message(
|
||||
user_id,
|
||||
"🔗 Ссылки кнопок записи/оплаты\n\n" + "\n\n".join(lines) +
|
||||
"\n\nUTM-метки добавляются автоматически. Нажмите «Изменить ссылку», чтобы задать новый адрес.",
|
||||
keyboard=kb.get_keyboard()
|
||||
)
|
||||
|
||||
def _send_faq_admin(self, user_id: int):
|
||||
"""Раздел «Быстрые ответы»: список + инструкции."""
|
||||
rows = db.get_custom_faq()
|
||||
|
||||
Reference in New Issue
Block a user