From 79cc1ba38df6bb61fcf62aac60b52f0f70b81b67 Mon Sep 17 00:00:00 2001 From: Kenik Date: Sun, 21 Jun 2026 21:54:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20VK-=D0=B1=D0=BE=D1=82=D0=B0=20=D0=98=D0=9A=D0=9F?= =?UTF-8?q?:=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BD=D1=82,=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B5=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= =?UTF-8?q?,=20=D0=B7=D0=B0=D1=89=D0=B8=D1=82=D0=B0=20=D0=B8=20=D0=B2?= =?UTF-8?q?=D0=B8=D0=B7=D1=83=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Контент и брендинг: - Реальные данные ИКP (infocyber.pro): услуги CS2/Dota 2/WoT, цены, FAQ, контакты, площадки - Приветствие и тексты приведены к бренду ИКП Новые функции: - Раздельные согласия (152-ФЗ): маркетинговая рассылка — отдельный необязательный шаг - Шаг «удобное время звонка» в форме заявки - Раздел «Карьера в ИКП»: набор игроков и сотрудников (таблица applications) - Админ-команды: статистика (стата) и рассылка по подписчикам с троттлингом и отпиской - Журнал заявок leads, «бот помнит клиента», международные телефоны, валидация имени - Email-уведомления о заявках и откликах (mailer.py); HR-адрес через HR_MAIL_TO Защита: - Анти-флуд по пользователю и кулдаун уведомлений оператору (анти-DDoS) - Веб-форма: серверная валидация, honeypot, рейт-лимит, CORS по ALLOWED_ORIGIN, SQLite-first Визуал: - Убрана Markdown-разметка (VK её не рендерит) - Логотип в приветствии, карусели услуг и «Карьеры» (фото 13:8), предзагрузка медиа - Кнопки-ссылки на соцсети - Веб-форма: анимации, конфетти на успехе Промокоды добавлены, но пока ВЫКЛЮЧЕНЫ флагом PROMO_ENABLED (по умолчанию false). Co-Authored-By: Claude Opus 4.8 --- .env.example | 24 + .gitignore | 3 + bot.py | 1273 ++++++++++++++++++++++++++++++++++++++------ config.py | 83 ++- database.py | 202 ++++++- form/index.html | 11 + form/script.js | 44 +- form/server.py | 269 +++++++--- form/style.css | 159 ++++++ help_img/cs2.jpg | Bin 0 -> 33700 bytes help_img/dota2.jpg | Bin 0 -> 40769 bytes help_img/wot.jpg | Bin 0 -> 77228 bytes mailer.py | 61 +++ 13 files changed, 1860 insertions(+), 269 deletions(-) create mode 100644 help_img/cs2.jpg create mode 100644 help_img/dota2.jpg create mode 100644 help_img/wot.jpg create mode 100644 mailer.py diff --git a/.env.example b/.env.example index e7cda77..dbb8e82 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,27 @@ OPERATOR_PEER_ID=12345678 # Список ID администраторов (через запятую) для управления услугами через чат ADMIN_IDS=12345678 + +# Разрешённый источник (Origin) для CORS веб-формы (домен сайта). Для локальной отладки можно '*' +ALLOWED_ORIGIN=https://infocyber.pro + +# Промокоды на скидку. Пока выключены. Поставь true, когда захочешь их выдавать клиентам. +PROMO_ENABLED=false + +# ===== Email (SMTP) для уведомлений о заявках ===== +# Адрес SMTP-сервера почты (например, smtp.yandex.ru / smtp.gmail.com / smtp.mail.ru) +SMTP_HOST= +# Порт: 465 для SSL, 587 для STARTTLS +SMTP_PORT=465 +# Логин (полный email) и пароль (для Яндекс/Gmail — пароль приложения, не основной!) +SMTP_USER= +SMTP_PASSWORD= +# От кого отправлять (по умолчанию = SMTP_USER) +MAIL_FROM= +# Куда отправлять заявки (можно несколько через запятую) +MAIL_TO=group-zakaz@infocyber.pro +# Куда отправлять отклики на вакансии/набор в состав. Пусто = на тот же адрес, что MAIL_TO. +# Можно вынести на отдельный HR-адрес, напр. hr@infocyber.pro +HR_MAIL_TO= +# true — SSL (порт 465), false — STARTTLS (порт 587) +SMTP_USE_SSL=true diff --git a/.gitignore b/.gitignore index d0cbd5c..e44fe6a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ __pycache__/ # Logs *.log + +# Резервные копии исходных картинок (до кадрирования) +help_img/_orig/ diff --git a/bot.py b/bot.py index c8e6dbb..1149b5d 100644 --- a/bot.py +++ b/bot.py @@ -1,6 +1,11 @@ import json import re +import time +import random +import string import logging +import threading +from collections import defaultdict from typing import Dict, Any, Optional # Нам понадобятся библиотеки vk_api. @@ -14,6 +19,7 @@ except ImportError: from database import DatabaseManager import config +import mailer # Настройка логирования logging.basicConfig( @@ -25,8 +31,40 @@ logger = logging.getLogger("VK_Bot_IKP") # Инициализация базы данных сессий db = DatabaseManager() -# Регулярное выражение для базовой валидации номера телефона -PHONE_REGEX = re.compile(r"^\+?[78]\d{10}$") +# Анти-флуд: максимум сообщений от одного пользователя за окно времени +FLOOD_MAX_MSGS = 12 +FLOOD_WINDOW_SEC = 10 +# Кулдаун между уведомлениями оператора о заявке/отклике от одного пользователя (сек) +NOTIFY_COOLDOWN_SEC = 120 + +# Регулярные выражения для валидации +# Российский номер для нормализации 8XXX / 7XXX -> +7XXX +RU_PHONE_REGEX = re.compile(r"^\+?[78]\d{10}$") +# Международный формат (E.164): + и 10–15 цифр. Принимаем и иностранные номера. +INTL_PHONE_REGEX = re.compile(r"^\+?\d{10,15}$") +# Имя: буквы (кириллица/латиница), пробел, дефис, апостроф; длина 2–50 +NAME_REGEX = re.compile(r"^[A-Za-zА-Яа-яЁё][A-Za-zА-Яа-яЁё \-']{1,49}$") + + +def normalize_phone(text: str) -> Optional[str]: + """Очищает и нормализует телефон. Возвращает нормализованный номер или None, если формат неверен.""" + phone_cleaned = re.sub(r"[\s\-\(\)]", "", text) + + # Российские номера приводим к виду +7XXXXXXXXXX + if phone_cleaned.startswith("8") and len(phone_cleaned) == 11: + phone_cleaned = "+7" + phone_cleaned[1:] + elif phone_cleaned.startswith("7") and len(phone_cleaned) == 11: + phone_cleaned = "+" + phone_cleaned + + # Принимаем как российский, так и любой международный номер в формате E.164 + if RU_PHONE_REGEX.match(phone_cleaned) or INTL_PHONE_REGEX.match(phone_cleaned): + return phone_cleaned + return None + + +def is_valid_name(text: str) -> bool: + """Имя должно состоять только из букв, пробела, дефиса или апострофа (без цифр и спецсимволов).""" + return bool(NAME_REGEX.match(text.strip())) # ========================================== @@ -39,12 +77,66 @@ def get_main_keyboard() -> str: # Кнопка начать опрос (зеленая) keyboard.add_button("Заполнить заявку 📝", color=VkKeyboardColor.PRIMARY) keyboard.add_line() + # Карьера в ИКП (набор игроков и сотрудников) + keyboard.add_button("Карьера в ИКП 💼", color=VkKeyboardColor.POSITIVE) + keyboard.add_line() # Кнопки FAQ и оператора keyboard.add_button("Частые вопросы ❓", color=VkKeyboardColor.SECONDARY) 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.PRIMARY) + keyboard.add_line() + keyboard.add_button("Сотрудником 💼", color=VkKeyboardColor.PRIMARY) + keyboard.add_line() + keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY) + return keyboard.get_keyboard() + + +def get_vacancy_discipline_keyboard() -> str: + """Выбор дисциплины для набора в состав.""" + keyboard = VkKeyboard(one_time=False) + keyboard.add_button("Counter-Strike 2 🔫", color=VkKeyboardColor.SECONDARY) + keyboard.add_button("Dota 2 🎮", color=VkKeyboardColor.SECONDARY) + keyboard.add_line() + keyboard.add_button("World of Tanks 🛡️", color=VkKeyboardColor.SECONDARY) + keyboard.add_line() + keyboard.add_button("Отменить ❌", color=VkKeyboardColor.NEGATIVE) + return keyboard.get_keyboard() + + +def get_vacancy_position_keyboard() -> str: + """Выбор должности для найма сотрудника.""" + keyboard = VkKeyboard(one_time=False) + keyboard.add_button("SMM / контент", 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.NEGATIVE) + 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) @@ -54,6 +146,22 @@ def get_faq_keyboard() -> str: 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.PRIMARY) + 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("Discord 🎧", "https://discord.gg/z92mAw7KEe") + keyboard.add_line() + keyboard.add_openlink_button("ВКонтакте 🌐", "https://vk.com/ikpro") + keyboard.add_line() keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.PRIMARY) return keyboard.get_keyboard() @@ -85,6 +193,42 @@ def get_binary_keyboard(show_skip: bool = False) -> str: 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.SECONDARY) + keyboard.add_button("Сменить телефон 📞", color=VkKeyboardColor.SECONDARY) + keyboard.add_line() + keyboard.add_button("Заполнить заново 🔄", color=VkKeyboardColor.SECONDARY) + keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE) + return keyboard.get_keyboard() + + +def get_calltime_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) + 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) @@ -126,6 +270,8 @@ def get_edit_menu_keyboard() -> str: 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.PRIMARY) return keyboard.get_keyboard() @@ -140,6 +286,14 @@ class VKBotApplication: self.vk_session = None self.vk = None self.longpoll = None + # Тексты рассылок, ожидающих подтверждения админом: {admin_id: text} + self._pending_broadcasts: Dict[int, str] = {} + # Кэш загруженных в VK фото: {путь_к_файлу: {"attachment": "...", "raw": "..."}} + self._photo_cache: Dict[str, Dict[str, str]] = {} + # Анти-флуд и анти-спам (in-memory, сбрасываются при перезапуске) + self._msg_times: Dict[int, list] = defaultdict(list) # user_id -> метки времени сообщений + self._flood_warned: Dict[int, float] = {} # user_id -> когда предупредили о флуде + self._last_notify: Dict[int, float] = {} # user_id -> время последнего уведомления оператора # Инициализируем динамические услуги из базы данных config.SERVICES = db.get_services() @@ -150,13 +304,16 @@ class VKBotApplication: self.vk = self.vk_session.get_api() self.longpoll = VkBotLongPoll(self.vk_session, config.GROUP_ID) logger.info("Успешное подключение к VK API!") + # Предзагрузка картинок в фоне, чтобы карусель не тормозила при первом показе + threading.Thread(target=self._prewarm_media, daemon=True).start() except Exception as e: logger.error(f"Ошибка авторизации в VK API: {e}") - def send_message(self, peer_id: int, message: str, keyboard: Optional[str] = None, attachment: Optional[str] = None): + def send_message(self, peer_id: int, message: str, keyboard: Optional[str] = None, + attachment: Optional[str] = None, template: Optional[str] = None): """Отправка сообщения пользователю с сохранением в лог.""" db.log_message(peer_id, "bot", message) - + if self.vk: try: params = { @@ -168,18 +325,51 @@ class VKBotApplication: params["keyboard"] = keyboard if attachment: params["attachment"] = attachment + if template: + params["template"] = template self.vk.messages.send(**params) except Exception as e: logger.error(f"Не удалось отправить сообщение пользователю {peer_id}: {e}") else: attachment_str = f", Attachment: {attachment}" if attachment else "" - logger.info(f"[MOCK SEND] to {peer_id}: {message} (Keyboard: {'Да' if keyboard else 'Нет'}{attachment_str})") + tmpl_str = ", Carousel: Да" if template else "" + logger.info(f"[MOCK SEND] to {peer_id}: {message} (Keyboard: {'Да' if keyboard else 'Нет'}{attachment_str}{tmpl_str})") + + def _is_flooding(self, user_id: int) -> bool: + """True, если пользователь шлёт сообщения слишком часто (защита от флуда/DDoS).""" + now = time.time() + times = [t for t in self._msg_times[user_id] if now - t < FLOOD_WINDOW_SEC] + times.append(now) + self._msg_times[user_id] = times + return len(times) > FLOOD_MAX_MSGS + + def _notify_allowed(self, user_id: int) -> bool: + """Антиспам: не чаще раза в NOTIFY_COOLDOWN_SEC уведомляем оператора об одном пользователе.""" + now = time.time() + if now - self._last_notify.get(user_id, 0) < NOTIFY_COOLDOWN_SEC: + return False + self._last_notify[user_id] = now + return True def handle_message(self, user_id: int, text: str): """Маршрутизатор входящих сообщений на основе состояний пользователя.""" text_clean = text.strip() text_lower = text_clean.lower() - + + # Анти-флуд: шквал сообщений от одного пользователя не обрабатываем (админы — исключение) + if not (hasattr(config, "ADMIN_IDS") and user_id in config.ADMIN_IDS): + if self._is_flooding(user_id): + now = time.time() + # Предупреждаем максимум раз за окно, чтобы само предупреждение не стало спамом + if now - self._flood_warned.get(user_id, 0) > FLOOD_WINDOW_SEC: + self._flood_warned[user_id] = now + logger.warning(f"Флуд от пользователя {user_id} — сообщения игнорируются") + self.send_message( + user_id, + "⏳ Слишком много сообщений подряд. Пожалуйста, подождите немного." + ) + return + # Логируем входящее сообщение от пользователя db.log_message(user_id, "user", text_clean) @@ -201,6 +391,22 @@ class VKBotApplication: elif text_lower.startswith("удал "): self._handle_admin_delete(user_id, text_clean) return + elif text_lower in ["стата", "статистика", "/stats"]: + self._handle_admin_stats(user_id) + return + elif text_lower.startswith("рассылка"): + self._handle_admin_broadcast(user_id, text_clean) + return + + # Глобальная отписка от рассылки (доступна в любом состоянии) + if text_lower in ["отписаться от рассылки 🔕", "отписаться от рассылки", "отписаться"]: + db.update_user_field(user_id, "consent_2", "Не согласен") + self.send_message( + user_id, + "🔕 Вы отписаны от рассылки. Рекламные сообщения больше присылать не будем. " + "Это не влияет на ваши заявки и поддержку." + ) + return logger.info(f"User {user_id} in state {state} sent: {text_clean}") @@ -214,15 +420,36 @@ class VKBotApplication: ) return + # Глобальная отмена отклика на вакансию + if state.startswith("VAC_") and text_lower in ["отменить ❌", "отмена", "отменить"]: + db.clear_vac_data(user_id) + db.update_user_state(user_id, "FREE_CHAT") + self.send_message( + user_id, + "Отклик отменён. Вы вернулись в главное меню.", + keyboard=get_main_keyboard() + ) + return + # Маршрутизация по состояниям (FSM) if state == "FREE_CHAT": self._handle_free_chat(user_id, text_clean, text_lower) + elif state == "FORM_RETURNING": + self._handle_form_returning(user_id, text_clean, text_lower, user_data) + elif state == "FORM_RET_NAME": + self._handle_form_ret_name(user_id, text_clean) + elif state == "FORM_RET_PHONE": + self._handle_form_ret_phone(user_id, text_clean) elif state == "FORM_NAME": self._handle_form_name(user_id, text_clean) elif state == "FORM_PHONE": self._handle_form_phone(user_id, text_clean) elif state == "FORM_QUESTION": self._handle_form_question(user_id, text_clean, user_data) + elif state == "FORM_CALLTIME": + self._handle_form_calltime(user_id, text_clean) + elif state == "FORM_EDIT_CALLTIME": + self._handle_form_edit_calltime(user_id, text_clean) elif state == "FORM_CONFIRM_QUESTION": self._handle_form_confirm_question(user_id, text_clean, user_data) elif state == "FORM_REVIEW": @@ -241,6 +468,26 @@ class VKBotApplication: self._handle_form_consent_2(user_id, text_clean, text_lower) elif state == "FORM_CONSENT_3": self._handle_form_consent_3(user_id, text_clean, text_lower, user_data) + elif state == "VAC_CHOOSE": + self._handle_vac_choose(user_id, text_clean, text_lower) + elif state == "VAC_P_DISCIPLINE": + self._handle_vac_p_discipline(user_id, text_clean, text_lower) + elif state == "VAC_P_RANK": + self._handle_vac_p_rank(user_id, text_clean) + elif state == "VAC_P_ROLE": + self._handle_vac_p_role(user_id, text_clean) + elif state == "VAC_P_EXP": + self._handle_vac_p_exp(user_id, text_clean) + elif state == "VAC_P_CONTACT": + self._handle_vac_p_contact(user_id, text_clean) + elif state == "VAC_W_POSITION": + self._handle_vac_w_position(user_id, text_clean, text_lower) + elif state == "VAC_W_POSITION_OTHER": + self._handle_vac_w_position_other(user_id, text_clean) + elif state == "VAC_W_EXP": + self._handle_vac_w_exp(user_id, text_clean) + elif state == "VAC_W_CONTACT": + self._handle_vac_w_contact(user_id, text_clean) elif state == "OPERATOR_CHAT": self._handle_operator_chat(user_id, text_clean, text_lower) else: @@ -261,12 +508,27 @@ class VKBotApplication: # 1. Запуск формы опроса if text_lower in ["заполнить заявку 📝", "заявка", "начать опрос", "опрос"]: - db.update_user_state(user_id, "FORM_NAME") + user_data = db.get_user_data(user_id) + # Если клиент уже оставлял данные — предлагаем подтвердить их, а не вводить заново + if user_data.get("name") and user_data.get("phone"): + db.update_user_state(user_id, "FORM_RETURNING") + self._send_returning_prompt(user_id, user_data) + else: + db.update_user_state(user_id, "FORM_NAME") + self.send_message( + user_id, + "📝 Начинаем заполнение заявки!\n\n" + "Шаг 1 из 3: Как к вам обращаться? Пожалуйста, введите ваше имя.", + keyboard=get_cancel_keyboard() + ) + return + + # 1.5 Площадки ИКП — кнопки-ссылки + if text_lower in ["наши соцсети 🔗", "наши соцсети", "соцсети", "площадки", "ссылки"]: self.send_message( user_id, - "📝 Начинаем заполнение заявки!\n\n" - "Шаг 1 из 3: Как к вам обращаться? Пожалуйста, введите ваше имя.", - keyboard=get_cancel_keyboard() + "🔗 Мы на площадках ИКП — заходите, подписывайтесь и смотрите трансляции 👇", + keyboard=get_socials_keyboard() ) return @@ -288,6 +550,13 @@ class VKBotApplication: ) return + # 3.5 Раздел "Карьера в ИКП" (набор игроков и сотрудников) + if text_lower in ["карьера в икп 💼", "карьера", "вакансии", "вакансия", "работа", "набор", "хочу в команду"]: + db.clear_vac_data(user_id) + db.update_user_state(user_id, "VAC_CHOOSE") + self._send_career_choice(user_id) + return + # 4. Вызов оператора if text_lower in ["позвать оператора 🧑‍💼", "позвать оператора", "оператор", "менеджер", "человек", "позови оператора", "связь с оператором"]: self._handoff_to_operator(user_id) @@ -303,10 +572,12 @@ class VKBotApplication: if text_lower in ["начать", "привет", "здравствуйте", "ку", "hi", "hello"]: self.send_message( user_id, - "👋 Здравствуйте! Я умный чат-бот школы киберспорта 'Cyber Pro'.\n\n" - "Я могу помочь вам быстро оформить заявку на наши услуги или ответить на частые вопросы. " - "Выберите действие на кнопках ниже или просто напишите ваш вопрос!", - keyboard=get_main_keyboard() + "👋 Здравствуйте! Это чат-бот киберспортивной организации ИКП — Игра. Киберспорт. Профессионалы.\n\n" + "Я помогу быстро оформить заявку на тренировки, рассказать об услугах или связать вас с командой. " + "Также у нас открыт набор игроков и сотрудников — загляните в раздел «Карьера в ИКП 💼».\n\n" + "Выберите действие на кнопках ниже 👇", + keyboard=get_main_keyboard(), + attachment=self.get_logo_attachment(user_id) ) return @@ -318,13 +589,90 @@ class VKBotApplication: keyboard=get_main_keyboard() ) - def _handle_form_name(self, user_id: int, text: str): - """Сохранение имени и переход к номеру телефона.""" - # Валидация имени: длина от 2 до 50 символов - if len(text) < 2 or len(text) > 50: + def _send_returning_prompt(self, user_id: int, user_data: Dict[str, Any]): + """Приветствие для клиента, чьи имя и телефон уже сохранены.""" + name = user_data.get("name") + phone = user_data.get("phone") + self.send_message( + user_id, + f"Здравствуйте! Мы вас помним, {name}. 👋\n\n" + f"Ваш номер телефона по-прежнему {phone}?\n\n" + "Если всё верно — нажмите «Да, всё верно ✅», и сразу перейдём к выбору услуги. " + "Либо измените данные или заполните заявку заново.", + keyboard=get_returning_keyboard() + ) + + def _handle_form_returning(self, user_id: int, text: str, text_lower: str, user_data: Dict[str, Any]): + """Развилка для возвращающегося клиента: подтвердить данные / сменить / заполнить заново.""" + if text_lower in ["да, всё верно ✅", "да, все верно", "да", "верно", "всё верно"]: + # Данные подтверждены — переходим сразу к выбору услуги + db.update_user_state(user_id, "FORM_QUESTION") + self._send_services_prompt(user_id, prefix="Отлично! Перейдём к выбору услуги.\n\n") + elif text_lower in ["сменить имя 👤", "сменить имя", "имя"]: + db.update_user_state(user_id, "FORM_RET_NAME") + self.send_message(user_id, "👤 Введите ваше новое имя:", keyboard=get_cancel_keyboard()) + elif text_lower in ["сменить телефон 📞", "сменить телефон", "телефон"]: + db.update_user_state(user_id, "FORM_RET_PHONE") self.send_message( user_id, - "⚠️ Имя выглядит слишком коротким или длинным. Пожалуйста, введите корректное имя.", + "📞 Введите ваш новый номер телефона:", + keyboard=get_cancel_keyboard() + ) + elif text_lower in ["заполнить заново 🔄", "заполнить заново", "заново"]: + db.reset_form_full(user_id) + db.update_user_state(user_id, "FORM_NAME") + self.send_message( + user_id, + "📝 Хорошо, заполним заявку с нуля!\n\n" + "Шаг 1 из 3: Как к вам обращаться? Пожалуйста, введите ваше имя.", + keyboard=get_cancel_keyboard() + ) + else: + self._send_returning_prompt(user_id, user_data) + + def _handle_form_ret_name(self, user_id: int, text: str): + """Смена имени у возвращающегося клиента → обратно к подтверждению.""" + if not is_valid_name(text): + self.send_message( + user_id, + "⚠️ Имя должно содержать только буквы (без цифр и символов) и быть длиной от 2 до 50 знаков. " + "Пожалуйста, введите корректное имя.", + keyboard=get_cancel_keyboard() + ) + return + db.update_user_field(user_id, "name", text.strip()) + db.update_user_state(user_id, "FORM_RETURNING") + self.send_message(user_id, "✅ Имя обновлено!") + self._send_returning_prompt(user_id, db.get_user_data(user_id)) + + def _handle_form_ret_phone(self, user_id: int, text: str): + """Смена телефона у возвращающегося клиента → обратно к подтверждению.""" + phone_cleaned = normalize_phone(text) + if not phone_cleaned: + self.send_message( + user_id, + "⚠️ Некорректный формат телефона. Укажите российский номер (+79991234567 или 89991234567) " + "либо международный в формате +<код страны><номер>.", + keyboard=get_cancel_keyboard() + ) + return + db.update_user_field(user_id, "phone", phone_cleaned) + db.update_user_state(user_id, "FORM_RETURNING") + self.send_message(user_id, "✅ Телефон обновлён!") + self._send_returning_prompt(user_id, db.get_user_data(user_id)) + + def _send_services_prompt(self, user_id: int, prefix: str = ""): + """Отправляет услуги каруселью (с фолбэком на текст).""" + self._send_services(user_id, prefix) + + def _handle_form_name(self, user_id: int, text: str): + """Сохранение имени и переход к номеру телефона.""" + # Валидация имени: только буквы/пробел/дефис, длина 2–50 + if not is_valid_name(text): + self.send_message( + user_id, + "⚠️ Имя должно содержать только буквы (без цифр и символов) и быть длиной от 2 до 50 знаков. " + "Пожалуйста, введите корректное имя.", keyboard=get_cancel_keyboard() ) return @@ -342,20 +690,14 @@ class VKBotApplication: def _handle_form_phone(self, user_id: int, text: str): """Сохранение телефона и переход к выбору услуги.""" - # Очищаем телефон от пробелов, скобок и дефисов для валидации - phone_cleaned = re.sub(r"[\s\-\(\)]", "", text) - - # Если телефон начинается с 8, заменяем на +7 для стандартизации - if phone_cleaned.startswith("8") and len(phone_cleaned) == 11: - phone_cleaned = "+7" + phone_cleaned[1:] - elif phone_cleaned.startswith("7") and len(phone_cleaned) == 11: - phone_cleaned = "+" + phone_cleaned + phone_cleaned = normalize_phone(text) - # Валидация регулярным выражением - if not PHONE_REGEX.match(phone_cleaned): + # Валидация (российский или международный формат) + if not phone_cleaned: self.send_message( user_id, - "⚠️ Некорректный формат телефона. Пожалуйста, отправьте номер в формате +79991234567.", + "⚠️ Некорректный формат телефона. Укажите российский номер (+79991234567 или 89991234567) " + "либо международный в формате +<код страны><номер>.", keyboard=get_cancel_keyboard() ) return @@ -363,24 +705,9 @@ class VKBotApplication: # Сохраняем телефон в БД db.update_user_field(user_id, "phone", phone_cleaned) db.update_user_state(user_id, "FORM_QUESTION") - - # Формируем список услуг с ценами - services_text = ( - "✅ Телефон принят!\n\n" - "Шаг 3 из 3: Какая услуга вас интересует?\n\n" - "📋 Доступные услуги и расценки:\n\n" - ) - for key, svc in config.SERVICES.items(): - services_text += f"{key}️⃣ **{svc['name']}**\n" - services_text += f" 💰 Стоимость: {svc['price']}\n" - services_text += f" 📝 {svc['description']}\n\n" - services_text += "Пожалуйста, выберите нужную услугу, нажав на кнопку на клавиатуре или отправив её номер/название." - self.send_message( - user_id, - services_text, - keyboard=get_services_keyboard() - ) + # Показываем услуги каруселью (с фолбэком на текст) + self._send_services(user_id, "✅ Телефон принят!\n\nШаг 3 из 3: какое направление вас интересует?\n\n") def _handle_form_question(self, user_id: int, text: str, user_data: Dict[str, Any]): """Обработка выбора услуги с прямым переходом к проверке данных.""" @@ -389,8 +716,9 @@ class VKBotApplication: # 1. Прямое сопоставление синонимов дисциплин для надежности synonyms = { - "1": ["дота", "dota", "дотку", "дотка", "dota2"], - "2": ["кс", "cs", "ксго", "кс2", "csgo", "cs2", "контра", "кондра", "counter"] + "1": ["кс", "cs", "ксго", "кс2", "csgo", "cs2", "контра", "кондра", "counter", "counter-strike", "counter strike"], + "2": ["дота", "dota", "дотку", "дотка", "dota2", "дота2", "доту"], + "3": ["танки", "танк", "wot", "world of tanks", "ворлд оф танкс", "вот", "tanks"] } for key_opt, words in synonyms.items(): if any(w in text_lower for w in words): @@ -417,10 +745,14 @@ class VKBotApplication: if selected_service: # Сохраняем временный выбор в поле question db.update_user_field(user_id, "question", f"{selected_service['name']} ({selected_service['price']})") - db.update_user_state(user_id, "FORM_REVIEW") - - updated_data = db.get_user_data(user_id) - self._send_review_message(user_id, updated_data) + # Переходим к выбору удобного времени для звонка + db.update_user_state(user_id, "FORM_CALLTIME") + self.send_message( + user_id, + f"Отличный выбор — {selected_service['name']}!\n\n" + "🕒 Когда вам удобно, чтобы менеджер с вами связался?", + keyboard=get_calltime_keyboard() + ) else: # Формируем список услуг с ценами для повторного показа services_text = ( @@ -428,7 +760,7 @@ class VKBotApplication: "Пожалуйста, выберите одну из доступных услуг на клавиатуре или отправьте её номер/название:\n\n" ) for key, svc in config.SERVICES.items(): - services_text += f"{key}️⃣ **{svc['name']}**\n" + services_text += f"{key}️⃣ {svc['name']}\n" services_text += f" 💰 Стоимость: {svc['price']}\n" services_text += f" 📝 {svc['description']}\n\n" @@ -443,17 +775,48 @@ class VKBotApplication: db.update_user_state(user_id, "FORM_REVIEW") self._send_review_message(user_id, user_data) + def _handle_form_calltime(self, user_id: int, text: str): + """Сохранение удобного времени звонка и переход к финальной проверке.""" + call_time = text.strip() + if len(call_time) < 2 or len(call_time) > 100: + self.send_message( + user_id, + "🕒 Пожалуйста, выберите время на клавиатуре или коротко напишите, когда вам удобно.", + keyboard=get_calltime_keyboard() + ) + return + db.update_user_field(user_id, "call_time", call_time) + db.update_user_state(user_id, "FORM_REVIEW") + self._send_review_message(user_id, db.get_user_data(user_id)) + + def _handle_form_edit_calltime(self, user_id: int, text: str): + """Сохранение нового времени звонка и возврат к проверке.""" + call_time = text.strip() + if len(call_time) < 2 or len(call_time) > 100: + self.send_message( + user_id, + "🕒 Пожалуйста, выберите время на клавиатуре или коротко напишите, когда вам удобно.", + keyboard=get_calltime_keyboard() + ) + return + db.update_user_field(user_id, "call_time", call_time) + db.update_user_state(user_id, "FORM_REVIEW") + self.send_message(user_id, "✅ Время звонка обновлено!") + self._send_review_message(user_id, db.get_user_data(user_id)) + def _send_review_message(self, user_id: int, user_data: Dict[str, Any]): """Отправляет сводную карточку проверки данных перед финальным шагом.""" name = user_data.get("name", "Не указано") phone = user_data.get("phone", "Не указано") question = user_data.get("question", "Не указано") - + call_time = user_data.get("call_time") or "Не указано" + review_text = ( - "🔍 **Пожалуйста, проверьте правильность заполненных данных:**\n\n" - f"👤 **Имя:** {name}\n" - f"📞 **Телефон:** {phone}\n" - f"🎮 **Выбранная услуга:** {question}\n\n" + "🔍 Пожалуйста, проверьте правильность заполненных данных:\n\n" + f"👤 Имя: {name}\n" + f"📞 Телефон: {phone}\n" + f"🎮 Выбранная услуга: {question}\n" + f"🕒 Удобное время звонка: {call_time}\n\n" "Если всё верно, нажмите «Да, всё верно! ✅» для продолжения. Если хотите исправить — нажмите «Изменить данные 🔄»." ) self.send_message(user_id, review_text, keyboard=get_review_keyboard()) @@ -472,7 +835,7 @@ class VKBotApplication: # Отправляем значительно сокращенное и дружелюбное сообщение-согласие self.send_message( user_id, - "📄 **Согласие с условиями и офертой**\n\n" + "📄 Согласие с условиями и офертой\n\n" "Для завершения отправки заявки подтвердите, пожалуйста, ваше согласие с условиями Публичной оферты, " "Политикой обработки персональных данных и записью разговора.\n\n" "«Я подтверждаю, что ознакомлен(а) с договором оферты (прикреплен ниже), принимаю его условия, " @@ -486,7 +849,7 @@ class VKBotApplication: db.update_user_state(user_id, "FORM_EDIT_MENU") self.send_message( user_id, - "🔄 **Меню изменения данных**\n\n" + "🔄 Меню изменения данных\n\n" "Пожалуйста, выберите на клавиатуре ниже, какое поле вы хотите изменить:", keyboard=get_edit_menu_keyboard() ) @@ -520,10 +883,10 @@ class VKBotApplication: db.update_user_state(user_id, "FORM_EDIT_QUESTION") services_text = ( - "🎮 **Выберите новую услугу из списка:**\n\n" + "🎮 Выберите новую услугу из списка:\n\n" ) for key, svc in config.SERVICES.items(): - services_text += f"{key}️⃣ **{svc['name']}**\n" + services_text += f"{key}️⃣ {svc['name']}\n" services_text += f" 💰 Стоимость: {svc['price']}\n\n" self.send_message( @@ -531,7 +894,14 @@ class VKBotApplication: services_text, keyboard=get_services_keyboard() ) - elif text_lower in ["назад к проверке ↩️", "назад к проверке", "назад", "вернуть", "4"]: + elif text_lower in ["изменить время 🕒", "изменить время", "время", "4"]: + db.update_user_state(user_id, "FORM_EDIT_CALLTIME") + self.send_message( + user_id, + "🕒 Когда вам удобно, чтобы менеджер с вами связался?", + keyboard=get_calltime_keyboard() + ) + elif text_lower in ["назад к проверке ↩️", "назад к проверке", "назад", "вернуть", "5"]: db.update_user_state(user_id, "FORM_REVIEW") self._send_review_message(user_id, user_data) else: @@ -543,10 +913,11 @@ class VKBotApplication: def _handle_form_edit_name(self, user_id: int, text: str): """Сохранение нового имени и возврат к проверке.""" - if len(text) < 2 or len(text) > 50: + if not is_valid_name(text): self.send_message( user_id, - "⚠️ Имя выглядит слишком коротким или длинным. Пожалуйста, введите корректное имя.", + "⚠️ Имя должно содержать только буквы (без цифр и символов) и быть длиной от 2 до 50 знаков. " + "Пожалуйста, введите корректное имя.", keyboard=get_cancel_keyboard() ) return @@ -560,17 +931,13 @@ class VKBotApplication: def _handle_form_edit_phone(self, user_id: int, text: str): """Сохранение нового телефона и возврат к проверке.""" - phone_cleaned = re.sub(r"[\s\-\(\)]", "", text) - - if phone_cleaned.startswith("8") and len(phone_cleaned) == 11: - phone_cleaned = "+7" + phone_cleaned[1:] - elif phone_cleaned.startswith("7") and len(phone_cleaned) == 11: - phone_cleaned = "+" + phone_cleaned + phone_cleaned = normalize_phone(text) - if not PHONE_REGEX.match(phone_cleaned): + if not phone_cleaned: self.send_message( user_id, - "⚠️ Некорректный формат телефона. Пожалуйста, отправьте номер в формате +79991234567.", + "⚠️ Некорректный формат телефона. Укажите российский номер (+79991234567 или 89991234567) " + "либо международный в формате +<код страны><номер>.", keyboard=get_cancel_keyboard() ) return @@ -589,8 +956,9 @@ class VKBotApplication: # 1. Прямое сопоставление синонимов дисциплин для надежности synonyms = { - "1": ["дота", "dota", "дотку", "дотка", "dota2"], - "2": ["кс", "cs", "ксго", "кс2", "csgo", "cs2", "контра", "кондра", "counter"] + "1": ["кс", "cs", "ксго", "кс2", "csgo", "cs2", "контра", "кондра", "counter", "counter-strike", "counter strike"], + "2": ["дота", "dota", "дотку", "дотка", "dota2", "дота2", "доту"], + "3": ["танки", "танк", "wot", "world of tanks", "ворлд оф танкс", "вот", "tanks"] } for key_opt, words in synonyms.items(): if any(w in text_lower for w in words): @@ -626,7 +994,7 @@ class VKBotApplication: "Пожалуйста, выберите одну из доступных услуг на клавиатуре:\n\n" ) for key, svc in config.SERVICES.items(): - services_text += f"{key}️⃣ **{svc['name']}**\n" + services_text += f"{key}️⃣ {svc['name']}\n" services_text += f" 💰 Стоимость: {svc['price']}\n\n" self.send_message( @@ -669,7 +1037,10 @@ class VKBotApplication: saved_doc = self.vk.docs.save(file=response_json["file"], title=title) doc_info = saved_doc[0] if isinstance(saved_doc, list) else saved_doc - + # docs.save может вернуть {'type': 'doc', 'doc': {...}} — берём вложенный объект + if isinstance(doc_info, dict) and "doc" in doc_info: + doc_info = doc_info["doc"] + attachment = f"doc{doc_info['owner_id']}_{doc_info['id']}" self._cached_doc_attachment = attachment logger.info(f"Документ загружен успешно: {attachment}") @@ -678,48 +1049,203 @@ class VKBotApplication: logger.error(f"Ошибка при загрузке документа в VK API: {e}") return None + def _upload_photo(self, peer_id: int, image_path: str) -> Optional[Dict[str, str]]: + """Загружает фото в VK для прикрепления к сообщению. Возвращает {'attachment','raw'} или None.""" + if image_path in self._photo_cache: + return self._photo_cache[image_path] + import os + if not self.vk or not os.path.exists(image_path): + return None + try: + import requests + upload_server = self.vk.photos.getMessagesUploadServer(peer_id=peer_id) + upload_url = upload_server["upload_url"] + with open(image_path, "rb") as f: + response = requests.post(upload_url, files={"photo": (os.path.basename(image_path), f)}) + r = response.json() + saved = self.vk.photos.saveMessagesPhoto(photo=r["photo"], server=r["server"], hash=r["hash"]) + info = saved[0] if isinstance(saved, list) else saved + raw = f"{info['owner_id']}_{info['id']}" + result = {"attachment": f"photo{raw}", "raw": raw} + self._photo_cache[image_path] = result + logger.info(f"Фото загружено в VK: {result['attachment']}") + return result + except Exception as e: + logger.error(f"Ошибка загрузки фото {image_path} в VK: {e}") + return None + + def get_logo_attachment(self, peer_id: int) -> Optional[str]: + """Attachment-строка с логотипом ИКП для прикрепления к приветствию.""" + import os + logo_path = os.path.join(os.path.dirname(__file__), "help_img", "Logo.jpg") + info = self._upload_photo(peer_id, logo_path) + return info["attachment"] if info else None + + def _prewarm_media(self): + """Заранее (при старте) загружает логотип и картинки дисциплин в VK, + чтобы первое приветствие и первая карусель открывались мгновенно.""" + import os + try: + peer = config.OPERATOR_PEER_ID + base = os.path.join(os.path.dirname(__file__), "help_img") + for fn in ("Logo.jpg", "cs2.jpg", "dota2.jpg", "wot.jpg"): + path = os.path.join(base, fn) + if os.path.exists(path): + self._upload_photo(peer, path) + logger.info("Медиа предзагружены в VK — карусель и логотип будут открываться быстро.") + except Exception as e: + logger.error(f"Не удалось предзагрузить медиа: {e}") + + @staticmethod + def _card_description(svc: Dict[str, str]) -> str: + """Компактное описание карточки карусели: цена + краткая суть (лимит VK — 80 символов).""" + price = str(svc.get("price", "")).strip() + # Берём первую фразу описания (до точки) как короткую суть + blurb = str(svc.get("description", "")).strip().split(".")[0].strip() + text = f"{price} · {blurb}" if blurb else price + if len(text) > 80: + text = text[:79].rstrip(" ,;·") + "…" + return text + + def _build_services_carousel(self, peer_id: int) -> Optional[str]: + """Собирает VK-карусель из доступных услуг. Возвращает JSON-строку шаблона или None.""" + import os + base = os.path.join(os.path.dirname(__file__), "help_img") + # Картинки дисциплин по id услуги. ВАЖНО: VK требует пропорцию 13:8 (напр. 650×400). + # Логотип квадратный и не подходит — поэтому фото берём ТОЛЬКО если есть спец-файл дисциплины. + img_by_id = {"1": "cs2.jpg", "2": "dota2.jpg", "3": "wot.jpg"} + elements = [] + for key, svc in config.SERVICES.items(): + disc_img = os.path.join(base, img_by_id.get(key, "")) + info = self._upload_photo(peer_id, disc_img) if os.path.exists(disc_img) else None + element = { + "title": svc["name"][:80], + "description": self._card_description(svc), + "buttons": [{ + "action": {"type": "text", "label": f"Выбрать {svc['name']}"[:40]}, + "color": "primary" + }] + } + if info: + element["photo_id"] = info["raw"] + elements.append(element) + if not elements: + return None + # VK требует фото либо у всех карточек, либо ни у одной + if any("photo_id" not in e for e in elements): + for e in elements: + e.pop("photo_id", None) + return json.dumps({"type": "carousel", "elements": elements}, ensure_ascii=False) + + def _send_services(self, user_id: int, intro: str): + """Отправляет услуги каруселью (если возможно), иначе текстом-фолбэком.""" + # Полный текстовый список — для фолбэка (и лога) + full_text = intro + "📋 Доступные направления и расценки:\n\n" + for key, svc in config.SERVICES.items(): + full_text += f"{key}️⃣ {svc['name']}\n 💰 {svc['price']}\n 📝 {svc['description']}\n\n" + full_text += "Выберите направление кнопкой ниже или отправьте его номер/название." + + if self.vk: + template = self._build_services_carousel(user_id) + if template: + try: + # 1) Короткий текст + кнопка отмены. Это сразу убирает «зависшие» + # кнопки прошлого шага и даёт нормальную кнопку «Отменить». + self.send_message( + user_id, + intro + "Листайте карточки ниже 👇 и нажмите «Выбрать» под нужным направлением.", + keyboard=get_cancel_keyboard() + ) + # 2) Сама карусель (без клавиатуры — кнопка отмены сохраняется). + db.log_message(user_id, "bot", "[Карусель направлений]") + self.vk.messages.send( + peer_id=user_id, message="🎮 Выберите направление:", + template=template, random_id=get_random_id() + ) + return + except Exception as e: + logger.error(f"Карусель не отправилась, фолбэк на текст: {e}") + # Фолбэк / режим эмуляции + self.send_message(user_id, full_text, keyboard=get_services_keyboard()) + + def _build_career_carousel(self, peer_id: int) -> Optional[str]: + """Карусель выбора в разделе «Карьера»: карточки «Игроком» и «Сотрудником».""" + import os + base = os.path.join(os.path.dirname(__file__), "help_img") + cards = [ + ("career_player.jpg", "Игроком в состав 🎮", + "Покажи скилл и попади в ростер ИКП", "Игроком в состав 🎮"), + ("career_worker.jpg", "Сотрудником 💼", + "Тренер, SMM, монтаж, менеджмент, разработка", "Сотрудником 💼"), + ] + elements = [] + for img_name, title, desc, label in cards: + img_path = os.path.join(base, img_name) + info = self._upload_photo(peer_id, img_path) if os.path.exists(img_path) else None + element = { + "title": title[:80], + "description": desc[:80], + "buttons": [{"action": {"type": "text", "label": label[:40]}, "color": "primary"}] + } + if info: + element["photo_id"] = info["raw"] + elements.append(element) + if any("photo_id" not in e for e in elements): + for e in elements: + e.pop("photo_id", None) + return json.dumps({"type": "carousel", "elements": elements}, ensure_ascii=False) + + def _send_career_choice(self, user_id: int): + """Отправляет выбор в «Карьере» каруселью (с фолбэком на обычную клавиатуру).""" + intro = ("💼 Карьера в ИКП\n\n" + "Мы растём и ищем как игроков в состав, так и специалистов в команду.") + if self.vk: + template = self._build_career_carousel(user_id) + if template: + try: + self.send_message( + user_id, + intro + "\nВыберите на карточке ниже 👇, кем хотите присоединиться.", + keyboard=get_vacancy_back_keyboard() + ) + db.log_message(user_id, "bot", "[Карусель карьеры]") + self.vk.messages.send( + peer_id=user_id, message="Кем хотите присоединиться?", + template=template, random_id=get_random_id() + ) + return + except Exception as e: + logger.error(f"Карусель карьеры не отправилась, фолбэк: {e}") + # Фолбэк / эмуляция + self.send_message( + user_id, + intro + "\nКем вы хотите к нам присоединиться?", + keyboard=get_vacancy_choice_keyboard() + ) + def _handle_form_consent_1(self, user_id: int, text: str, text_lower: str): - """Обработка объединенного шага согласий.""" + """Шаг 1 — ОБЯЗАТЕЛЬНЫЕ согласия: оферта, обработка ПД и запись консультации.""" is_agree = text_lower in ["согласен(а) и подтверждаю ✅", "согласен и подтверждаю", "согласен(а) и подтверждаю", "согласен", "согласна", "да", "принимаю", "согласен(а) ✅"] is_disagree = text_lower in ["не согласен(а) ❌", "не согласен", "не согласна", "нет", "не принимаю"] if is_agree: - # Записываем все согласия как "Согласен" в базу данных для полной совместимости + # Фиксируем обязательные согласия (оферта/ПД и запись консультации) db.update_user_field(user_id, "consent_1", "Согласен") - db.update_user_field(user_id, "consent_2", "Согласен") db.update_user_field(user_id, "consent_3", "Согласен") - - # Получаем актуальные данные пользователя - updated_data = db.get_user_data(user_id) - phone = updated_data["phone"] - - # Возвращаем пользователя в главное меню - db.update_user_state(user_id, "FREE_CHAT") - # Динамически определяем время ответа в зависимости от рабочего времени (с 9:00 до 21:00) - from datetime import datetime - now = datetime.now() - if 9 <= now.hour < 21: - callback_time = "в течение 15 минут" - else: - callback_time = "сегодня утром, начиная с 9:00" - - # Отправляем подтверждение пользователю + # Переходим к ОТДЕЛЬНОМУ необязательному согласию на рассылку (требование 152-ФЗ) + db.update_user_state(user_id, "FORM_CONSENT_2") self.send_message( user_id, - "🎉 Заявка и все необходимые согласия успешно оформлены!\n\n" - f"Наш менеджер уже изучает детали и свяжется с вами {callback_time} по номеру " - f"{phone}. Спасибо за обращение!\n\n" - "Вы вернулись в главное меню бота.", - keyboard=get_main_keyboard() + "✅ Спасибо! Обязательные согласия приняты.\n\n" + "📨 Последний шаг (по желанию): хотите получать новости, акции и спецпредложения ИКП?\n\n" + "Это необязательно и никак не влияет на обработку вашей заявки.", + keyboard=get_marketing_keyboard() ) - - # Уведомляем оператора о новой заявке со всеми согласиями - self._notify_operator_about_lead(user_id, updated_data) elif is_disagree: self.send_message( user_id, - "⚠️ **Для продолжения оформления заявки необходимо принять условия Публичной оферты и дать согласие на обработку данных и запись разговора.**\n\n" + "⚠️ Для оформления заявки необходимо принять условия оферты, согласие на обработку персональных данных и запись консультации.\n\n" "Пожалуйста, подтвердите согласие кнопкой «Согласен(а) и подтверждаю ✅» или нажмите «Отменить заполнение ❌» для выхода.", keyboard=get_binary_keyboard(show_skip=False) ) @@ -731,37 +1257,84 @@ class VKBotApplication: ) def _handle_form_consent_2(self, user_id: int, text: str, text_lower: str): - """Резервный обработчик для совместимости с устаревшими состояниями в БД.""" - db.update_user_field(user_id, "consent_2", "Согласен") - db.update_user_field(user_id, "consent_3", "Согласен") - - updated_data = db.get_user_data(user_id) - phone = updated_data["phone"] - db.update_user_state(user_id, "FREE_CHAT") - - self.send_message( - user_id, - "🎉 Заявка успешно оформлена!\n\n" - f"Наш менеджер свяжется с вами по номеру {phone}. Спасибо за обращение!", - keyboard=get_main_keyboard() - ) - self._notify_operator_about_lead(user_id, updated_data) + """Шаг 2 — НЕОБЯЗАТЕЛЬНОЕ согласие на маркетинговую рассылку. Любой выбор завершает заявку.""" + is_agree = text_lower in ["согласен на рассылку ✅", "согласен на рассылку", "согласен", "согласна", "да", "хочу"] + is_decline = text_lower in ["без рассылки ➡️", "без рассылки", "не согласен", "не согласна", "нет", "не хочу", "пропустить ➡️", "пропустить"] + + if is_agree: + db.update_user_field(user_id, "consent_2", "Согласен") + self._finalize_lead(user_id) + elif is_decline: + db.update_user_field(user_id, "consent_2", "Не согласен") + self._finalize_lead(user_id) + else: + self.send_message( + user_id, + "❓ Хотите получать рассылку ИКП? Нажмите «Согласен на рассылку ✅» или «Без рассылки ➡️».", + keyboard=get_marketing_keyboard() + ) def _handle_form_consent_3(self, user_id: int, text: str, text_lower: str, user_data: Dict[str, Any]): """Резервный обработчик для совместимости с устаревшими состояниями в БД.""" db.update_user_field(user_id, "consent_3", "Согласен") - + self._finalize_lead(user_id) + + @staticmethod + def _generate_promo() -> str: + """Генерирует персональный промокод вида ИКП-AB12CD.""" + code = "".join(random.choices(string.ascii_uppercase + string.digits, k=6)) + return f"ИКП-{code}" + + def _finalize_lead(self, user_id: int): + """Завершает оформление заявки: подтверждение клиенту + уведомление оператору и на почту.""" updated_data = db.get_user_data(user_id) phone = updated_data["phone"] + call_time = updated_data.get("call_time") or "не указано" + marketing = (updated_data.get("consent_2") == "Согласен") db.update_user_state(user_id, "FREE_CHAT") - - self.send_message( - user_id, + + # Персональный промокод на скидку (пока выключен флагом config.PROMO_ENABLED) + promo = self._generate_promo() if getattr(config, "PROMO_ENABLED", False) else None + + # Динамически определяем время ответа в зависимости от рабочего времени (с 9:00 до 21:00) + from datetime import datetime + now = datetime.now() + if 9 <= now.hour < 21: + callback_time = "в течение 15 минут" + else: + callback_time = "сегодня утром, начиная с 9:00" + + confirm_msg = ( "🎉 Заявка успешно оформлена!\n\n" - f"Наш менеджер свяжется с вами по номеру {phone}. Спасибо за обращение!", - keyboard=get_main_keyboard() + f"Наш менеджер свяжется с вами ({callback_time}) по номеру {phone}, " + f"удобное время: {call_time}.\n\n" ) - self._notify_operator_about_lead(user_id, updated_data) + if promo: + confirm_msg += ( + "🎁 Ваш персональный промокод на скидку 10% на первое занятие:\n" + f"{promo}\n" + "Назовите его менеджеру при записи. " + ) + confirm_msg += "Спасибо за обращение!\n\nВы вернулись в главное меню бота." + + self.send_message(user_id, confirm_msg, keyboard=get_main_keyboard()) + + # Журналируем заявку для статистики и истории + try: + db.add_lead( + user_id=user_id, source="bot", + name=updated_data.get("name"), phone=phone, + service=updated_data.get("question"), call_time=updated_data.get("call_time"), + promo=promo, marketing=marketing + ) + except Exception as e: + logger.error(f"Не удалось записать лид в журнал: {e}") + + # Антиспам: не заваливаем оператора повторными уведомлениями от одного пользователя + if self._notify_allowed(user_id): + self._notify_operator_about_lead(user_id, updated_data, promo=promo) + else: + logger.warning(f"Подавлено повторное уведомление о заявке от {user_id} (антиспам)") def _handle_operator_chat(self, user_id: int, text: str, text_lower: str): """Поведение бота в режиме активного диалога с живым человеком.""" @@ -788,15 +1361,251 @@ class VKBotApplication: # СЦЕНАРИИ ПЕРЕДАЧИ ОПЕРАТОРУ И СВЯЗИ # ------------------------------------------ + # ------------------------------------------ + # РАЗДЕЛ "КАРЬЕРА В ИКП" (НАЙМ ИГРОКОВ И СОТРУДНИКОВ) + # ------------------------------------------ + + @staticmethod + def _valid_free_text(text: str) -> bool: + """Базовая проверка свободного ответа в отклике (не пусто, разумной длины).""" + return 1 <= len(text.strip()) <= 300 + + def _handle_vac_choose(self, user_id: int, text: str, text_lower: str): + """Выбор ветки: игрок в состав или сотрудник.""" + if text_lower in ["игроком в состав 🎮", "игроком", "игрок", "в состав", "состав"]: + db.update_user_state(user_id, "VAC_P_DISCIPLINE") + self.send_message( + user_id, + "🎮 Набор в состав\n\nШаг 1 из 5: по какой дисциплине вы играете?", + keyboard=get_vacancy_discipline_keyboard() + ) + elif text_lower in ["сотрудником 💼", "сотрудником", "сотрудник", "работником", "в команду"]: + db.update_user_state(user_id, "VAC_W_POSITION") + self.send_message( + user_id, + "💼 Набор в команду\n\nШаг 1 из 3: на какую должность вы претендуете?", + keyboard=get_vacancy_position_keyboard() + ) + elif text_lower in ["назад в меню ↩️", "назад", "меню", "в меню"]: + db.clear_vac_data(user_id) + db.update_user_state(user_id, "FREE_CHAT") + self.send_message(user_id, "🏠 Вы вернулись в главное меню.", keyboard=get_main_keyboard()) + else: + self.send_message( + user_id, + "Пожалуйста, выберите на клавиатуре: «Игроком в состав 🎮» или «Сотрудником 💼».", + keyboard=get_vacancy_choice_keyboard() + ) + + # ----- Ветка игрока ----- + + def _handle_vac_p_discipline(self, user_id: int, text: str, text_lower: str): + discipline = None + syn = { + "Counter-Strike 2": ["кс", "cs", "cs2", "csgo", "ксго", "контра", "counter"], + "Dota 2": ["дота", "dota", "дотка", "доту"], + "World of Tanks": ["танк", "wot", "tanks", "ворлд оф танкс", "вот"], + } + for name, words in syn.items(): + if any(w in text_lower for w in words): + discipline = name + break + if not discipline: + self.send_message( + user_id, + "Пожалуйста, выберите дисциплину на клавиатуре ниже.", + keyboard=get_vacancy_discipline_keyboard() + ) + return + db.set_vac_field(user_id, "discipline", discipline) + db.update_user_state(user_id, "VAC_P_RANK") + self.send_message( + user_id, + f"Отлично, {discipline}!\n\n" + "Шаг 2 из 5: укажите ваш текущий ранг / MMR / ELO " + "(например, «Faceit lvl 8», «5000 MMR» или «Легенда»).", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_p_rank(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, коротко укажите ваш ранг / MMR / ELO.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "rank", text.strip()) + db.update_user_state(user_id, "VAC_P_ROLE") + self.send_message( + user_id, + "Шаг 3 из 5: ваша роль / позиция в игре? (например, AWPer, мид, саппорт, командир)", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_p_role(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, укажите вашу роль / позицию.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "role", text.strip()) + db.update_user_state(user_id, "VAC_P_EXP") + self.send_message( + user_id, + "Шаг 4 из 5: расскажите коротко об опыте — в каких командах играли, турниры, достижения.", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_p_exp(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, опишите ваш опыт хотя бы кратко.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "experience", text.strip()) + db.update_user_state(user_id, "VAC_P_CONTACT") + self.send_message( + user_id, + "Шаг 5 из 5: как с вами связаться? Укажите телефон или Telegram (например, +79991234567 или @nickname).", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_p_contact(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, оставьте телефон или ник в Telegram для связи.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "contact", text.strip()) + self._finalize_application(user_id, "player") + + # ----- Ветка сотрудника ----- + + def _handle_vac_w_position(self, user_id: int, text: str, text_lower: str): + known = { + "smm / контент": "SMM / контент", + "монтажёр / дизайнер": "Монтажёр / дизайнер", + "менеджер": "Менеджер", + "разработчик": "Разработчик", + } + if text_lower in ["другое ✍️", "другое", "иное"]: + db.update_user_state(user_id, "VAC_W_POSITION_OTHER") + self.send_message( + user_id, + "✍️ Напишите, на какую должность вы претендуете.", + keyboard=get_vacancy_cancel_keyboard() + ) + return + # Известная кнопка или произвольный непустой текст принимаем как должность + position = known.get(text_lower) + if not position: + if not self._valid_free_text(text): + self.send_message( + user_id, + "Выберите должность на клавиатуре или нажмите «Другое ✍️».", + keyboard=get_vacancy_position_keyboard() + ) + return + position = text.strip() + db.set_vac_field(user_id, "position", position) + db.update_user_state(user_id, "VAC_W_EXP") + self.send_message( + user_id, + f"Должность: {position}.\n\n" + "Шаг 2 из 3: расскажите коротко о вашем опыте и навыках (можно ссылку на портфолио).", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_w_position_other(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, напишите желаемую должность.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "position", text.strip()) + db.update_user_state(user_id, "VAC_W_EXP") + self.send_message( + user_id, + "Шаг 2 из 3: расскажите коротко о вашем опыте и навыках (можно ссылку на портфолио).", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_w_exp(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, опишите ваш опыт хотя бы кратко.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "experience", text.strip()) + db.update_user_state(user_id, "VAC_W_CONTACT") + self.send_message( + user_id, + "Шаг 3 из 3: как с вами связаться? Укажите телефон или Telegram (например, +79991234567 или @nickname).", + keyboard=get_vacancy_cancel_keyboard() + ) + + def _handle_vac_w_contact(self, user_id: int, text: str): + if not self._valid_free_text(text): + self.send_message(user_id, "Пожалуйста, оставьте телефон или ник в Telegram для связи.", keyboard=get_vacancy_cancel_keyboard()) + return + db.set_vac_field(user_id, "contact", text.strip()) + self._finalize_application(user_id, "worker") + + def _finalize_application(self, user_id: int, track: str): + """Завершает отклик: подтверждение кандидату + уведомление HR/оператору и письмо.""" + data = db.get_vac_data(user_id) + try: + db.add_application(user_id, track, data) + except Exception as e: + logger.error(f"Не удалось записать отклик в журнал: {e}") + db.clear_vac_data(user_id) + db.update_user_state(user_id, "FREE_CHAT") + + self.send_message( + user_id, + "✅ Спасибо за отклик!\n\n" + "Мы изучим вашу анкету и свяжемся, если кандидатура подойдёт. Удачи! 🎮\n\n" + "Вы вернулись в главное меню.", + keyboard=get_main_keyboard() + ) + # Антиспам: ограничиваем частоту уведомлений об откликах от одного пользователя + if self._notify_allowed(user_id): + self._notify_hr_about_application(user_id, track, data) + else: + logger.warning(f"Подавлено повторное уведомление об отклике от {user_id} (антиспам)") + + def _notify_hr_about_application(self, user_id: int, track: str, data: Dict[str, Any]): + """Уведомление об отклике оператору в VK и на HR-почту.""" + if track == "player": + title = "Набор в состав (игрок)" + details = ( + f"Дисциплина: {data.get('discipline', '—')}\n" + f"Ранг/MMR/ELO: {data.get('rank', '—')}\n" + f"Роль/позиция: {data.get('role', '—')}\n" + f"Опыт: {data.get('experience', '—')}\n" + f"Контакт: {data.get('contact', '—')}" + ) + else: + title = "Вакансия (сотрудник)" + details = ( + f"Должность: {data.get('position', '—')}\n" + f"Опыт/навыки: {data.get('experience', '—')}\n" + f"Контакт: {data.get('contact', '—')}" + ) + + operator_msg = ( + f"🧩 Новый отклик — {title}!\n\n" + f"{details}\n\n" + f"🔗 Профиль кандидата: vk.com/id{user_id}" + ) + self.send_message(config.OPERATOR_PEER_ID, operator_msg) + + # Письмо на HR-адрес (пока совпадает с group-zakaz, см. config.HR_MAIL_TO) + try: + email_body = ( + f"Новый отклик из чат-бота ВКонтакте — {title}.\n\n" + f"{details}\n\n" + f"Профиль кандидата: https://vk.com/id{user_id}\n" + ) + mailer.send_email(f"Новый отклик — {title}", email_body, mail_to=getattr(config, "HR_MAIL_TO", None)) + except Exception as mail_err: + logger.error(f"Не удалось отправить email об отклике: {mail_err}") + def _handoff_to_operator(self, user_id: int): """Переводит пользователя в режим оператора и уведомляет команду поддержки.""" db.update_user_state(user_id, "OPERATOR_CHAT") self.send_message( user_id, - "🧑‍💼 Переключаю вас на оператора...\n\n" - "Я отправил уведомление специалисту поддержки. Он ответит вам прямо в этом диалоге в ближайшее время.\n" - "Все ваши сообщения сейчас направляются напрямую человеку. Чтобы вернуться к боту, нажмите кнопку ниже.", + "🧑‍💼 Передал ваш запрос оператору — он ответит здесь в ближайшее время.\n" + "Чтобы вернуться к боту, нажмите кнопку ниже.", keyboard=get_operator_keyboard() ) @@ -809,28 +1618,32 @@ class VKBotApplication: # Отправляем алерт оператору с историей диалога operator_msg = ( - f"🚨 **Запрос оператора!**\n" + f"🚨 Запрос оператора!\n" f"Пользователь: vk.com/id{user_id}\n" f"Тип запроса: Прямой вызов из меню\n\n" - f"📋 **Последние сообщения для контекста:**\n{formatted_history}" + f"📋 Последние сообщения для контекста:\n{formatted_history}" ) self.send_message(config.OPERATOR_PEER_ID, operator_msg) - def _notify_operator_about_lead(self, user_id: int, user_data: Dict[str, Any]): + def _notify_operator_about_lead(self, user_id: int, user_data: Dict[str, Any], promo: Optional[str] = None): """Отправляет структурированный лид оператору после заполнения формы с указанием всех согласий.""" name = user_data["name"] phone = user_data["phone"] question = user_data["question"] + call_time = user_data.get("call_time") or "не указано" consent_1 = user_data.get("consent_1", "Согласен") consent_2 = user_data.get("consent_2", "Согласен") consent_3 = user_data.get("consent_3", "Согласен") + promo_line = f"🎁 Промокод клиента: {promo}\n" if promo else "" operator_msg = ( - f"⚡ **Получена новая заявка!**\n\n" - f"👤 **Имя:** {name}\n" - f"📞 **Телефон:** {phone}\n" - f"🎯 **Выбранная услуга:** {question}\n\n" - f"⚖️ **Согласия пользователя:**\n" + f"⚡ Получена новая заявка!\n\n" + f"👤 Имя: {name}\n" + f"📞 Телефон: {phone}\n" + f"🎯 Выбранная услуга: {question}\n" + f"🕒 Удобное время звонка: {call_time}\n" + f"{promo_line}\n" + f"⚖️ Согласия пользователя:\n" f"1. Оферта и обработка ПД (Обяз.): {consent_1}\n" f"2. Маркетинговые рассылки (Необяз.): {consent_2}\n" f"3. Запись консультации (Обяз.): {consent_3}\n\n" @@ -839,10 +1652,29 @@ class VKBotApplication: ) self.send_message(config.OPERATOR_PEER_ID, operator_msg) + # Дублируем заявку на корпоративную почту (group-zakaz@infocyber.pro) + try: + email_body = ( + "Новая заявка из чат-бота ВКонтакте.\n\n" + f"Имя: {name}\n" + f"Телефон: {phone}\n" + f"Выбранная услуга: {question}\n" + f"Удобное время звонка: {call_time}\n" + + (f"Промокод клиента: {promo}\n" if promo else "") + + "\nСогласия:\n" + f" - Оферта и обработка ПД (обяз.): {consent_1}\n" + f" - Маркетинговые рассылки (необяз.): {consent_2}\n" + f" - Запись консультации (обяз.): {consent_3}\n\n" + f"Профиль клиента: https://vk.com/id{user_id}\n" + ) + mailer.send_email(f"Новая заявка из бота — {name}", email_body) + except Exception as mail_err: + logger.error(f"Не удалось отправить email о заявке: {mail_err}") + def _forward_to_operator(self, user_id: int, text: str): """Пересылка сообщений от клиента в чат оператора.""" forward_msg = ( - f"💬 **Сообщение от vk.com/id{user_id}:**\n{text}" + f"💬 Сообщение от vk.com/id{user_id}:\n{text}" ) self.send_message(config.OPERATOR_PEER_ID, forward_msg) @@ -856,26 +1688,29 @@ class VKBotApplication: services_list_text = "" for key, svc in services.items(): - services_list_text += f"🔹 **[{key}] {svc['name']}**\n" + services_list_text += f"🔹 [{key}] {svc['name']}\n" services_list_text += f" 💰 Стоимость: {svc['price']}\n" services_list_text += f" 📝 Описание: {svc['description']}\n\n" admin_text = ( - "⚙️ **Панель управления услугами (Админ-меню)**\n\n" + "⚙️ Панель управления услугами (Админ-меню)\n\n" "Здесь вы можете редактировать существующие услуги или добавлять новые.\n\n" - "📋 **Текущий список активных услуг:**\n\n" + "📋 Текущий список активных услуг:\n\n" f"{services_list_text}" - "🔧 **Доступные команды управления:**\n\n" - "1. **Редактирование услуги**:\n" - " 👉 Отправьте: `ред [номер] [поле] [новое значение]`\n" - " Доступные поля: `имя`, `цена`, `описание`.\n" - " *Пример:* `ред 1 цена 1 800 ₽/час`\n\n" - "2. **Добавление услуги**:\n" - " 👉 Отправьте: `доб [Имя] | [Цена] | [Описание]`\n" - " *Пример:* `доб Тренировка по Valorant 🎯 | 1 500 ₽/час | Разбор стрельбы и aim.`\n\n" - "3. **Удаление услуги**:\n" - " 👉 Отправьте: `удал [номер]`\n" - " *Пример:* `удал 4`" + "🔧 Доступные команды управления:\n\n" + "1. Редактирование услуги:\n" + " 👉 Отправьте: ред [номер] [поле] [новое значение]\n" + " Доступные поля: имя, цена, описание.\n" + " *Пример:* ред 1 цена 1 800 ₽/час\n\n" + "2. Добавление услуги:\n" + " 👉 Отправьте: доб [Имя] | [Цена] | [Описание]\n" + " *Пример:* доб Тренировка по Valorant 🎯 | 1 500 ₽/час | Разбор стрельбы и aim.\n\n" + "3. Удаление услуги:\n" + " 👉 Отправьте: удал [номер]\n" + " *Пример:* удал 4\n\n" + "📊 Статистика: отправьте стата\n\n" + "📣 Рассылка по подписчикам:\n" + " 👉 рассылка [текст] → предпросмотр → рассылка подтвердить" ) self.send_message(user_id, admin_text) @@ -886,8 +1721,8 @@ class VKBotApplication: self.send_message( user_id, "⚠️ Некорректный формат команды редактирования.\n" - "Правильный формат: `ред [номер] [поле] [новое значение]`\n" - "Пример: `ред 1 цена 1 800 ₽/час`" + "Правильный формат: ред [номер] [поле] [новое значение]\n" + "Пример: ред 1 цена 1 800 ₽/час" ) return @@ -906,7 +1741,7 @@ class VKBotApplication: if field_name not in field_mapping: self.send_message( user_id, - "⚠️ Неизвестное поле для редактирования. Допустимые поля: `имя`, `цена`, `описание`." + "⚠️ Неизвестное поле для редактирования. Допустимые поля: имя, цена, описание." ) return @@ -927,7 +1762,7 @@ class VKBotApplication: self.send_message( user_id, f"✅ Услуга [{service_id}] успешно обновлена!\n" - f"Поле `{field_name}` изменено на: `{new_value}`." + f"Поле {field_name} изменено на: {new_value}." ) self._send_admin_menu(user_id) except Exception as e: @@ -943,8 +1778,8 @@ class VKBotApplication: self.send_message( user_id, "⚠️ Некорректный формат команды добавления.\n" - "Правильный формат: `доб [Имя] | [Цена] | [Описание]`\n" - "Пример: `доб Тренировка по Valorant 🎯 | 1 500 ₽/час | Разбор стрельбы и aim.`" + "Правильный формат: доб [Имя] | [Цена] | [Описание]\n" + "Пример: доб Тренировка по Valorant 🎯 | 1 500 ₽/час | Разбор стрельбы и aim." ) return @@ -960,7 +1795,7 @@ class VKBotApplication: self.send_message( user_id, - f"✅ Новая услуга успешно добавлена с номером **[{new_id}]**!" + f"✅ Новая услуга успешно добавлена с номером [{new_id}]!" ) self._send_admin_menu(user_id) except Exception as e: @@ -974,7 +1809,7 @@ class VKBotApplication: self.send_message( user_id, "⚠️ Укажите номер услуги для удаления.\n" - "Пример: `удал 4`" + "Пример: удал 4" ) return @@ -1001,6 +1836,120 @@ class VKBotApplication: logger.error(f"Ошибка при деактивации услуги в БД: {e}") self.send_message(user_id, f"⚠️ Произошла ошибка при обновлении базы данных: {e}") + def _handle_admin_stats(self, user_id: int): + """Формирует и отправляет админу сводную статистику по заявкам и диалогам.""" + ls = db.get_lead_stats() + ds = db.get_dialogue_stats() + + total = ls["total"] + marketing_pct = round(ls["marketing_yes"] / total * 100) if total else 0 + + service_lines = "" + if ls["by_service"]: + for svc_name, cnt in ls["by_service"]: + service_lines += f" • {svc_name}: {cnt}\n" + else: + service_lines = " • пока нет данных\n" + + msg = ( + "📊 Статистика ИКП\n\n" + "🗂 Заявки (всего): {total}\n" + " • сегодня: {today}\n" + " • за 7 дней: {last7}\n" + " • из бота: {from_bot}\n" + " • с сайта: {from_web}\n\n" + "📨 Согласны на рассылку: {mk_yes} из {total} ({mk_pct}%)\n\n" + "🎮 По направлениям:\n{services}\n" + "💬 Диалоги:\n" + " • уникальных пользователей: {uniq}\n" + " • сообщений от клиентов: {umsg}" + ).format( + total=total, today=ls["today"], last7=ls["last7"], + from_bot=ls["from_bot"], from_web=ls["from_web"], + mk_yes=ls["marketing_yes"], mk_pct=marketing_pct, + services=service_lines, uniq=ds["unique_users"], umsg=ds["user_messages"] + ) + self.send_message(user_id, msg) + + def _handle_admin_broadcast(self, user_id: int, text: str): + """Рассылка по подписчикам: 'рассылка <текст>' → предпросмотр → 'рассылка подтвердить' → отправка.""" + # Отрезаем ключевое слово 'рассылка' + content = text[len("рассылка"):].strip() + content_lower = content.lower() + + # Шаг 2: подтверждение и запуск + if content_lower in ["подтвердить", "да", "отправить", "!"]: + pending = self._pending_broadcasts.get(user_id) + if not pending: + self.send_message( + user_id, + "❌ Нет рассылки, ожидающей подтверждения.\n" + "Сначала отправьте: рассылка <текст сообщения>." + ) + return + recipients = db.get_marketing_subscribers() + if not recipients: + self.send_message(user_id, "⚠️ Нет подписчиков с согласием на рассылку — отправлять некому.") + self._pending_broadcasts.pop(user_id, None) + return + self.send_message( + user_id, + f"🚀 Запускаю рассылку для {len(recipients)} получателей. " + "Отправка идёт с паузами, чтобы не нарушать лимиты VK. Сообщу по завершении." + ) + self._pending_broadcasts.pop(user_id, None) + # Запускаем в отдельном потоке, чтобы не блокировать обработку сообщений + threading.Thread( + target=self._run_broadcast, args=(user_id, pending, recipients), daemon=True + ).start() + return + + # Шаг 1: приём текста и предпросмотр + if not content: + self.send_message( + user_id, + "📣 Рассылка по подписчикам\n\n" + "Отправьте: рассылка <текст сообщения>\n" + "После этого я покажу число получателей и попрошу подтверждение.\n\n" + "Рассылка уходит только тем, кто дал согласие (consent_2)." + ) + return + + recipients = db.get_marketing_subscribers() + self._pending_broadcasts[user_id] = content + preview = ( + "📋 Предпросмотр рассылки\n\n" + f"Получателей (с согласием): {len(recipients)}\n\n" + "Текст сообщения:\n" + "— — —\n" + f"{content}\n" + "— — —\n\n" + "Чтобы отправить — напишите: рассылка подтвердить\n" + "Чтобы отменить — просто проигнорируйте." + ) + self.send_message(user_id, preview) + + def _run_broadcast(self, admin_id: int, text: str, recipients: list): + """Фоновая отправка рассылки с троттлингом и подсказкой об отписке.""" + body = ( + f"{text}\n\n" + "— — —\n" + "🔕 Чтобы отписаться от рассылки, отправьте: Отписаться от рассылки" + ) + sent = 0 + for uid in recipients: + try: + # Повторно проверяем согласие на случай отписки во время рассылки + data = db.get_user_data(uid) + if data.get("consent_2") != "Согласен": + continue + self.send_message(uid, body) + sent += 1 + except Exception as e: + logger.error(f"Ошибка рассылки пользователю {uid}: {e}") + time.sleep(0.4) # троттлинг против лимитов VK + self.send_message(admin_id, f"✅ Рассылка завершена. Доставлено получателям: {sent} из {len(recipients)}.") + def run(self): """Запуск бесконечного цикла прослушивания событий VK LongPoll.""" if not self.longpoll: diff --git a/config.py b/config.py index fa3cf37..410b342 100644 --- a/config.py +++ b/config.py @@ -40,35 +40,80 @@ if admin_ids_raw: else: ADMIN_IDS = [OPERATOR_PEER_ID] +# Разрешённый источник (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") + # Standard replies and patterns FAQ_ANSWERS = { - "цены": "💰 Наши тарифы на киберспортивные тренировки:\n• Тренировка по Dota 2 🎮 — 1 500 ₽/час\n• Тренировка по CS2 🔫 — 1 500 ₽/час\n• Пакет 'Стандарт' 📦 (5 тренировок) — 7 500 ₽\n• Пакет 'Pro' 🌟 (10 тренировок + постоянная поддержка тренера) — 14 000 ₽\n\n🎁 ПОДАРОК: Всем новым ученикам дарим скидку 10% на первую индивидуальную тренировку! Заполните заявку через меню прямо сейчас!", - "контакты": "📞 Связаться с нами:\n• Телефон: +7 (999) 123-45-67\n• Email: info@cyberpro.com\n• Адрес: г. Москва, Киберспортивная Арена 'Cyber Pro'\n• Режим работы: Ежедневно с 10:00 до 22:00", - "услуги": "⚙️ Мы предлагаем профессиональное обучение по киберспортивным дисциплинам:\n1. Индивидуальные тренировки по Dota 2 с разбором реплеев и позиционки\n2. Персональные занятия по CS2 (aim, раскидки гранат, командная тактика)\n3. Выгодные абонементы на 5 и 10 занятий с постоянным сопровождением тренера", - "о компании": "🏢 Мы — школа киберспорта 'Cyber Pro'. Помогаем игрокам любого уровня повышать свой MMR/Faceit Elo, развивать игровое мышление и побеждать на турнирах с 2022 года!\n\nНаши тренеры — действующие и экс-профессиональные игроки с 7000+ MMR и опытом игры на PRO-сцене." + "цены": ( + "💰 Тарифы ИКП на тренировки (направления: CS2, Dota 2, World of Tanks):\n\n" + "• Разовая тренировка — 870 ₽\n" + "• Абонемент на 4 занятия (групповой) — 2 790 ₽\n" + "• Абонемент на 8 занятий (групповой) — 4 990 ₽\n" + "• Абонемент на 4 занятия (индивидуальный) — 3 290 ₽\n" + "• Абонемент на 8 занятий (индивидуальный) — 5 990 ₽\n\n" + "Оставьте заявку через меню — менеджер подберёт оптимальный формат и направление!" + ), + "контакты": ( + "📞 Связаться с ИКП:\n" + "• Email (поддержка): support@infocyber.pro\n" + "• Заявки: group-zakaz@infocyber.pro\n" + "• Telegram: @ikp_team\n" + "• Сайт: https://infocyber.pro/" + ), + "услуги": ( + "⚙️ ИКП — тренировки по киберспортивным дисциплинам:\n\n" + "1. Counter-Strike 2 — aim, раскидки гранат, командная тактика\n" + "2. Dota 2 — разбор реплеев, позиционка, фарм и тимплей\n" + "3. World of Tanks — позиционирование, сведение, тактика боёв\n\n" + "Форматы: разовые занятия, групповые и индивидуальные абонементы (4 и 8 занятий)." + ), + "о компании": ( + "🏢 ИКП — Игра. Киберспорт. Профессионалы.\n\n" + "Мощная экосистема для взрывного роста игроков: лучшие тренеры и глубокая data-аналитика. " + "Организация основана 1 сентября 2025 года.\n\n" + "Мы на площадках:\n" + "• ВКонтакте: https://vk.com/ikpro\n" + "• VK Live (трансляции): https://live.vkvideo.ru/ikp\n" + "• Telegram: https://t.me/ikp_team\n" + "• Discord: https://discord.gg/z92mAw7KEe" + ) } -# Доступные услуги и тарифы для выбора в форме заявки +# Доступные услуги (направления) для выбора в форме заявки SERVICES = { "1": { - "name": "Тренировка по Dota 2 🎮", - "price": "1 500 ₽/час", - "description": "Индивидуальное занятие с опытным тренером, разбор реплеев, позиционка и микроконтроль." + "name": "Counter-Strike 2 🔫", + "price": "от 870 ₽ / занятие", + "description": "Тренировки по CS2: aim, раскидки гранат, командная тактика. Разовое занятие 870 ₽, абонементы от 2 790 ₽." }, "2": { - "name": "Тренировка по CS2 🔫", - "price": "1 500 ₽/час", - "description": "Анализ ошибок, тренировка стрельбы (aim), раскидки гранат и командное взаимодействие." + "name": "Dota 2 🎮", + "price": "от 870 ₽ / занятие", + "description": "Тренировки по Dota 2: разбор реплеев, позиционка, фарм и тимплей. Разовое занятие 870 ₽, абонементы от 2 790 ₽." }, "3": { - "name": "Пакет услуг 'Стандарт' 📦", - "price": "7 500 ₽", - "description": "5 индивидуальных тренировок по выбранной дисциплине + базовый план развития." - }, - "4": { - "name": "Пакет услуг 'Pro' 🌟", - "price": "14 000 ₽", - "description": "10 индивидуальных тренировок + детальный разбор матчей + постоянная поддержка тренера." + "name": "World of Tanks 🛡️", + "price": "от 870 ₽ / занятие", + "description": "Тренировки по World of Tanks: позиционирование, сведение, тактика боёв. Разовое занятие 870 ₽, абонементы от 2 790 ₽." } } diff --git a/database.py b/database.py index 35ada56..95c2b68 100644 --- a/database.py +++ b/database.py @@ -1,5 +1,6 @@ import sqlite3 import os +import json from typing import Optional, Dict, Any class DatabaseManager: @@ -29,7 +30,7 @@ class DatabaseManager: """) # Migration checks for existing databases - for col in ["consent_1", "consent_2", "consent_3"]: + for col in ["consent_1", "consent_2", "consent_3", "call_time", "vac_data"]: try: cursor.execute(f"ALTER TABLE user_states ADD COLUMN {col} TEXT") except sqlite3.OperationalError: @@ -63,24 +64,19 @@ class DatabaseManager: if cursor.fetchone()[0] == 0: default_services = { "1": { - "name": "Тренировка по Dota 2 🎮", - "price": "1 500 ₽/час", - "description": "Индивидуальное занятие с опытным тренером, разбор реплеев, позиционка и микроконтроль." + "name": "Counter-Strike 2 🔫", + "price": "от 870 ₽ / занятие", + "description": "Тренировки по CS2: aim, раскидки гранат, командная тактика. Разовое занятие 870 ₽, абонементы от 2 790 ₽." }, "2": { - "name": "Тренировка по CS2 🔫", - "price": "1 500 ₽/час", - "description": "Анализ ошибок, тренировка стрельбы (aim), раскидки гранат и командное взаимодействие." + "name": "Dota 2 🎮", + "price": "от 870 ₽ / занятие", + "description": "Тренировки по Dota 2: разбор реплеев, позиционка, фарм и тимплей. Разовое занятие 870 ₽, абонементы от 2 790 ₽." }, "3": { - "name": "Пакет услуг 'Стандарт' 📦", - "price": "7 500 ₽", - "description": "5 индивидуальных тренировок по выбранной дисциплине + базовый план развития." - }, - "4": { - "name": "Пакет услуг 'Pro' 🌟", - "price": "14 000 ₽", - "description": "10 индивидуальных тренировок + детальный разбор матчей + постоянная поддержка тренера." + "name": "World of Tanks 🛡️", + "price": "от 870 ₽ / занятие", + "description": "Тренировки по World of Tanks: позиционирование, сведение, тактика боёв. Разовое занятие 870 ₽, абонементы от 2 790 ₽." } } for key, svc in default_services.items(): @@ -89,6 +85,38 @@ class DatabaseManager: (key, svc["name"], svc["price"], svc["description"]) ) + # Table for completed leads (заявки) — единый источник для статистики + cursor.execute(""" + CREATE TABLE IF NOT EXISTS leads ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + source TEXT, -- 'bot' | 'web' + name TEXT, + phone TEXT, + service TEXT, + call_time TEXT, + promo TEXT, + marketing INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + + # Table for HR/recruitment applications (отклики на вакансии и набор в состав) + cursor.execute(""" + CREATE TABLE IF NOT EXISTS applications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + track TEXT, -- 'player' | 'worker' + discipline TEXT, + position TEXT, + rank TEXT, + role TEXT, + experience TEXT, + contact TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + # Table for web registrations cursor.execute(""" CREATE TABLE IF NOT EXISTS web_registrations ( @@ -112,7 +140,7 @@ class DatabaseManager: conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute( - "SELECT state, name, phone, question, consent_1, consent_2, consent_3 FROM user_states WHERE user_id = ?", + "SELECT state, name, phone, question, consent_1, consent_2, consent_3, call_time FROM user_states WHERE user_id = ?", (user_id,) ) row = cursor.fetchone() @@ -126,13 +154,14 @@ class DatabaseManager: ) conn.commit() return { - "state": "FREE_CHAT", - "name": None, - "phone": None, + "state": "FREE_CHAT", + "name": None, + "phone": None, "question": None, "consent_1": None, "consent_2": None, - "consent_3": None + "consent_3": None, + "call_time": None } def update_user_state(self, user_id: int, state: str): @@ -148,7 +177,7 @@ class DatabaseManager: def update_user_field(self, user_id: int, field: str, value: Optional[str]): """Update a specific field in the form.""" - allowed_fields = ["name", "phone", "question", "state", "consent_1", "consent_2", "consent_3"] + allowed_fields = ["name", "phone", "question", "state", "consent_1", "consent_2", "consent_3", "call_time"] if field not in allowed_fields: raise ValueError(f"Invalid field name: {field}") @@ -162,12 +191,25 @@ class DatabaseManager: conn.commit() def reset_form(self, user_id: int): - """Reset form fields and return to free chat state.""" + """Reset form progress and return to free chat, but KEEP name/phone for returning users.""" with self._get_connection() as conn: cursor = conn.cursor() cursor.execute( "UPDATE user_states SET state = 'FREE_CHAT', " - "name = NULL, phone = NULL, question = NULL, " + "question = NULL, call_time = NULL, vac_data = NULL, " + "consent_1 = NULL, consent_2 = NULL, consent_3 = NULL " + "WHERE user_id = ?", + (user_id,) + ) + conn.commit() + + def reset_form_full(self, user_id: int): + """Полностью очистить данные пользователя (включая имя/телефон) — для 'Заполнить заново'.""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "UPDATE user_states SET state = 'FREE_CHAT', " + "name = NULL, phone = NULL, question = NULL, call_time = NULL, " "consent_1 = NULL, consent_2 = NULL, consent_3 = NULL " "WHERE user_id = ?", (user_id,) @@ -254,6 +296,120 @@ class DatabaseManager: ) conn.commit() + def count_web_registrations(self) -> int: + """Количество заявок с сайта (для генерации следующего ID независимо от Excel).""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM web_registrations") + return cursor.fetchone()[0] + + def add_lead(self, user_id: int, source: str, name: str, phone: str, + service: Optional[str] = None, call_time: Optional[str] = None, + promo: Optional[str] = None, marketing: bool = False): + """Записать завершённую заявку в единый журнал leads (для статистики и истории).""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO leads (user_id, source, name, phone, service, call_time, promo, marketing) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + (user_id, source, name, phone, service, call_time, promo, 1 if marketing else 0) + ) + conn.commit() + + def get_marketing_subscribers(self) -> list: + """Список user_id, давших согласие на маркетинговую рассылку (consent_2 = 'Согласен').""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT user_id FROM user_states WHERE consent_2 = 'Согласен'") + return [row[0] for row in cursor.fetchall()] + + def get_lead_stats(self) -> Dict[str, Any]: + """Сводная статистика по заявкам из журнала leads.""" + with self._get_connection() as conn: + cursor = conn.cursor() + + def scalar(query, params=()): + cursor.execute(query, params) + return cursor.fetchone()[0] + + stats = { + "total": scalar("SELECT COUNT(*) FROM leads"), + "today": scalar("SELECT COUNT(*) FROM leads WHERE date(created_at) = date('now', 'localtime')"), + "last7": scalar("SELECT COUNT(*) FROM leads WHERE created_at >= datetime('now', '-7 days')"), + "from_bot": scalar("SELECT COUNT(*) FROM leads WHERE source = 'bot'"), + "from_web": scalar("SELECT COUNT(*) FROM leads WHERE source = 'web'"), + "marketing_yes": scalar("SELECT COUNT(*) FROM leads WHERE marketing = 1"), + } + + cursor.execute( + "SELECT service, COUNT(*) FROM leads WHERE service IS NOT NULL AND service != '' " + "GROUP BY service ORDER BY COUNT(*) DESC" + ) + stats["by_service"] = cursor.fetchall() + return stats + + def get_dialogue_stats(self) -> Dict[str, Any]: + """Статистика диалогов: уникальные пользователи и объём сообщений.""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT COUNT(DISTINCT user_id) FROM dialogue_logs") + unique_users = cursor.fetchone()[0] + cursor.execute("SELECT COUNT(*) FROM dialogue_logs WHERE sender = 'user'") + user_messages = cursor.fetchone()[0] + return {"unique_users": unique_users, "user_messages": user_messages} + + def get_vac_data(self, user_id: int) -> Dict[str, Any]: + """Черновик отклика на вакансию (хранится как JSON в user_states.vac_data).""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT vac_data 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_vac_field(self, user_id: int, key: str, value: Optional[str]): + """Обновить одно поле в черновике отклика.""" + data = self.get_vac_data(user_id) + data[key] = value + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO user_states (user_id, vac_data) VALUES (?, ?) " + "ON CONFLICT(user_id) DO UPDATE SET vac_data = excluded.vac_data, last_updated = CURRENT_TIMESTAMP", + (user_id, json.dumps(data, ensure_ascii=False)) + ) + conn.commit() + + def clear_vac_data(self, user_id: int): + """Очистить черновик отклика.""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO user_states (user_id, vac_data) VALUES (?, NULL) " + "ON CONFLICT(user_id) DO UPDATE SET vac_data = NULL, last_updated = CURRENT_TIMESTAMP", + (user_id,) + ) + conn.commit() + + def add_application(self, user_id: int, track: str, data: dict): + """Сохранить завершённый отклик (игрок/сотрудник) в журнал applications.""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO applications (user_id, track, discipline, position, rank, role, experience, contact) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + ( + user_id, track, + data.get("discipline"), data.get("position"), data.get("rank"), + data.get("role"), data.get("experience"), data.get("contact") + ) + ) + conn.commit() + def add_web_registration(self, reg_id: str, data: dict): """Сохранить заявку с веб-формы в базу данных SQLite.""" with self._get_connection() as conn: diff --git a/form/index.html b/form/index.html index 7c38f5f..f7e8c68 100644 --- a/form/index.html +++ b/form/index.html @@ -33,6 +33,12 @@

