Files
bot_vk_ikp_prodagi/config.py
T
Kenik 74a855819d Чистка старого ИКП-флоу: удалены форма-заявка, карьера/вакансии, меню FAQ и осиротевшая админка
Из ТЗ Кибершколы БАЗА эти сценарии заменены воронкой подбора, поэтому убран мёртвый код:
- клиентские FSM: FORM_* (форма-заявка, возвращающийся клиент, редактирование, согласия,
  выбор услуги, карусель) и VAC_* (карьера: игроки/сотрудники);
- меню «Частые вопросы» и config.FAQ_ANSWERS;
- осиротевшая админка: управление услугами + карусель, вакансии, кандидаты
  (в панели и в мастере ADMIN_SVC_*/ADMIN_VAC_*), блок статистики по откликам;
- старый _handoff_to_operator и _notify_operator_about_lead (заменены _baza_to_manager);
- неиспользуемые клавиатуры (форма/услуги/вакансии/согласия) и импорты (validators, random, string).

Оставлено (используется/по ТЗ): воронка БАЗА, быстрые ответы (custom_faq), стата по лидам,
рассылка, управление админами, чат с оператором, логотип. Слой БД не тронут — существующие
установки не ломаются (таблицы services/applications/vacancy_options просто не используются).

bot.py: -1490 строк. Тесты: pytest 28 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 18:36:38 +03:00

167 lines
10 KiB
Python

import os
# Load environment variables from .env in the project root
def _load_env():
# Find the project root (the directory containing config.py)
root_dir = os.path.dirname(os.path.abspath(__file__))
dotenv_path = os.path.join(root_dir, ".env")
if os.path.exists(dotenv_path):
try:
with open(dotenv_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
key, val = line.split("=", 1)
val = val.strip().strip('"').strip("'")
# Не перетираем переменные, уже заданные в реальном окружении
# (на проде токены приходят из env, .env — лишь фолбэк).
os.environ.setdefault(key.strip(), val)
except Exception as e:
print(f"Warning: Failed to load .env file: {e}")
_load_env()
# VK API Configuration
# In production, these should be loaded from environment variables or a secure .env file.
VK_TOKEN = os.getenv("VK_TOKEN", "your_vk_community_token_here")
GROUP_ID = int(os.getenv("VK_GROUP_ID", "0")) # Community ID (must be integer)
# Operator / Admin configuration
# ID of the user or chat where notifications about new leads and operator requests will be sent
OPERATOR_PEER_ID = int(os.getenv("OPERATOR_PEER_ID", "12345678")) # Change to actual VK user ID of operator
# Parse ADMIN_IDS as a list of integers from comma-separated env values
admin_ids_raw = os.getenv("ADMIN_IDS")
if admin_ids_raw:
try:
ADMIN_IDS = [int(x.strip()) for x in admin_ids_raw.split(",") if x.strip().isdigit()]
except Exception:
ADMIN_IDS = [OPERATOR_PEER_ID]
else:
ADMIN_IDS = [OPERATOR_PEER_ID]
# ============================================================
# СУПЕР-АДМИН. Меняется ТОЛЬКО ЗДЕСЬ, в коде (не через .env, не через бота).
# Супер-админ может добавлять/удалять обычных администраторов прямо из бота.
# Чтобы сменить/добавить супер-админа — впишите сюда VK ID (числом):
SUPER_ADMIN_IDS = [350793273]
# ============================================================
# Ссылки на юридические документы (инлайн-кнопки в боте)
OFFER_URL = os.getenv("OFFER_URL", "https://infocyber.pro/form/Dogovor_oferty_kibersport_versia_izmenennaya.pdf")
POLICY_URL = os.getenv("POLICY_URL", "https://infocyber.pro/form/Политика%20ПД.pdf")
# Несколько операторов: уведомления о заявках/откликах/вызовах уходят ВСЕМ из списка.
# По умолчанию = один OPERATOR_PEER_ID. Задать через .env: OPERATOR_PEER_IDS=111,222,333
_op_ids_raw = os.getenv("OPERATOR_PEER_IDS")
if _op_ids_raw:
OPERATOR_PEER_IDS = [int(x.strip()) for x in _op_ids_raw.split(",") if x.strip().lstrip("-").isdigit()]
if not OPERATOR_PEER_IDS:
OPERATOR_PEER_IDS = [OPERATOR_PEER_ID]
else:
OPERATOR_PEER_IDS = [OPERATOR_PEER_ID]
# Часы работы (для статуса оператора). Вне этого интервала — «ответим утром».
WORK_HOUR_START = int(os.getenv("WORK_HOUR_START", "9"))
WORK_HOUR_END = int(os.getenv("WORK_HOUR_END", "21"))
# Разрешённый источник (Origin) для CORS веб-формы. '*' допустим только для локальной отладки.
ALLOWED_ORIGIN = os.getenv("ALLOWED_ORIGIN", "https://infocyber.pro")
# Промокоды на скидку. Пока ВЫКЛЮЧЕНЫ — включить можно, задав PROMO_ENABLED=true в .env.
# Когда выключено: бот и форма не показывают промокод, в уведомлениях/письмах его нет.
PROMO_ENABLED = os.getenv("PROMO_ENABLED", "false").strip().lower() in ("1", "true", "yes")
# Email (SMTP) configuration для уведомлений о заявках
SMTP_HOST = os.getenv("SMTP_HOST", "")
SMTP_PORT = int(os.getenv("SMTP_PORT", "465"))
SMTP_USER = os.getenv("SMTP_USER", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
MAIL_FROM = os.getenv("MAIL_FROM", "") or SMTP_USER
# Куда отправлять уведомления о новых заявках (можно несколько через запятую)
MAIL_TO = os.getenv("MAIL_TO", "group-zakaz@infocyber.pro")
# Куда отправлять отклики на вакансии / набор в состав.
# TODO: пока совпадает с MAIL_TO; возможно вынести на отдельный HR-адрес (напр. hr@infocyber.pro) — задать HR_MAIL_TO в .env.
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 тренировки."
)
# Доступные услуги (направления) — используются для сидирования БД (services)
SERVICES = {
"1": {
"name": "Counter-Strike 2 🔫",
"price": "от 870 ₽ / занятие",
"description": "Тренировки по CS2: aim, раскидки гранат, командная тактика. Разовое занятие 870 ₽, абонементы от 2 790 ₽."
},
"2": {
"name": "Dota 2 🎮",
"price": "от 870 ₽ / занятие",
"description": "Тренировки по Dota 2: разбор реплеев, позиционка, фарм и тимплей. Разовое занятие 870 ₽, абонементы от 2 790 ₽."
},
"3": {
"name": "World of Tanks 🛡️",
"price": "от 870 ₽ / занятие",
"description": "Тренировки по World of Tanks: позиционирование, сведение, тактика боёв. Разовое занятие 870 ₽, абонементы от 2 790 ₽."
}
}