Ребрендинг БАЗА (фундамент): конфиг + профиль воронки в БД

Фаза 1 — config: BRAND «Кибершкола БАЗА», цены/тексты по ТЗ (приветствие,
прайс, интенсив), 8 ссылок-плейсхолдеров с UTM, триггеры (основные/лето).
Фаза 2 — database: JSON-профиль воронки (роль/возраст/игра/уровень/формат/
метки/…) + методы get/set/clear + add_metka. Живой флоу пока не затронут.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-07-18 17:58:00 +03:00
parent ce2860aec9
commit f6a635cc2e
2 changed files with 105 additions and 1 deletions
+57
View File
@@ -88,6 +88,63 @@ HR_MAIL_TO = os.getenv("HR_MAIL_TO", "") or MAIL_TO
# True — порт 465 (SSL), False — STARTTLS (обычно порт 587)
SMTP_USE_SSL = os.getenv("SMTP_USE_SSL", "true").strip().lower() in ("1", "true", "yes")
# ============================================================
# КИБЕРШКОЛА БАЗА — бренд, цены, ссылки, триггеры (по ТЗ)
# Всё редактируемое собрано здесь, чтобы менять без правки логики.
# ============================================================
BRAND = "Кибершкола БАЗА"
# --- Ссылки на оплату/запись (ПЛЕЙСХОЛДЕРЫ — заменить на реальные) ---
# UTM добавляются автоматически там, где ссылка ведёт наружу.
UTM_MAIN = "utm_source=vk&utm_medium=bot&utm_campaign=main_bot"
UTM_SUMMER = "utm_source=vk&utm_medium=bot&utm_campaign=summer_intensive"
LINKS = {
"trial": os.getenv("LINK_TRIAL", "https://infocyber.pro/"), # запись на пробное
"tariffs": os.getenv("LINK_TARIFFS", "https://infocyber.pro/"), # страница с тарифами
"group_4": os.getenv("LINK_GROUP_4", "https://infocyber.pro/"), # групповой абонемент 4
"group_8": os.getenv("LINK_GROUP_8", "https://infocyber.pro/"), # групповой абонемент 8
"individual_1": os.getenv("LINK_IND_1", "https://infocyber.pro/"), # индивидуальная 1
"individual_4": os.getenv("LINK_IND_4", "https://infocyber.pro/"), # индивидуальные 4
"individual_8": os.getenv("LINK_IND_8", "https://infocyber.pro/"), # индивидуальные 8
"summer": os.getenv("LINK_SUMMER", "https://infocyber.pro/"), # оплата летнего интенсива
}
# --- Триггеры запуска (регистронезависимо) ---
MAIN_TRIGGERS = ["начать", "тренировки", "занятия", "цена", "стоимость", "записаться",
"привет", "здравствуйте", "старт", "меню"]
SUMMER_TRIGGERS = ["лето", "интенсив", "летний интенсив", "интенсив cs2", "хочу на интенсив"]
# --- Тексты (по ТЗ, редактируемые) ---
GREETING_BAZA = (
"Привет! Это Кибершкола БАЗА 🎮\n\n"
"Мы проводим онлайн-тренировки по CS2, Dota 2 и World of Tanks для подростков 12+.\n"
"Помогу подобрать подходящий формат, посмотреть цены или записаться на тренировку."
)
PRICES_TEXT = (
"Стоимость тренировок в Кибершколе БАЗА:\n\n"
"🧑‍🤝‍🧑 Групповые тренировки:\n"
"• 4 занятия — 2 790 ₽\n"
"• 8 занятий — 4 990 ₽\n\n"
"👤 Индивидуальные тренировки:\n"
"• 1 занятие — 870 ₽\n"
"• 4 занятия — 3 290 ₽\n"
"• 8 занятий — 5 990 ₽\n\n"
"Все занятия проходят онлайн с тренером."
)
INTENSIVE_PRICE = "1 990 ₽"
INTENSIVE_INFO = (
"Привет! Вы написали кодовое слово «лето» — рассказываю про летний интенсив по CS2 🔥\n\n"
"Это 4 онлайн-тренировки по 90 минут с тренером.\n"
"На интенсиве участники будут:\n"
"— разбирать роли и командную игру;\n"
"— тренировать аим, дуэли и контрстрейфы;\n"
"— разбирать игровые моменты и демозаписи;\n"
"— изучать гранаты, мувмент и полезные игровые приёмы.\n\n"
f"Стоимость участия — {INTENSIVE_PRICE} за все 4 тренировки."
)
# Standard replies and patterns
FAQ_ANSWERS = {
"цены": (
+48 -1
View File
@@ -30,7 +30,7 @@ class DatabaseManager:
""")
# Migration checks for existing databases
for col in ["consent_1", "consent_2", "consent_3", "call_time", "vac_data"]:
for col in ["consent_1", "consent_2", "consent_3", "call_time", "vac_data", "profile"]:
try:
cursor.execute(f"ALTER TABLE user_states ADD COLUMN {col} TEXT")
except sqlite3.OperationalError:
@@ -475,6 +475,53 @@ class DatabaseManager:
)
conn.commit()
# --- Профиль воронки БАЗА (JSON в user_states.profile) ---
def get_profile(self, user_id: int) -> Dict[str, Any]:
"""Профиль пользователя из воронки (роль/возраст/игра/уровень/формат/метки/…)."""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT profile FROM user_states WHERE user_id = ?", (user_id,))
row = cursor.fetchone()
if row and row[0]:
try:
return json.loads(row[0])
except (ValueError, TypeError):
return {}
return {}
def set_profile_field(self, user_id: int, key: str, value: Any):
"""Обновить одно поле профиля (не спрашиваем повторно то, что уже есть)."""
data = self.get_profile(user_id)
data[key] = value
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO user_states (user_id, profile) VALUES (?, ?) "
"ON CONFLICT(user_id) DO UPDATE SET profile = excluded.profile, last_updated = CURRENT_TIMESTAMP",
(user_id, json.dumps(data, ensure_ascii=False))
)
conn.commit()
def add_metka(self, user_id: int, metka: str):
"""Добавить метку пользователю (хранится списком в профиле, без повторов)."""
data = self.get_profile(user_id)
metki = data.get("metki", [])
if metka not in metki:
metki.append(metka)
data["metki"] = metki
self.set_profile_field(user_id, "metki", metki)
def clear_profile(self, user_id: int):
"""Полностью очистить профиль воронки."""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO user_states (user_id, profile) VALUES (?, NULL) "
"ON CONFLICT(user_id) DO UPDATE SET profile = NULL, last_updated = CURRENT_TIMESTAMP",
(user_id,)
)
conn.commit()
# --- Вакансии (направления игроков / должности сотрудников) ---
def get_vacancy_options(self, track: str) -> list:
"""Активные опции для трека ('player'|'worker') как список (id, label)."""