Оставить заявку на бесплатный урок и доступ к тестовым материалам ИКП

+ + +
@@ -143,6 +149,11 @@ Наш киберспортивный координатор свяжется с вами в ближайшее время, чтобы открыть доступ к тестовым материалам и записать на бесплатный урок.

+
+ 🎁 Ваш промокод на скидку 10% + + Назовите его менеджеру при записи +
diff --git a/form/script.js b/form/script.js index 84bb155..a71816a 100644 --- a/form/script.js +++ b/form/script.js @@ -173,6 +173,28 @@ document.addEventListener('DOMContentLoaded', () => { } }); + /* ==================================================== + Confetti (салют при успешной отправке) + ==================================================== */ + function launchConfetti() { + if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + const colors = ['#ff7a00', '#00d2ff', '#ffffff', '#ff9233', '#ffd166']; + const count = 90; + for (let i = 0; i < count; i++) { + const piece = document.createElement('div'); + piece.className = 'confetti-piece'; + piece.style.left = Math.random() * 100 + 'vw'; + piece.style.background = colors[Math.floor(Math.random() * colors.length)]; + piece.style.animationDelay = (Math.random() * 0.5) + 's'; + piece.style.animationDuration = (2.2 + Math.random() * 1.6) + 's'; + const size = 6 + Math.random() * 8; + piece.style.width = size + 'px'; + piece.style.height = (size * 0.5) + 'px'; + document.body.appendChild(piece); + setTimeout(() => piece.remove(), 4200); + } + } + /* ==================================================== Form Submission ==================================================== */ @@ -194,6 +216,7 @@ document.addEventListener('DOMContentLoaded', () => { if (isNameValid && isPhoneValid && isEmailValid && isVKValid && isOfertaValid && isPolicyValid) { // Gather form data + const websiteInput = document.getElementById('website'); // honeypot const formData = { name: nameInput.value.trim(), phone: phoneInput.value, @@ -201,17 +224,19 @@ document.addEventListener('DOMContentLoaded', () => { vk: vkInput.value.trim(), agreeOferta: agreeOfertaInput.checked, agreePolicy: agreePolicyInput.checked, - agreeNewsletter: agreeNewsletterInput.checked + agreeNewsletter: agreeNewsletterInput.checked, + website: websiteInput ? websiteInput.value : '' // должно быть пустым у людей }; // Disable submit button during request const submitBtn = document.getElementById('submitBtn'); const originalBtnText = submitBtn.textContent; submitBtn.disabled = true; + submitBtn.classList.add('loading'); submitBtn.textContent = 'Отправка...'; - // Send to our backend server (supports running on localhost:8000) - fetch('http://localhost:8000/submit', { + // Отправка на тот же сервер, что отдаёт форму (относительный путь = тот же origin, без CORS) + fetch('/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -232,6 +257,18 @@ document.addEventListener('DOMContentLoaded', () => { }); }) .then(data => { + // Показываем персональный промокод, если сервер его вернул + if (data && data.promo) { + const promoCode = document.getElementById('promoCode'); + const promoBox = document.getElementById('promoBox'); + if (promoCode && promoBox) { + promoCode.textContent = data.promo; + promoBox.style.display = 'flex'; + } + } + // Салют 🎉 + launchConfetti(); + // Success State: Animate form fading out and success window fading in formContainer.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; formContainer.style.opacity = '0'; @@ -263,6 +300,7 @@ document.addEventListener('DOMContentLoaded', () => { }) .finally(() => { submitBtn.disabled = false; + submitBtn.classList.remove('loading'); submitBtn.textContent = originalBtnText; }); } else { diff --git a/form/server.py b/form/server.py index 896357f..62b03cb 100644 --- a/form/server.py +++ b/form/server.py @@ -1,5 +1,11 @@ import os +import re import json +import time +import random +import string +import threading +from collections import defaultdict from http.server import HTTPServer, SimpleHTTPRequestHandler from datetime import datetime import sys @@ -36,6 +42,12 @@ except Exception as e: db = None config = None +try: + import mailer +except Exception as e: + print(f"Предупреждение: Не удалось загрузить модуль mailer: {e}") + mailer = None + try: import vk_api from vk_api.utils import get_random_id @@ -46,6 +58,60 @@ except ImportError: SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) FILE_NAME = os.path.join(SCRIPT_DIR, "registration_for_training.xlsx") +# Разрешённый источник для CORS (берётся из config, '*' только для локальной отладки) +ALLOWED_ORIGIN = getattr(config, "ALLOWED_ORIGIN", "*") if config else "*" + +# --------------------------------------------------------------- +# Защита формы: валидация, рейт-лимит и honeypot +# --------------------------------------------------------------- +NAME_RE = re.compile(r"^[A-Za-zА-Яа-яЁё][A-Za-zА-Яа-яЁё \-']{1,49}$") +EMAIL_RE = re.compile(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$") + +# Простой in-memory рейт-лимит по IP: не более N заявок за окно +RATE_LIMIT_MAX = 5 +RATE_LIMIT_WINDOW = 600 # секунд (10 минут) +_rate_lock = threading.Lock() +_rate_hits = defaultdict(list) + + +def is_rate_limited(ip: str) -> bool: + """True, если с этого IP было слишком много заявок за окно времени.""" + now = time.time() + with _rate_lock: + hits = [t for t in _rate_hits[ip] if now - t < RATE_LIMIT_WINDOW] + if len(hits) >= RATE_LIMIT_MAX: + _rate_hits[ip] = hits + return True + hits.append(now) + _rate_hits[ip] = hits + return False + + +def validate_submission(data: dict): + """Серверная валидация заявки. Возвращает (ok: bool, error: str|None).""" + name = (data.get("name") or "").strip() + phone_digits = re.sub(r"\D", "", data.get("phone") or "") + email = (data.get("email") or "").strip() + vk = (data.get("vk") or "").strip().lower() + + if not NAME_RE.match(name): + return False, "Имя должно содержать только буквы (2–50 символов)." + # Российский (11 цифр) или международный (10–15 цифр) номер + if not (10 <= len(phone_digits) <= 15): + return False, "Некорректный номер телефона." + if not EMAIL_RE.match(email): + return False, "Некорректный e-mail." + if not (vk and ("vk.com" in vk or "vkontakte.ru" in vk)): + return False, "Ссылка должна вести на vk.com или vkontakte.ru." + if not data.get("agreeOferta") or not data.get("agreePolicy"): + return False, "Необходимо согласие с офертой и политикой обработки данных." + return True, None + + +def is_spam(data: dict) -> bool: + """Honeypot: скрытое поле 'website' должно оставаться пустым у реальных пользователей.""" + return bool((data.get("website") or "").strip()) + def init_excel(): """Create the Excel file with styled headers if it doesn't exist.""" recreate = False @@ -124,10 +190,25 @@ def get_next_user_id(ws): except (IndexError, ValueError): return f"id_{max_row:03d}" -def add_registration(data): - """Append a new form submission row into the Excel sheet.""" +def generate_next_id(): + """Генерирует следующий ID заявки. Источник правды — SQLite; Excel используется как запасной.""" + if db: + try: + return f"id_{db.count_web_registrations() + 1:03d}" + except Exception as e: + print(f"Не удалось получить счётчик из SQLite ({e}), используем Excel как запасной источник.") + # Запасной вариант — по строкам Excel + try: + init_excel() + wb = load_workbook(FILE_NAME) + return get_next_user_id(wb.active) + except Exception: + return f"id_{int(time.time())}" + + +def append_to_excel(next_id, data, current_time): + """Дописывает строку в Excel. Вынесено отдельно: ошибка здесь НЕ срывает приём заявки.""" init_excel() - try: wb = load_workbook(FILE_NAME) ws = wb.active @@ -136,12 +217,7 @@ def add_registration(data): init_excel() wb = load_workbook(FILE_NAME) ws = wb.active - - current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate next user ID - next_id = get_next_user_id(ws) - + row = [ next_id, data.get("name", "").strip(), @@ -153,39 +229,87 @@ def add_registration(data): "Да" if data.get("agreeNewsletter") else "Нет", current_time ] - + ws.append(row) - - # Align data cells + new_row_index = ws.max_row data_align_left = Alignment(horizontal="left", vertical="center") data_align_center = Alignment(horizontal="center", vertical="center") - for col_idx in range(1, len(row) + 1): cell = ws.cell(row=new_row_index, column=col_idx) - # Left align Name (2), Email (4), VK Link (5). Center align others. if col_idx in [2, 4, 5]: cell.alignment = data_align_left else: cell.alignment = data_align_center - - wb.save(FILE_NAME) - print(f"[{current_time}] Добавлена новая запись в Excel: ID={row[0]}, Имя={row[1]}, Телефон={row[2]}") - # 1. Запись в SQLite базу данных + wb.save(FILE_NAME) + print(f"[{current_time}] Добавлена запись в Excel: ID={row[0]}, Имя={row[1]}, Телефон={row[2]}") + + +def gen_promo(): + """Персональный промокод на скидку — выдаётся клиенту на экране 'Спасибо'.""" + return "ИКП-" + "".join(random.choices(string.ascii_uppercase + string.digits, k=6)) + + +def add_registration(data): + """Сохраняет заявку: SQLite (источник правды) → Excel (нестрого) → уведомления. Возвращает промокод.""" + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + next_id = generate_next_id() + # Промокод пока выключен флагом config.PROMO_ENABLED + promo = gen_promo() if (config and getattr(config, "PROMO_ENABLED", False)) else None + + # 1. Запись в SQLite базу данных — основной приёмник, выполняется первым if db: + db.add_web_registration(next_id, data) + # Единый журнал заявок для статистики (source='web') try: - db.add_web_registration(next_id, data) - print(f"[{current_time}] Заявка {next_id} успешно сохранена в SQLite (таблица web_registrations).") - except Exception as db_err: - print(f"[{current_time}] Ошибка записи заявки в SQLite: {db_err}") - - # 2. Отправка уведомления оператору в VK + db.add_lead( + user_id=None, source="web", + name=data.get("name", "").strip(), phone=data.get("phone", "").strip(), + service=None, call_time=None, promo=promo, + marketing=bool(data.get("agreeNewsletter")) + ) + except Exception as lead_err: + print(f"[{current_time}] Не удалось записать лид в журнал: {lead_err}") + print(f"[{current_time}] Заявка {next_id} сохранена в SQLite (web_registrations + leads).") + + # 2. Дублирование в Excel — НЕ критично: если файл открыт/заблокирован, заявка всё равно принята + try: + append_to_excel(next_id, data, current_time) + except PermissionError: + print(f"[{current_time}] ⚠️ Excel заблокирован (открыт в программе). Заявка сохранена в SQLite, Excel пропущен.") + except Exception as xl_err: + print(f"[{current_time}] ⚠️ Ошибка записи в Excel (пропущено): {xl_err}") + + # 3. Отправка уведомления оператору в VK try: send_vk_operator_notification(next_id, data) except Exception as vk_err: print(f"[{current_time}] Ошибка при отправке уведомления оператору: {vk_err}") + # 3. Отправка уведомления на корпоративную почту (group-zakaz@infocyber.pro) + if mailer: + try: + email_body = ( + "Новая заявка с сайта.\n\n" + f"ID заявки: {next_id}\n" + f"Имя: {data.get('name', '').strip()}\n" + f"Телефон: {data.get('phone', '').strip()}\n" + f"E-mail: {data.get('email', '').strip()}\n" + f"Профиль VK: {data.get('vk', '').strip()}\n\n" + f"Согласия:\n" + f" - Оферта: {'Да' if data.get('agreeOferta') else 'Нет'}\n" + f" - Политика ПД: {'Да' if data.get('agreePolicy') else 'Нет'}\n" + f" - Рассылка: {'Да' if data.get('agreeNewsletter') else 'Нет'}\n\n" + + (f"Промокод клиента: {promo}\n" if promo else "") + + f"Дата: {current_time}\n" + ) + mailer.send_email(f"Новая заявка с сайта — {data.get('name', '').strip()}", email_body) + except Exception as mail_err: + print(f"[{current_time}] Ошибка при отправке email: {mail_err}") + + return promo + def send_vk_operator_notification(reg_id, data): """Отправить уведомление о новой веб-заявке в VK-чат оператора.""" @@ -238,50 +362,71 @@ class FormHandler(SimpleHTTPRequestHandler): rel_path = os.path.relpath(path, os.getcwd()) return os.path.join(SCRIPT_DIR, rel_path) + def _client_ip(self) -> str: + """IP клиента с учётом обратного прокси (X-Forwarded-For).""" + xff = self.headers.get("X-Forwarded-For") + if xff: + return xff.split(",")[0].strip() + return self.client_address[0] if self.client_address else "unknown" + + def _send_json(self, code: int, payload: dict): + body = json.dumps(payload).encode("utf-8") + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.send_header("Access-Control-Allow-Origin", ALLOWED_ORIGIN) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + def do_POST(self): - if self.path == '/submit': - content_length = int(self.headers['Content-Length']) - post_data = self.rfile.read(content_length) - - try: - data = json.loads(post_data.decode('utf-8')) - add_registration(data) - - self.send_response(200) - self.send_header('Content-Type', 'application/json') - self.send_header('Access-Control-Allow-Origin', '*') # Prevent CORS errors - self.end_headers() - - response = {"status": "success", "message": "Заявка успешно записана!"} - self.wfile.write(json.dumps(response).encode('utf-8')) - except PermissionError: - self.send_response(500) - self.send_header('Content-Type', 'application/json') - self.send_header('Access-Control-Allow-Origin', '*') - self.end_headers() - - response = { - "status": "error", - "message": "Таблица Excel заблокирована. Пожалуйста, закройте файл 'registration_for_training.xlsx' в Excel и отправьте форму заново." - } - self.wfile.write(json.dumps(response).encode('utf-8')) - except Exception as e: - self.send_response(500) - self.send_header('Content-Type', 'application/json') - self.send_header('Access-Control-Allow-Origin', '*') - self.end_headers() - - response = {"status": "error", "message": str(e)} - self.wfile.write(json.dumps(response).encode('utf-8')) - else: + if self.path != '/submit': self.send_response(404) self.end_headers() - + return + + # Защита от флуда по IP + ip = self._client_ip() + if is_rate_limited(ip): + print(f"[RATE LIMIT] Слишком много заявок с IP {ip}.") + self._send_json(429, {"status": "error", "message": "Слишком много заявок. Попробуйте позже."}) + return + + try: + content_length = int(self.headers.get('Content-Length', 0)) + # Ограничение размера тела запроса (защита от больших payload'ов) + if content_length <= 0 or content_length > 10000: + self._send_json(400, {"status": "error", "message": "Некорректный размер запроса."}) + return + + post_data = self.rfile.read(content_length) + data = json.loads(post_data.decode('utf-8')) + + # Honeypot: если скрытое поле заполнено — это бот. Делаем вид, что всё ок, но не сохраняем. + if is_spam(data): + print(f"[SPAM] Honeypot сработал для IP {ip}, заявка отброшена.") + self._send_json(200, {"status": "success", "message": "Заявка принята."}) + return + + # Серверная валидация + ok, error = validate_submission(data) + if not ok: + self._send_json(400, {"status": "error", "message": error}) + return + + promo = add_registration(data) + self._send_json(200, {"status": "success", "message": "Заявка успешно записана!", "promo": promo}) + + except json.JSONDecodeError: + self._send_json(400, {"status": "error", "message": "Некорректный формат данных."}) + except Exception as e: + print(f"[ERROR] Ошибка обработки заявки: {e}") + self._send_json(500, {"status": "error", "message": "Внутренняя ошибка сервера. Попробуйте позже."}) + def do_OPTIONS(self): # Respond to CORS preflight requests self.send_response(204) - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') + self.send_header('Access-Control-Allow-Origin', ALLOWED_ORIGIN) + self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS') self.send_header('Access-Control-Allow-Headers', 'Content-Type') self.end_headers() diff --git a/form/style.css b/form/style.css index 90f74b9..2ae1ed1 100644 --- a/form/style.css +++ b/form/style.css @@ -483,3 +483,162 @@ main { font-size: 0.95rem; } } + +/* ---------------------------------------------------- + 11. VISUAL POLISH (анимации и «живой» неон) + ---------------------------------------------------- */ + +/* Плавное появление карточки при загрузке */ +@keyframes cardIn { + from { opacity: 0; transform: translateY(24px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } +} +.card { + animation: cardIn 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +/* Бегущий градиент в верхней акцент-линии */ +@keyframes accentFlow { + 0% { background-position: 0% 50%; } + 100% { background-position: 200% 50%; } +} +.card::before { + background: linear-gradient(90deg, + var(--cyan-accent) 0%, var(--primary-orange) 50%, var(--cyan-accent) 100%); + background-size: 200% 100%; + animation: accentFlow 4s linear infinite; +} + +/* Каскадное появление полей формы */ +@keyframes fadeUp { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} +#formContainer .form-group, +#formContainer .checkbox-group, +#formContainer .submit-btn { + animation: fadeUp 0.5s ease both; +} +#formContainer .form-group:nth-of-type(1) { animation-delay: 0.10s; } +#formContainer .form-group:nth-of-type(2) { animation-delay: 0.16s; } +#formContainer .form-group:nth-of-type(3) { animation-delay: 0.22s; } +#formContainer .form-group:nth-of-type(4) { animation-delay: 0.28s; } +#formContainer .checkbox-group { animation-delay: 0.34s; } +#formContainer .submit-btn { animation-delay: 0.42s; } + +/* Мягкое пульсирующее свечение логотипа */ +@keyframes logoGlow { + 0%, 100% { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); } + 50% { box-shadow: 0 8px 30px rgba(255, 122, 0, 0.35); } +} +.logo-img { + animation: logoGlow 3.5s ease-in-out infinite; +} + +/* Световой блик, пробегающий по кнопке при наведении */ +.submit-btn { + position: relative; + overflow: hidden; +} +.submit-btn::after { + content: ''; + position: absolute; + top: 0; + left: -120%; + width: 60%; + height: 100%; + background: linear-gradient(120deg, transparent, rgba(255, 255, 255, 0.45), transparent); + transform: skewX(-20deg); + transition: left 0.6s ease; +} +.submit-btn:hover::after { + left: 130%; +} + +/* Спиннер в кнопке во время отправки (класс .loading добавляется из script.js) */ +@keyframes spin { to { transform: rotate(360deg); } } +.submit-btn.loading { + pointer-events: none; + opacity: 0.85; +} +.submit-btn.loading::before { + content: ''; + width: 18px; + height: 18px; + margin-right: 10px; + border: 2.5px solid rgba(10, 15, 31, 0.35); + border-top-color: var(--text-dark); + border-radius: 50%; + animation: spin 0.7s linear infinite; +} + +/* Уважаем системную настройку «уменьшить движение» */ +@media (prefers-reduced-motion: reduce) { + .card, + .card::before, + #formContainer .form-group, + #formContainer .checkbox-group, + #formContainer .submit-btn, + .logo-img { + animation: none !important; + } + .submit-btn::after { display: none; } +} + +/* ---------------------------------------------------- + 12. ПРОМОКОД НА ЭКРАНЕ УСПЕХА + ---------------------------------------------------- */ +.promo-box { + display: none; + flex-direction: column; + align-items: center; + gap: 4px; + width: 100%; + background: rgba(255, 122, 0, 0.08); + border: 1.5px dashed var(--primary-orange); + border-radius: 14px; + padding: 16px 20px; + margin: 0 0 24px; + animation: scaleIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} +.promo-label { + font-size: 0.78rem; + color: var(--text-gray); + text-transform: uppercase; + letter-spacing: 1px; + text-align: center; +} +.promo-code { + font-family: var(--font-headings); + font-size: 1.55rem; + font-weight: 800; + color: var(--primary-orange); + letter-spacing: 1.5px; +} +.promo-hint { + font-size: 0.76rem; + color: var(--text-gray); + text-align: center; +} + +/* ---------------------------------------------------- + 13. КОНФЕТТИ (салют при успешной отправке) + ---------------------------------------------------- */ +.confetti-piece { + position: fixed; + top: -14px; + z-index: 9999; + border-radius: 2px; + pointer-events: none; + animation-name: confetti-fall; + animation-timing-function: cubic-bezier(0.2, 0.6, 0.4, 1); + animation-fill-mode: forwards; +} +@keyframes confetti-fall { + 0% { transform: translateY(-10px) rotate(0deg); opacity: 1; } + 100% { transform: translateY(105vh) rotate(720deg); opacity: 0.9; } +} +@media (prefers-reduced-motion: reduce) { + .confetti-piece { display: none; } + .promo-box { animation: none; } +} diff --git a/help_img/cs2.jpg b/help_img/cs2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b7438831bcbf13b0d8c91a8e3c38aa192764cdae GIT binary patch literal 33700 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 brF%s)-%8iZ&Ng!8AXoE+HQx#N&&2-#iK{a$ literal 0 HcmV?d00001 diff --git a/help_img/dota2.jpg b/help_img/dota2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ddcde545c0386f9f08b66fdedc11f36b28ff1096 GIT binary patch literal 40769 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

rxVrQa%ZGnNPc`0<>10o=*^s7~ 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%0DqNf8Lr2?ny?;t zG`R2Oldkqadg;DMHniz*h-8_@S_|834Qns&o{x-h0YU_9L|Z-?WJUUNb1#oXZ62Ga zHu#arlim&4mTzG&EvaB6j!`|-8odHcDmg3V?<%wZjClR1Sm~W!0Sm~izzC=jMGq{V z_IruC1$jZVpf~2UnQ?Y_G^D?CQ`dh;zp2?e1EK#;!2WmV_#YdWUX}=#pVmRHSA1nZ zRx+ITBDQkM1u@xZA{+~a$v0ha$j#QqHy4%*Y^JQIBiu^F_?PK)2H@4F2dwP=hJNvk zO<2};20IJP!FlyRs5Nx{;JF)7XtZL3?|)O9r>369@_+FPley2Ulnk|#N!4uQgWxHo zs2K7Fv@r3(<+VQZg){TQ!9T62;+wGx>^;8Y?cN&`sp)NjnxO5hQ^qet^{cG`LYaCk z^gUaWwuD97E(dxjAD143s82tMaLVwEB8@dZr4rf#Y~?Uv1oNp3QS95Bx(i^@u-bjy ziE&SLZayR=q<^#MUxnHCvZPK({W^lNG$GJl+y{<9`vl|0cU{yL9mjC0MniffM~Bj& zB^j>m+1s!=?$(DZbp#Zn_S?W&Nj3VEdXWXdC*!yK(&nw9AYm}Rezlx4Xkm^M1n^}W zpo+4zP{fXm&!pYUIP{_oO>gQl)Ifj1=kH4TK$-QOulSm5e`!&NNgbWQUIHWdnU}a3 zU}2~+*y~r*^m3w-+3K(%>dAr?e`) zBVkCvQ-w}s@14M?R5#SZ7+1D*~i|jDQ(}Em!tF5ttJM| z7KuN~Jyyam)d2fmBd%%tV)>UH!2yBBqCTVx?j#kmFe;?MycSV)6k(@%c1E>Sz+SKr zqr#%L0k;4b?rE32v{=)j0ws;OEdU~}I;7dXcua?l+Vf(y4Wv1Cm$-an%4zfcRxR4k zg#cl8pGo?N9B0JF4R7X;DX3=|E`LLXhX4p3ai96xr|H7X#OBKd#j{^CSLbZW@VfZa zRj6jVP-pznJuEC6E~V?V^CU{{6`(i~9xY7ik9=%(n3Bw>h|^BVYdq;hY& zPH{Q>f$eHC=YM7~{M#O`_B^RqR=%f_to+a83BM~&t>*>SU#!AMny7paxwwi0U8)(; z-4w?-@n|AUVz!s*8^jKy(iYwFqWzXwy#p8dAGRUEx`FP@`{LD=3?vE4m1jMY-exm9 zq_TPlma6S{-ft=C5f8y?^s3^kMrrN}e2FoBJ0bK$P>$|9=`G##NGbNO?-k2{;sUTBGezQ1%waBO@^8vtX-HeGu4xNH4w^?8oX#ENrW?mbeeW`KV5%AXORmA2P33t zP@!s=aZTLg?JmQZtfJ8P4FG1pR-1lR5If!u7&@i9WNnje9^Vi+GsM8quxlRzVW95V zioqad!zMpgAf;;fa{8~7ye}&Pj(3ekd$k|L)!8nGro&a7kF4yAa*%oUam=b9f95>N zCUG&CuuLgX2S?a|EcodoSZlK*f2U#eh3NgR1Q>^@&ConJ;@<>PLGZspW2T~f1bUxTJE6NCHVpOoZtfI;21;dNDpK56y%!k z^+8rTF)q{Po+8aI5tNBf%wS&G_X_B$dRwM)OhNv?^XC8l9X|h0iPZGgKG^Z2_X%N1 zZzBm2-e-E$)cIWxhW#HOGmR&kVmZ5bHAn--=)PwwILBHa)?vK+)^<_9b{3rb{yYE6 z0MdVvW>gIQ*ttIj?h=j^z?L;%X)nP{QvsA7;F*dSnp`dj1 znUH#TU;X!$?)fhlS-1TK({`P{#)eoDiI@bLFd`)nV;JEGA~>GNrHM%&MR^OD^h^)^ zb8!(mNO!gGo6lWU4|D!gxY!3S3$H*!KN;s9%r%`~?X~6JEdba9cx}1(%>LxYvVyx$ zxj8HXB74PLS73aH_73FC(D9qP`7)2BhJo(s8!!Q9GIR};y_np5M$4JW)*)T3c{)u> zVrVL1s2n{gzYG|n`m-Z7L{KPPVr6-pANwFk${BST4+zJ;OiM%i8`~$v1qSrhI}WoM z_VguU`x~5MNJQ6fdZ9{%9ROsfzA1F}MQfD@zo1_MR*1qwPz#4My}XNNA>cq>V=$4E zYo!a`Ip4OD6V}*cK55^?%2LvT@*O^n`Qc~Bv2Qg!>yb-?Bz~l3D@Q77pB-UPD9>4* zO6Ib0ae%hj4#E8H7n&;SGSj%VqIc&tCkX-D*vMp}C`4B&xP08)!)eCEw$$Y*Kq0Cs zpQYPEi5)ZZk!-q%xAlXw#L(8}=>?^d5^=2IP(7NoYwmu!RyX7(M&YgjnpZ%jQk$h> zL&RHl<6X%8nT+g``68>}`NnqCCBxV2X5M{qdV>X;gquB%cfXS`{Y8;7KHFN3`kJ}J zO#0eIjocLAvwn(I@E4-0RR=n~i?VNvK1s3|+VGV`B4Y3IY}P(!1kGG#y#f%6eO->(LT*>i+P?@d>`Ae3&DSntBQ8 zvZKJ_@YE$1+|ny~_P!e3&k4Vc3i>}Ou9%A)Yp&v3y1ChI&O9|8xdnc8bb{@nJ~abH ze+B*Ue@|&FWXrX=(KFkH(i_T_Z;btZ0dy(o3`_;te0pUqBmVG z)shZO5nT%m+*Bs_o3Q1&&CCmiJ;*8c;jttZ>aAvOJPQt6H)l`#|p5eqMBbh7l zdQ1QMTz?3o7W$ETR3OGpZSYSKKS|Z@E z4WR_UC^S(L3;8&I?(g0E*qD)EYP`;z9R1A#9{M)ne50(XLI)Ev*yBc1)Q9?|Ay?5i zAxufCVRHZ}LO(oM_1@oWUhp%B*V0Uex8YKFh!TBcE*L^*7|!zHs`iW=<}K??aWon{ zERj17Mh`x9gC3|bX?CdTBO_Umxt8M)feef9g&3Xxj=S=Y3G;UtJrDcmZG7@hnV7CG zr1lc!T@m3@L`oL800(hHIc)xX*=ha$(JyjN1pMJE7Z>UF!?VX+5%8%m64=aJn}$ly zunNlI#tL(eUUMZdZB5YkFnjlPVy!kZQ;BFHSiShkq%-bh*(Mofsk(v$bVhnYkw|h? z$yixDtPc&5y_tO~4KEcd5RhWnloS-`EPC_s2brAz?#=&C=k62T68#)Y5JY%-Om~XZ zcK$9iZCpfoN+XIOx5{pzf(5O$nbAORE^GDT_9p4bd;x1;6^HP<(P_25iJg1U%{WkP z#KQ9xz^P{R_`7F^=spaeh!N(;FMbA*Z;Efr0mb)d3M>f~OkjjDVi!NO-Hq`_xdsu^ z$_YV9UGnVFAr1*hfSW$eL{54g$!)lx7vc(>8X;}7G|cs9%c8m)y6kF^-S>7^=nh|f z8QLtnAcdfO$@gydOhk@k-0({AqZ>iIg9ng~y0ckQ$Mt#J1_+VE(Y68r_e|4ji4iwzI!V9 zQjNm64ZEngK-QU0PbV4Xvw3fK=TzvO*XR`*#?nm`S);N2{jg&;oLVy?EWOJliQ~ic z#Ew0)Z4*D&Zp^Rm``yp$aKR<=B3g_qBUFY5|AVo!j%owi(tXeXrBDjAP@LdatT+TH z-r_FB-QAs{h2ZWkh2mD+wYa;xL(o89?p<$Y-I@1h&HW=JAz4|0m7H_-x4-YVSzdAT z9{5DbQw@&cFI)e}U^`zY5vrFhRLMX~M(7$KCN>P`IyJ?T8q;=D)a0Q#GcPo0cIUe4 zO2H?F?uu*RyhowlXh5^GyFApaCS%=2?;;>~cvLO4t9z#!kH*6f@gr8_2+j(rN=h=m zE-PcG36}?wAnez(z3Cn_#vI)TW@SHnW~$WZ=@_$TP$(~uGVsia8B*KiyK==2+Fdc? z-BdR+G#!HYb>YXzDo~~2Yo#Y}JkP?iN&~9|cH^eHv^AYhFfY*U^Lf@yWZ`L}^#Fx5 zQl+PTI%MN8ri*|ZV<(9$OVF}#o^8l3WZ^-L3OmJ(EYUpkFTjfH9_TQ%O$C{Kz4B!* z$2&&b%BTd&^Yi;DV~q(>Uw)5(+XmUmO}5@7B}o2tYDFin|9%{J;qYd>&|}xfXcvTQ zIvEn~|I|sc0XJjnDe9_M!1$(|JMilERvJP}7?*pW24BXT?4ZUQSKlwtH_U)=AF5t@ zQCp_XqsL!B7npb4-JrDWUYoA5?Bwau?ewrQR#8bu`;UbibH%7BZ>|$@gcmCh=(^9~KnCJov+kKlg%@Z+_K?n`e}o?NY%VE9wd!u5gGV%;C@ zud{WzenqQ=qx`6gcstk%J=M4H08MPZCEw{o^;8b-%6E1&x3b!Rd0VNW{o=Xb*$9Mn zWF#JZzw#bA7-HrcMe?+R0uif0NEyW0o%6vdyLFbj_O5Yrt1psEJA}9JH1>z%v~*Y5 zn}@b%Zox@`zJR-it(?=&r+R`oWm~*lb@De3GmkGizMsYqd|4Vh5}h+umA}l+M%w6~ z`FXO<5=62JR?n~fE|3Z^zd}FGa3MIb+P73rq_wD!Z||88p%l0&ba}R0e83@J#ZCkD zu&@R6klt*Gh3*2)4VP86h3eVaWAQv*VY&<4joVeoI(?9&9KcyB=9dYWfrPF#uR!Y| z33b02D2o%$&al=9;xplUZYW*{Xha~O3Kd6Ep=U0~BZA3XB_K;g$BeYza84r-S&gb> zZE?d4cBwWkvpspnxs2yP)L<_UDx#0&c(^J^!71CexVia>?SP$Ss33|zc480Lk}=Ah zh+CaW*gc=ke>iG|7fv`PO7hQy4POA&@@!2p+E58_Av}jl3`q?V1ZvhLdfuKYO31ix z-MBVm1F~%`ie2B6-X7Q#&}D=W2h7}+jmiwsD}2TRj3XVMt9_7;qlls6S#sScfeS+n zTfXGj@A|Baz`=2jRV_pIHiKri=^}bty3P0vAL2`eBu;(zX}IyBo^q9piNBf0)VG;5 zX6h```Ipwjcu4^IlGl}Nzv6oz@4{NYI9aCE2T3y5;Hf}XXW|^k%-Exb`TI_QYwy5mx=!gUH%>`$yEQQqp#o zl{91EjB$k4J5<@0-)7k*MPJj6luny65D^ahcr%hlSl0L<($Ip$)C^3dIxM!%yE1h# znH~cZoEi^JWGF2`aSMWZ#l*g4jK?4dEFw#7==djafH-SA<5KnX{N`!McB?Uy>WMR* zR>fAa4Gm^lzE(=eTWGwtb54dcTpVfE=$kOV%H+TAMs5auNsvKnh!xp+T@0$sEsB(^ z7akm2iO*Ur3b$Owm99^=-x3ue1ZP=tyyXScj=+zJ&-5oa+WP%?s6-M@HzgbHLtT+S z0n$|45npVOGgrfEDWwSXS*e`2i?uWtBgGw(Itj+Ll0-rx_kkG!gsI zHdD+(y6v?Tt&+raf?QT8Qa!I0p&>EW3B3KOE6#+9SIc#RA+^F74-tGa{3)}=tq|4{ z#-A!rrnRx#;wowL;6g^KWk5^)z6h1ohhFRlcE(0Fb3ixSQ-{{Cd0SM(&4rEaxh!LK zqw6>R{^&bM#}Vz4vsj1GX}g+6CQ*^;PR;H^B@XQhy>E8>n*?^Svd0SFBJum7Pbt8lhgsCxEpkrPCSXe_$|U=Z{jjKV(pE#$NSzi$9JM-$v@(xK)aeg z*PTN5g2y9_N6YDog5?cUh8kn4v-9NnF2EUkardIO%`bL{_jA)KP1WDDd#0o$`uA-` zP`RKWUSRG(g^!wQO6li?aZTn{SeyJ(hl-~y zt4p^XRenuQ_7_UM^O8j->BC?artR>b%A%n+jhK9dk(WpcnPkW^N%6c>-El4QkCA3h3!=v5kLL=~qqf@Xp;vwks<-3Z_LOg5h=dFq{dvs$$8LmRaoT1^%Cn8#QyVbLAKJaG^<2B$l-<$QRR z3@TUClnGWKCQFI4mxdObibWMIq-WWn@GEic|HlEj8xHx%f8A(aCm zFy*8yXb*V2R%qj@EVgBD69gbCYd$}XsB8*l>q{Is@bc5DF=DKH&mV*nKd2lDn~BW} z$ACTfr6YB~GYW;A^{-qglQF8x;zMl8ic4*0sSe7kFFenL7RZJ}7%-K1yKA?L*o8}o z%K}&6;Deo8Xs(qN%E_13BqZu+CS34vYxK7KholJZx*L5Y^P1xja0^A-H{_qb(6X4&tARMhD znFN12HKC?@S0DL{ZD7pfS=>vwCCr2-)O3rrr*TF-07jhP*X{Qyo%NNI>GnEc6^_y#XDVku9$$dfHt8iiyp<1@V0Mpb@7eHOOwa?a?L|l!*eJ^_ zbLe^Y1s%j>+mMZ3HIfJ4luWS9$B?Bs4_sGLvqV0IJ^e=KM<@PCoC^%}axH7oh?5C| z0B(OO(XpQ!HoXe^Lj&M-;b-M$-M*}#d{DG-p=&Wb;z?b0qVGIl8vsvLNYgn}D`XAe zQzlwuQmK+hBCgd9Q7$v2bj9j8jd`j%IcaU=n~KvZ9I2e42;&n*DT$)0{tmPprqQ4$ zws+mGDsUwl`3Sfx@|6fR2crU&_#Jop>G!Qr1|aeE~- z8j_vQdPk%0Op$kfdYN7QdfK|J>#~Lk679d}7BAm_2SUM#9;!a3sv$B#3G;mqWJ#B5 zX1qqKd`gYAu)Dti>bqZY(*k3+xK- z-8?JL&`@X?l!WVB+(*pfv%$96PzT++&1hVU7n&)QKe;$Ep}4}m@4}-iO9Z3+Dm>g_ zNIgRCR`2i0KF97?jlL~Zm-%t#YXys{!y+2(+KbR5l(+Gpz z)9g@wEl` zlwqJ)pIUG8x3$qNlvIt>k?OeSIbT57TR)eF8fI`}?Wn5r} zMNMN-a`^KbR(>^`)rBQCX7WR z`NX4x^tOb*B?#i19hTca+^yW6Z_r4i9>>K*fU>$iRp^(7EZmy&kU;v3kaMPqNE(zK zU&t6uNy7$Y zVZg08&2ATn*5UM2nR>(rNT~BI&`_?Q0Kg@UTB>J}q?H(X>zRNqfQv{K9*XQ4{dqdO z(BhqFjtx;NJCOjlpd|_&2-$uu^Z73z$wJhYuL7eKZOCy%WgJ%?<(N0)k+GvU0t<3)zx>QQT|4=YJ}J{`q@4Y7O={G+tz49BFZYr(4hq8jrP9&Y^| z2>JLjS-6rY0d8&dHA?V`)^qsd-n5d1Z^y^?<>D{E-v=V!msmBIA90tvUiKFN9(@!h z&K(WVA<6$lt}%x|G>|Yx1VUB^Z|c2Ua4-JCopd3<%|^yzo(?p^i49sICMFweI18R8 zSXxtTmUH+hDu`B@o(bR9#ymN0v7xS9KQ^$~vjhr0$=iYyDDpH(?IFuZGpI1c=aofs3l(*dyiIX1` zG&d$%MF3Til)5t1<{0dKB;SvJaZvSPl4L2g7yD8bf}+hFuPb|*?{+&D8z*%|)i384 zrv9tRl}Dd_fa;dFlJb5E@mPyJV6Y(`e78`kO#c_)5j}n-j=WsVZqJOgds$k}a9w`=p{)(mI7Um=2R z?TNGr=k+QGO<2W^a7$W&LLquQQ9wTm4-aOmG3jEzgY?i13rp`uLIP7*?etPi$@Tl) z!x>t*63A2f0?WblC-V0IMTm#8b?_KW#6z;Dl0Vfm?7C7H_0Iumsj~79Rc(hm z5<4KCEN%)DQf2GE*45Z@=|pJWY4CXbu;I&m3|`bl|0cwRxUC>0wI55+<|p#mkSfzLLY*6tif5 z&k$f))fsl7*ZB8VRkN*C&Q>uIw>PL(hKuws6Hw*jhxrjxLZ_US&5IYw!)7c$Vn-Fr zLq%Wkg;!_tv(c6XH!9P=%Q@^*3)L;Xndrfm4-8;B)Yf293TW?flH-$twi7N z^m~jA(EYW4jcSoCOj1)!i}!P0?m z1iKF2dCNOs=j_hdL(Shs(RM!%#|?Pa344GN5Bc`)E}(p@oVjxIcOU86RrDXz42+Q( z$(gp#*#>;u{{o&Zr*IQY+;7-R*4BQTl#Qz1TqEbqTv4mYU0$f$ z_QZ%j$74JvR>zD#%x&uyE6xlxZT3DB^-8WOccHdgZGl5MWwCpfEQvxsl!Zi*7<4uV zYe(%kG>a&L2_sqSya# zCm=1o(0`Y$iTpMtl!vElWWKyy1QnK{zq4QAvEKECHH&EWJcCX1*V{L2GYtFSboKm} zfa|g|b-B;jx-b3hA2~BGl^c_He7kyB?ME#1{=6_LsM@58S!=K-7Pq957%RA4X8huF zeP*EGbd-tB0Cug8Tf5y9S6h&c(zQ4Q1VlQ_wH~_(?*HlnJy1sv#qpY)u3s0#q*AG_ zwfJ80bn$FuV~Rv4)2QHgB11`wFN>KRD!R|WR)E+vU=fK4gNJpK!xMQ&|kHB)MM+5D+gga7b7G2f-A4oyrCA|JoveKJ=NY-37jpz9bVaFppZD*hg(pM9-{z>U^BN()E`W*jy z;9Ei6Rr7)=%h8fC1xrO&^T&@}zO1#Ex|!`@me-=h z9=AKslh3{ zYq-=+Lw);Nj_oDZ7F+e@%JY{W&iY)tJm(0t_>+v@D;Ri#tl0hzN0K0kw548t`~`b| z;zaY#=L7{)p7f?0CwA6;K{4ol`V}}SjsxLpBdZx$A%0ydAp#S`COn>NRmD9<(Jzt= zNcRA9Keg|bmqd+^4!S}U-m>< z(J+M%gc5>59_kd~$BjkcF$Ve~pCK{=qZoP5n0F~GDx5zeZEm2CDw-8CYOyK}hXSLl z;{(HaroU}lOf#H@covvl90@O<8%qA1SZp} zXB3pSs#>IG7o%2tsD+@uHgb>e`E3?<1DtkI0KI~Ljz7P;)t5iw57K)1!tRhANC1b7g?!Wr}%YG-9}9kG)u>&!PSqDUOz=Wu47M{{LN=u8%E{Ok*2WQ zT^&~w!I{VTh27=^2Zy@4v{l~{SiaC;anj6QNZs0aWBf}!$3b2M6g%sG>L+8+mDTFykKrZMQFM0GGA6; zaMB(blYg9_Gh8O}p$}L!LSn)?0l2T8E1}AkHlur=G0BT8_<9uht;u27vyH^wg+6e` ze{^$Z%ZIm-q(_zpRbw(K~1#NFJ2riNwKs(RYcE2PwX<;xk+E2Y-0$WWl`1 zo?%f;FZixF3T8X+JHPeA29c6Wqd1jox+}3?7<~2%e4ekXt>*ITI7(ivdB%33?vB0g zm-ILlJd#qO`twGBl*HhmKkmSnEOlF#8(-k_(ap`_J$#G*M*E)wJ~XPQ z%i)!+^qVW}doL9{C5um{JVSk@;*0qQ*@*6Q1AU$bfreFJg%B}C;G#u=F4lHU9xcC@d0aGseAq^|}WUkKMGw~I?pNGB2 z@58ur{k3AUO-m1Z_b&kO%pd9=ZaCFr_^r&@@6TTV@R}bGLCm@w1N;jpSI#G@C}1fE z@DjYJP!8_NmAIZNLKcdnMyg*y35ITfmDiQ8YmZ|;h)!$M)A1lb;zoOl#fh253a`C4 zU*f}-NdvFe0u5D2d+cE-5;!}7B5wn#l7(()eQtq8y|L27Bjgx385^!h6AJ9ZD)bZ( zBB6HZH!~@zM3FUK21&C0LhWle3Wm?9L#v(pf?$kPiE+dDrY@R=#9;&q@}LaAteYX} z6{8hxkbpzBjOb*32@((Sb?04C-IxX-Cz$*UM0hAG`7NO8+7M$SFKL_Dkwa`8oO+Fq zJ`ww+9a^wKJXV{{F3P-?!8=MahIlBER8XSHlaTq^ zc4xo36oB%ol!r-jel1@q?-Rj;2Q6(3zw!l0JcJ9gIMNS#yt#GN1UJ(0VTyl{*S-u+C|d|e2%KK?I2LcK8FuTA{%MRM%Y4)%lgsDslZ z3eM3n_Z7waH;{1uc#d-xL3($#?er;3($YJ-PG(*mCFNs+VK;ViY$)>OvDP}4P;bYF zQ;B3yebGbjYkGtTYnzs~%LudX%nu|+TNp$sZ_Sy>y+Wkl@Fi`klRq{1VLT$POHP5> zxF-)?71nD~U^~I8mn(=U-Y*uA5K`sBpN`*d)SGWx!3nc{)5YPxWpLYS#x7&LS}>0tO(m&*qG$LSJM=t}tnF_Wsv5$!f?OJNjKuROl$^cDKir>3`f7a3!P zL*p6{z)GU&2N%xdvOcUzh3+y0!o8;OyKP6wx`YW zULJ2r!!)&=N$sOa@0E*22EIhNjbqjiXFxvwPffD?U7dQ%tSelZ^G)u_kf}yDsFq(p zc!FCkV9#=AsQ7Wk2)rmsLRp!zMijR$IfEC-$@mVd zYN&p#%hiM1GkaxnP9%Ha-i=NI{*ARzX1&rrSzN ze`*~o8t1*pAm@Gn_WI?;ceVt(z4=bIZ)5QGB4v_=ue89x5p%*h7 zV_yV(Y^>Z=W4>U)sA3ArMPgRuT{KrQ@1AB5MIeMlI;GvT3N+W!`dq*IKF3;{VP#=7Vq;fxjvOV0$%m zy45wBj?OA-A1C+o%v-+4t)^%)7#LpPKpMH0 z4QBI@KXceqA-!le-_Yis-q5s^Vzj0wyW(y093G2}kjhB#DhENJ?PR!oxXMK*lOsR6 zruwE5KMY^`&4muAC%M75@&@&WEA5r;KB@?j5DAhf5j}Bl(R=SK<~pk<9Ads!^m_D- zuRy7l$G-sQ+rx>=>)weGI|nM<-pemUc+L(XYQ%3Ojr4N#*)ICJk6nI)NyZ#Kn@wz` zt=`w3!|@;{@H5DMv^!Yp4-6Zg$GADwjJN<%dr`?^V-Au63Ln6YkDoq!T?+r3s)XRB66BH2fJ@) z)w>xm3DleD%Ve=dSGcpgPLkO+Qn*N+jCvvXv^k#tfRuz#7Dw3&MXen1zi@zS6UENXV(nH9zG4_iq5hIWyaYhVP+I(4(D>^Dp}kWE_3#SIpUE|MwuB`+)Xu9;6YD^^i_4-6|{z z`+FmB0>-IR{sJg;ClOm?Z1@FMH_irDw_4LTf}3ox)l+NhR^jgyk?gWk6PYTAIFY|b z{u){tJZ9pp@}d{}1jJO{9ijJFePN2NDy>BkwkG=It zoiW}Gzu|dN)if~04AUb=M9j*7%ZFvThDp6~1tRz}##uiXf({Qgm-@p^Di7@nEnSbP zTl1OY0}GrPBrmZB9%dI+Bi=c(-Bqt&6c~;k=>+c=>soH?q87d{!#nIR(4qR3dR-hP zL~5CZn|fxn>QxvY{9AQT;6@X8k?kA0T&m?;IRbj^tn<(i=;wH3h-Y_DCBGOz*-00BTWmadnlD^E|&>uQ& z?!Lx6%mmAtyVhBM4RUGyJb5f1JifqS`*zT9OYD5c$Oy^-=nFlN!(4 z@5^M*xAtJI)L}OD_!`baxk72WY1t0YLOU5uG`g27NMhFH%~2UZl;^$B_T;mqZ~jyp z(7{9lAy$NgVvrj0`U$vfNZP{Lbp?2A3@uccF>>u zbv$ZJ_{bq(+?R3dn!sT8%jT}v&FO(}eavkCSSSnEJ1f&W-=AJDzAdBavV#Im%5m~v zy=J0$&^BX`1_{PNzbw(bYZKW*s6F=U*)>g7P(Ye!-#S_a1%>MO9i61^l&cLUljtte z(=9e!$#$kBOc*|k7BeRiZaSPpOUWSy#sG)53yqQ?c-ArOiwPGGEw8{Waf}r=RUM~P z!60H4<}qB2)`r`Z8BL|aW12D8v5PmAs@b)3W3W>&d5`<3!in9!=Ases@*<_RtczMR z>`Btaku(pk!@*pK%CPWMRVnf-sb`BS*0ruYLbBdg({gO6F}_VNDv)h ztM{o+to&@>+Nur$5M+=PeS^;vk0dCp&z}}Z@wAn)gsreDRBwZTph>7f>L^Kz|Mt%H z^&m!_Rr7$Lu=4y;RPe2Au&eSA2szr*9)sW^m3%*F{-XGI*+9@!e$*)NVK4FI9VkjM zGvEBOH4-wr>-Wx=46bwb(T3e?-C;ZPA zk@_s>xW>f9A4|7+@u!3O#$T(dhU{Y*B~J}ZI5Sj`Z9HXvP}FU_l-%rD`ZN$xM-ZgN zw(eo|VWV4KUGaH(CSbZ!E=yW3&w$POQe{@g?_yx<0ivgkpgCDyfAIKe^`C|5zbsic z_wqeg_vw9eX)>r+tBqqLFr1G}ek{Ode2%p5I=eNW@+DNuBr@=qT_vkv)D!cv7mlm4 zM6WWXVOv~RAYyWug!bjHhx|V^?He3%sJ?ffIMpV#7ou61t0j;vOV;hN{C!pNuBcmE zlI?()*rHzgu9f4}1fgL+88R22T!HAsG^BW-TXh)!)1Y@V_l}rTOX7e`{HS|^7@Di0 z4D<--88c{DEAZ~~2+m!~j!KW+;>tVyqn!)3fjg`BEC^Wt0`6(zEIF|Iof~K$TdHuE z=${VDnC1iUxR(6_j1llGq`W-iW3I96Sc~~?AD_g9B;sC=ZBVHkeLx7h?MGDQEs{^H zpUzGD@>FT#K%mfts^Z82aT>p>e0#s%^72-YA{{whla~ZMr(PM?vXy@gi%!{sSK;Wj zQ23OB2KYg=4ovZ1)|UT1o#0>Y3pQ>9eT0Gds)jIGCL;Zsb*mA3>VKH=2ukW%USS9L zw`W&uh7K_?2l_2qLa$KI6T5g%tTwKLUuHiYK7elJK6o#hkV!sN#|~C^r`+zgcI>gGqiz~f2wWL%Alsq!#|<_eNYzqc%eFf>pHWF@y?a! zY$#c)R2LTUzTo5ZH_0We3k}+@a2AY&8cNkfvoNA@(h;98S*FARp=e1L(EN|4U8+tv zfNww~0FMsS{VDBV0RD_u#6`<9 zh>^Xx}6tT5}0etH2|`Wgf!RwQ=FoO#RE83-TGzY`F)?KM%~QD^H$K# zH}T%;Y@-#=pgyluUppp#m7`;_KEH3#@#8$US?tgf7OiY}W}J5aE}MPvv(4>K!AY*X zscQ9_AWyIkfB;MSh!wj{*~Qqgyu5%kl2vl94NE?xo#J9X(wc=jOR<=Gm~Ga2du;;q z;*?Ag!ZG7DE01&Ud$zAVQ_pOruKlA9Vo@n31E*0dUP%}kf?eI0j$_o?(+Gy0;&v0K zB|B$_tMN9T3y6)gljhd$Iu2V7?CVsY{=8S4KCNK+$ax%Q*eXKZQscmowGHE&YfprC z_WPE)nHnAisyYV{t@3v&Uj&C>D9$o%JijcYsqCk_3>n53yVLFJA*S$P&qAGFpuIoM z{HKsauO8zT2dL@(75>2o^+0dhyzYvkgSnr4ej^s4sTA;eL0k^^hONIJ_DHj0?y{jX zj{;=CZ7VCa30=>QX1~QLb=m0YPwR(dNM~3Bm#9_M zNOMH^Yku!-!Q@s8`D+q~2vjU9E~S}~;s8~HNHcJSdsE~c4!5Oi1Wdl9`@$CkQg6q- zpZApfkkKA{aoqJt{C(;0&C2lmaNF^Wp2L=adyF-PapG_S z$WqL|jy!2e@0ZtnqEfwdyJej9qYQx-Cul^1fpJKqlx?(0vChGtK~m}D!B;f|Ab=3P zD<|?a&J0;`h6AYOgp{O2x%LLfiMJqD=20J-MilbB*mzIWOSXOlQ}=gv@QlfGo-@Ix zO#)~sLJ3YItT;UUd3E*$B9+=3KR^O(&)@W)<`W46k@w20nRvYE1_2q{%hzQ)sH#y? z8y8NIk=I|iQ|;^l2tz!BC)l1z&G0a33ZCYdVfGY2$(ci+*wN)^!1QQ{e9FlAbheD= zjt=8{gKHFZL9~IdD*o@gBTLA7mgkCY^=Es-UrC*amVNZT+b-T$BXwY|Ns7jh6Fv-N zAft{A8r10b+@33DLZ(*F5eS;JqF-!D#;IQJAFTnCvk?$(6#$SQ6l{>a|IFjRtrn&V z-IpuJCV*M4k5d9xwllDN18dW*Ch9~{sRS_^bB2zm3fNH$z2Wdfwqn}KPf4F9ZNl&RAUlS|E7bIoxYN#g{~6`v%2F54{B+M?PudVp zIg&jxvfBioWWntzTHrR|4zU3(vaj&+(v&!*_Q}| z!DhPLCy$j9-o-wfemIBa;%A{HVhCM!6sdlwgs3&Eq%^%HlUXVD>JRhthbac^Bu%#X z!u;tSG9m6cZ6@zoCHav2L_TiCeQ}4TcNUPv;t{>^Dq@uzUqL4WykRe-n<^`$V=1}+5 z1ti@IWt5blHqg9al^Bw~q7Oxm|6|8DM12!kfY1T7I6u@iHA#OqB^^$yIrbZTB>oX1 zl&^k_zf;JPbhuwCSeGIh$&k4%0BJgf9T9vEe5Wdj;nbo}1o3=HnQ7<-M{%4fV)cM z51uUO$8r=HzMV~MHXRt`Xuk4%QV;XCW^wsL5{~k^u8{3;4}KcZJ67z--tiuI55E7J zb`5Wnu)xCyr_qD|cKSf`o(L2rukDZ6*@KrtnwtVAjk;w1s6|;o7$0T@f4CSAl+Au` zX@3UtiQyr+B!@H2?%2?-RVArDQa4qGjZJBk>Wy51bvHkHF$F(zKR~2efB3It$)U0- zGs;XMkgB5f!^eLm>axFQAe0nf74+IF=Ume=S-_2tKRf~RUGDDq;Kd(sIgT*oD4;*} zdFm@6(!~DYXL;d{?SdnyW|UIh7#UG_?0Jvn-r_A84FMh<`KNblCh8js$}#-W$0SGU zuFwlDL~|S?`+Tpx4D%93pQ`R1B}akVYOcxOxuiC*+omr{EEsh%WgKHEP`;dYc9OJ} zbzy2mHuPLCKe$6^bPUK2lt zq?)*o40B%OR}JKX>a{V7M2Ude2?27;SEv1Z{erbibxICykneICrp?B`S_e_;SS4b$ zyQ}+95%Wp_u7f0H-@>U{Qm}&k$CZ!h0CtJO6yZxP?n7r5aPy(l)kT@~fDCbJUW7JI zXN?ms4{7h^XQtS0QUBg7M}DslYWJYC8T2Ybot=`#aRHW~Tr{a0gr5^THfU)cGmEog(MlmT&Aa?;y}3V)Il zw|B5opt-%_L{+?ksPM&m%0FNpa^wM=s?)MGJ+F!p3*d>lo?l*>sqK#_(n-QxXOWQb zRO&-~sK!nXXsiUTWrIpZNQEhW7)K(F7n;#?Q|5)17>Auk8Z~&FFQpR=Le$iONav`> z*_PxMXz{})gFOA<*t|tdDO~-9eEw)!UO05b^26Hhg6G4URJ5xCZWB(wXY^EBoGFT$ z?DFsU38%xg%(jHH?N=GN{nEwsKSs@y+Oqct4u`t&Jn?MQ)4H);18SuC7D?m^O&nREmJ*gtt2YzSx1Q zqH<-%kNgb~j&(e}hOPR+NzrrulsH((Atb-pGb-@+;AAp6R#fMsO20hIVoi3d0NVg! zet=kPxdgka2OtZK3;ij3*1NP2oTP@%Mr5qFMN0tx?kXXBx%GE`Axp?^^%4Hb4vMW2 z#*1%ql*dOfRewk?vhRt9Rv0nnvuIa~yp^JggEzTxD#^(lWQy3k3Y?nWO6I@yG)+;r z+Tt9EkF9Nf^t>yG#7$(o>TuqVhvp}XCa3bM(oyN0Bb{ZfYXvWHu$dq+;9-(-dm4s* zCP4ihGGtw8sq_Ot>v#g6=JI>1)C)UzU*JCd|z9u+yo11>8 ztBug)Y-jCKpdb8BmFgGo{<&wQDq|iOaopRIjKG4Rx_#tmJsIm*v<)>L%tdmfLEv2W zhve9ls>rHNLRjhXn+SZRJxS&1=(M6+)3i6`)z(zG80rXoah@0uagTTJB(4>ut+OKB#oD;2aYCK3)aIm2=^&Ep=33qwRg=O| zlY)JY{xkeb->NCY{xf96OaSgz{}AO$xKhIkd=;&0nfE&(o(z=0A8}4@gMRw#@WqBA zYx+(su#>BQBmVGI@l-Z2Q~wtC;$|DDU)}*^ z0OULNb!jiVFs%3(U`(Y-apLZ=DMuo?9U9R5+R*g$r+am)MB2kP*SII!jhybp;! z_j3J=mQTK7ocyZA4OFPj)}51xTuKPIj#n*wUSW_!m0HcX;81@3has$8^+4prNtda9 zmP}>dkQdRT{73ASKgasTsZtM~Lw7H+)PR87xc4-5c<{ie2WXe)_*7O&0jHKc;}j`& z;C^bA5$hSXk0Vu+t#R+cY5nr2xvr!Nn4v+K_dwpV@@dZa>HB`tBliAWrVU@}1uL4h z(>i1=LHgS_9LhPV@6WSc3?nOBdF-gZLs>a4r5d%(F*%FT=}7RQVg(u##MLW*uINS3a=7BTQjr`+7(yuCNxE}ljLI$PE zm$IpMPA}y(fH!X(vU{$6ve$k$@b_fFgBJRSos7s7n-5w;TA@NW@r|vNB?$d&!Ndp| z`=n4xd=vG>z*VlSkb)yA9FHIe-!$Fu$hkFnCJ}74^ zZ!uY6kShbTdV>_oZyipB;06T1t5BgCj^vnTg%)9ANKr0#vm)(aG_j*Q!J|vb@?BIN z+2;DV@_^1n?8A??P+-ZUrMZVYB*|OJ4WL5D z9(G#3C>sVnV99wP8&OSZ|CD)UtK@&f7}!+iylwM2UHjcaLJVW$P}bUady9f#e_j__ z0nFzu_acZw+zIw7NhsbrW1_w`h8Gn0IOknzKH`OqBh?t10e3zuW!5VI@FCk{8@!JS zweMq4je@Q9F|n`vKHE?GWtWc_FxaQlP&jL}j1X zV4b$PcrZntO95*;C}`rgBuz&$hN}wM_`2`a+Cp;EyuvHr>%V}S+X5jSe`KQ;l1JAK zgln9KQ+(G^Wz{689#)NMp-3C>_ ziimoWNHhF&@#z*u!)O^iQ&=T)e>p)%oDj5NSk{!C$j?GW+P zUKyW&QcA{8M|L@PfT~g1W^!R|+A0lt9oCMU=>Z zBQkzD+tTz9qgOc&FG(3Dv_6;hE!SVd1IzdDq51fQlj_z)CyL6ZJe#iPE&7Wp78E!d zZ9&VU^rW)z3AxK;kl_Et2MyP!|2aaJH2p&2%?taCDLl*Ssx2bOKK1cly{+U5 z2P5$-ywIC#b=$Dbcnzs71&_3CH$+uu2{Spsi(w4t$anp+{9jIy@P1jySV3{5Rekb? zZ%?;#2KylQ-1#LDqcoF}M?}6kInL=+L7>#4L9*?i;||*Fp5s5FV^jR>+pg>B0>&y_ z@zS@-h)Lq%4eX5q8;*k2@C5cp+=H}_O_77Y$~A(r__vpe5|Kv%YH9|9B$D+a$o?gx z9?8h>ee*Ic9BXw~2R_8aGDMAfvJsL$-N26D6WabJl#}85D$y8^To(5RHt?8MN#I^& z$8ABI0a=%sbz3F=F7Of0KLY~-IsdKomF@u7a0Z8_2$($>95S72SJg_Rtt^)DlIX;%s zmD;w+t>Nw3kG`@TNKlQ8#Yx^qcDSD~ilK|r)a)^1?)M2VDhQqlz%$A7-gi~O4SdZT zI#y07ll5brcJ4d7WSz|r`8Ekq@K8u zg~nXOyLbC<;}8vb6|a)+2HP25$}drYNJ=Zu+Jl!=RNaUF;k)cwHd;vaHk6*pBt&L%PP{WgF(8 z^w6<~F-1a1gIcml`T&Vd5zXYcWE&#U7J#g{>qzXAIB}CfwGlci5{9gkK1m~r0y}?C zByn-!1F3C%GZl(cesvbl10AjFiD>WuzGxFUfP%5G8E_J*WAg#_G_F~iVpHd=XogQk zTrf<(z?Ca%o4iu-sXe{M&32%8{|naP!Jd(|$8*U@LKbV0VSSa8uI|0F5;Hf0&$UK$ zu#g(RQUIpz)=8eCBCb3+`z8$cH-xwSK=KO-Cxm66PJ2Iz8Q}N3SiAK!Kgi7tC8h?$ zIQ5qn*pF~?Sy@5&qgRTwybIz3=S9S1n{51p&!J?J6Un6}=gU!CEN3^ocEKi1=3c`b zZ{<^R<-0BlnwTZukaUt>5y5~Fp$ znR{H)I`yNgtt3th{gNle2I@;dC=KeU@VB3{jtf<*+Z8NL-OL;n<`kNq3k!7NhbK9! z9S$bFC!=R$i_{38Cj4;#(pTTpv$-i0fiG!{`@Us0wTmg$7i+fBh@5LGJ7$PZk(R|X zLTT#$DfqnWIB`!~wbMh38<^t?NNQU~XM9EWd`as`j^#W?KO|P1%G1pu&0&omW%#T$ z^E6G!kP%BPLnt_Z`AL3WLMn&~oKzJKo5xS%i%TUAkAub&rfIdJ38=NP4kZ6v3K~qYXC1V6y4sXp)1Qm1dVr6 z`2?a+YJ}jp>^f*4L{#tT4z@7qjaQ1I8LDAnTu7f@D&na!b&Et2r+*&p#2WrCQ4RR( zCjV=H{`$|QO%7fG0ftN3$lEkU5=gvIf4hWigP=dVO^PbE$~9*mUZ%B2g-oz*_!;#h zC9k0fv!kU198l}D8De`O;&69mcIxGeOIh8yWR~mG$GP1H5zla^jh?cP70sbaJNhZ~ zHaacNMUOpm?g0BT%fn-ACz5S<*&HRczix+>6&Cm6Z z4bl684I<=(HI=Br*@-#ZX4}rB#R^p|p;_tEz>)X0#n!!;@Wt={s`~stHY-4WJGCzH zZ^Q$HQXS5w?v_!RZ(#uv6r|S7>Ak#;d(bbXaE{5?TkjYzXrZWF$f~QkR2VM&SOW02 zW-*V|X~?%yjI>gtky^uw~QKNAH8I3yI`>wwTE z^L~cA`TP6VUYI&O9*pC8Gs$7+c!1pv)$H%Svv8G#laDW!52qCT+y-K#-o-(Ozpa{j?Z2u3w#fq6G^$19#dr#>Ct13I-8-RT| zjcztzM-PxDrZ4rHUU-46-$Za9Xa8M!frw+G`okU!A!?e;x<@w$VB=bti^SqvEWtY1 zH?wh5LfKzM>e;ZoJdiZZgjtlo93CfmHo(9nGMmcqol2}ZZ^GPq+8byU8c{qlpr_%f zRT#SaqXSi|937|m$brEV-hRl#Y2y}cbd)WX70TD+Qyn)u1uyC&E6-O9!4kTCs3cJ% z21!5CX+pJ|enwX=o&`vYxPYLT!f}c#?#jZHx6SZ;XogU6U$eWwx)=f?cPtZCT|aZC zNZ7s?7uPJs_RX}#JBbYXGy6mlKd>;B3LEwuoKaAT%f~&U?8JVAY)+p9jdGhNvWm0 z6)LAdOTCi)#MhP`!x-pmYBRWE*32C=K=QDY^i-TFv(*R$I?q&V0*~0AN zV)@WFyz{d|{i$_&+{J!A#MrXxY7=x001exJp$}}pQ zJeV6hH(+7ew=oW1FTHNt$V(0w+vAH)^2=zk>Tp#aR>x8aa(n4Fl8$${SSwV!3cAHPd)8w~-?yl|-%g~Kf z@$zT!%1ojB3%h%p^*hyRAKM7QFa|f5i&61n)RU(@Lh}GHznACT3v}lLX>&+1_8B@uyN6xui}ekZfHy=(ri+&oxziB!@Jto7l@cJQZ3pQ-evLXeAAk%bGC%{JX8~ z;q;oPSMpCjVX*Jk{1k&~UTTUI1wPy3ip zDqtc#&G-_qD}zhrO8*qbHJ9kUY6|y>J6*=2Zw5>R?7VB)AuxjywZo&}pLT&d) zC;Y11Ik(??Bb(dq7 z%&qi~@cFKIx+HeJcg%G-N*_|CXbs4;wdSXNq|qylEmp73`7)jVJUig&wt z0Zy4xY13kbgxB$wQ<^YUkcgwD-g0Zwz9P;&| zaA+G40v__Wt(c|j2dNhsCI?&>nyfuJu_CMIDN7Lih3N#2lY;9@R3I60Q}B^3AjIPX z-P&4;$e8a4P-T+I;o;2FcH!}}ipz%fheT8s5J}%URJPTsa1(UNf~f(eb0qikT$Z&7 z=9@N=w(kgjdXI{+X_f+J--Yx-feKY0fUCP7@2yE|+eY^^>~W^v{9Y=9u{qA?#&~qA zcC+Q>zzw=a>eCQZbQrO|Zt#grphr45` zIQ;h3*mFNrSZ$1uL*)VB$De=~meeUA^@rxb=Ifl3TcHGlYY}eL1 z(&|6>plh3$c`!M8R0v=i(@ne?SYkWu{M8AcyrcZDhp=J3&%jK&nZD`QDx)BZzy2da z+Mw8H6v_Qe9bZfGa7e-WGx-@M| z1FGQm%AIm@^7)7AENhXv8iVC@M*Mq|uzk5y!*|~V=EF5AfJg?)O9KBF(vV|8bW`n0 zJGnLle#Dg^&9}0XZ(HJA=Jsm-uq)cwV48po?<(0?T6ndRtItes_Vk-vKm`~QsCL52 zh{r1vwB~6^nQJ_AZA?2fzn<`toOki(3#xR;w##t*H`X-a`23Aca84%0FN<1q968ke zXGO>;Uv$X(9*)-VsXTj?!{>%YW|s?e#@`E5T*A5yV$sJm0E zMBv)}3Q6F*6Rg2OPWJY|a(!FcVBnX!(I>=?w_Or6>yp*oLqkJbf^KbZ*pJutQ;zSA zpt#_-eAN$B;$L5z9^Yg-z`w568m3$|F!^t%;G!yayel$M9mLmaC(>H>D<1N%Fmokl zoo`l0A3C!Q)E_<0J{aTq%4Kce7cgvv_j{j&9vaiZY-bSttXcSA0CQNyYNY(_H-pBf zUOsd*XnFZ`)a>HT7u~YT2kp|G%WP{9Pb)dHiE{mTgafosEY!YIa={(Sw>wYc%Z}NZ zqdvv}L9i~28-S}aq_qHxupWbJrIM(B9qF=-E&6zdyGJmQkYagczHpeg?SxFqB?i7NUS?)I6 z5C;N59qCn@E$bQcPA{h}K6PP+T)yB^D}qX|rc*<_`X^3>kpz?pa*Jjt-q?iayc(m{ z{hO;!$Uwt&V&9_jNhXWNX{W;1MFqP8oT>IH6c?5gj|#4qMZ$b{#|d%8^o;La>8^3D zBB#fY-rrBuAB*Y__wts2*s}rEIfA03x?w1f&9}b{%J^5;D*(4x>w2mS*>k^TG$Y^c zyD;qplI5FpW85qvdcTUXp#Jx2bE^{b@yI=q8m4A@X1M!}`A18=7$!sTfw?DrWXfv)-biN)}&Ym6V{|CTS%2(;`#Kwo=n9qFW8SIoN z1g{E9j0%Q+cV&Z2sLw%MRY=(tJC+CO$o;5g_Fq6ub~?zg5S{T5($HMX@Y}}9E#%cI z7ybqKkJ~32x0|&Nx!Eh@;SqWzB-eGn26M?WE`jHK^h4P5&!)F|4Fc|l8T7WLW`h)r zKl$Hi(4j4kepcaHcEpZ0qI`2g6ecS`hS0yRWHo2&v0T=r(0`<_aeN4CZ?w^THt8Q?U(us+Fd*;;DN4X@|@GM3H`eH zVjj`Si-%Hr%FmVg`xMeRUaDqrKOL@fEB-IPXfevsHLh*jqke8c!J9*!S$a0Yt;ady(cduz@NZB!(FXC zXprwWx+&F>am;%!8-q1KJGQCj7sZz7#Owc(^=EQpqw7fo<450+dt2@}c=kkO)! z$@P`m7b;V(4O)`6q?4^5<5AVQ=WHyAiq=|OiETK;|NYdo=F)BORB0gb*Cf&K1-iB8 zdmxv{I(eH?8Z{B)#Hy~N-^vPZ^aR=w8d=!E8EZxOcVT6{zA}g_StX` zZJ$Acz{*w=H&m?7I{cI<6`lA1Ld!>((CWb7n`$Vi*IrZkX~JudUNTn|e*?d!8Xlsx z3MJSM5;5DnBY7?L138xmmg9~bc!&r-sw5}6G443Ljog|Y7TNYI>uh?%LWxRTnO`tI z!<7yhupzkW^WfJ~)`wy}o_2Io=i=X#t2g5JHph*xtAcCbz)JjIxv62Bo@oa2;r%p- zN=Sx%xPv=qaVTk-<_2B1zCts;Y?kL@8@|fX`V{<#72Rj-mxtN2n+m|Uy>L;X(Q$3E z@LO%?#jFRFyMN61qHM&w7v6{VDZ3O^f=~-yiRSdWt1hyfUuH8t zjQefOR3&28YiDKxJ}OSQR&mr!?h0zY65Xwfc;wKbga0E49EwS}dHPOcJ|LmTpC8Qi zoVj?AG;ruf^U&>^ou|`ECqSOTpQO&Zo<@)LN{AlDG*kUz!u@GS2P8&x&Qj(+ZUBI^ z+Fa1XP{&nmvRZrW3redkuF$TN+If@B@lSbZ3E##S9mWlJCrftN+T~1tDPJjA4-mmS z^NYNg#mFApzML}Ok*8{nJ!6MT9zNu2e`n$@*LjDxIuuuXU`KNd-3UHK(A}`ou1jWm zj+x_)-k#~oArT~(Ax^XTwL3pVtv4oDXf`F~5^gw%GHQ09ou#?IZljj!PxXZ9_IuaBy?mT#*Zro*Bohh@ zkexzpEVlL=fzM{&yWMC7PBm6-XR}V4>rL)wtiBa$rZcqOFJZvbiyYeiSy4zhK)VFC za)7@ndxO?4Xs;Mwuo7+M%^L%*99y3}s)zU8{SS7-p5&EQ8d)_*^Tg;~3mhP}%gN+)+Y4UIf~zNlk}H%Ht{V z-6i0zVdS?7KL^3(7n)m&Y5pNv?qkWi#&St(QtUx*7#)*QJSNjAE7Lr#E*RZ%N3+%9 zfLwU!QC_iSJmn%W(oCg7Ub9l(<5=Mda${<3Ls$FePDhM-Hi;h_AX;a(JC{Qe^H;IO z%jzT+jPH6!kf_GbEO@BH>>xv0#A9hH0_D%TXvn@6qZyp*sY} zZ*v0UOw*lg7UWUKG^2W!ryg`cdh2nt8v%c_cIaae^>9_F0A*(i5Qw*h(@fs1|CeM! zWwx+jvLG;-Iy`k!y^u!B-E%~fUy02$J)-uDt*|6XYXGaQapNcTQZ*&C^Ra&Vs4b|W zNb!G3`Tvlm`k(Ld{xS1Uo^BI) literal 0 HcmV?d00001 diff --git a/help_img/wot.jpg b/help_img/wot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c69d78869f7b48c6429a1263062f9409420d473d GIT binary patch literal 77228 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 CG9gR= literal 0 HcmV?d00001 diff --git a/mailer.py b/mailer.py new file mode 100644 index 0000000..20375b9 --- /dev/null +++ b/mailer.py @@ -0,0 +1,61 @@ +"""Отправка email-уведомлений о заявках на корпоративную почту. + +Доступы берутся из .env (см. .env.example). Если SMTP не настроен, +функция тихо пропускает отправку и возвращает False, не ломая основной поток. +""" +import smtplib +import logging +from typing import Optional +from email.mime.text import MIMEText +from email.header import Header +from email.utils import formataddr + +import config + +logger = logging.getLogger("VK_Bot_IKP.mailer") + + +def is_configured() -> bool: + """Проверяет, заданы ли минимальные параметры SMTP для отправки писем.""" + return bool( + getattr(config, "SMTP_HOST", "") + and getattr(config, "SMTP_USER", "") + and getattr(config, "SMTP_PASSWORD", "") + and getattr(config, "MAIL_TO", "") + ) + + +def send_email(subject: str, body: str, mail_to: Optional[str] = None) -> bool: + """Отправляет письмо. По умолчанию на config.MAIL_TO; можно указать другой адрес (например, HR).""" + if not is_configured(): + logger.info("[MAIL] Пропущено: SMTP не настроен в .env (SMTP_HOST/SMTP_USER/SMTP_PASSWORD/MAIL_TO).") + return False + + host = config.SMTP_HOST + port = int(getattr(config, "SMTP_PORT", 465)) + user = config.SMTP_USER + password = config.SMTP_PASSWORD + mail_from = getattr(config, "MAIL_FROM", "") or user + mail_to = mail_to or config.MAIL_TO + use_ssl = bool(getattr(config, "SMTP_USE_SSL", True)) + + msg = MIMEText(body, "plain", "utf-8") + msg["Subject"] = Header(subject, "utf-8") + msg["From"] = formataddr((str(Header("Бот ИКП", "utf-8")), mail_from)) + msg["To"] = mail_to + + try: + if use_ssl: + with smtplib.SMTP_SSL(host, port, timeout=15) as server: + server.login(user, password) + server.sendmail(mail_from, [a.strip() for a in mail_to.split(",")], msg.as_string()) + else: + with smtplib.SMTP(host, port, timeout=15) as server: + server.starttls() + server.login(user, password) + server.sendmail(mail_from, [a.strip() for a in mail_to.split(",")], msg.as_string()) + logger.info(f"[MAIL] Письмо отправлено на {mail_to}: {subject}") + return True + except Exception as e: + logger.error(f"[MAIL] Ошибка отправки письма: {e}") + return False