c8d769ee1a
Начальный проект чат бота (Для тестирования и исправления)
1037 lines
55 KiB
Python
1037 lines
55 KiB
Python
import json
|
||
import re
|
||
import logging
|
||
from typing import Dict, Any, Optional
|
||
|
||
# Нам понадобятся библиотеки vk_api.
|
||
try:
|
||
import vk_api
|
||
from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType
|
||
from vk_api.keyboard import VkKeyboard, VkKeyboardColor
|
||
from vk_api.utils import get_random_id
|
||
except ImportError:
|
||
vk_api = None
|
||
|
||
from database import DatabaseManager
|
||
import config
|
||
|
||
# Настройка логирования
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format="%(asctime)s - [%(levelname)s] - %(name)s - %(message)s"
|
||
)
|
||
logger = logging.getLogger("VK_Bot_IKP")
|
||
|
||
# Инициализация базы данных сессий
|
||
db = DatabaseManager()
|
||
|
||
# Регулярное выражение для базовой валидации номера телефона
|
||
PHONE_REGEX = re.compile(r"^\+?[78]\d{10}$")
|
||
|
||
|
||
# ==========================================
|
||
# КЛАВИАТУРЫ ДЛЯ ИНТЕРФЕЙСА (UX/UI ВКонтакте)
|
||
# ==========================================
|
||
|
||
def get_main_keyboard() -> str:
|
||
"""Главное меню бота."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
# Кнопка начать опрос (зеленая)
|
||
keyboard.add_button("Заполнить заявку 📝", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_line()
|
||
# Кнопки FAQ и оператора
|
||
keyboard.add_button("Частые вопросы ❓", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Позвать оператора 🧑💼", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_faq_keyboard() -> str:
|
||
"""Клавиатура раздела Частых Вопросов (FAQ)."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Цены 💰", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Услуги ⚙️", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Контакты 📞", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("О компании 🏢", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.PRIMARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_cancel_keyboard() -> str:
|
||
"""Клавиатура отмены заполнения формы."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_operator_keyboard() -> str:
|
||
"""Клавиатура при активном диалоге с оператором."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Завершить диалог 🚪", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_binary_keyboard(show_skip: bool = False) -> str:
|
||
"""Клавиатура с кнопками Согласен / Не согласен."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Согласен(а) и подтверждаю ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_button("Не согласен(а) ❌", color=VkKeyboardColor.NEGATIVE)
|
||
if show_skip:
|
||
keyboard.add_line()
|
||
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)
|
||
first = True
|
||
for key, svc in config.SERVICES.items():
|
||
if not first:
|
||
keyboard.add_line()
|
||
keyboard.add_button(svc["name"], color=VkKeyboardColor.SECONDARY)
|
||
first = False
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_confirm_service_keyboard() -> str:
|
||
"""Клавиатура для подтверждения выбранной услуги."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Да, подтверждаю ✅", color=VkKeyboardColor.PRIMARY)
|
||
keyboard.add_button("Нет, выбрать другую 🔄", color=VkKeyboardColor.NEGATIVE)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
def get_review_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_edit_menu_keyboard() -> str:
|
||
"""Клавиатура меню изменения полей."""
|
||
keyboard = VkKeyboard(one_time=False)
|
||
keyboard.add_button("Изменить Имя 👤", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Изменить Телефон 📞", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_line()
|
||
keyboard.add_button("Изменить Услугу 🎮", color=VkKeyboardColor.SECONDARY)
|
||
keyboard.add_button("Назад к проверке ↩️", color=VkKeyboardColor.PRIMARY)
|
||
return keyboard.get_keyboard()
|
||
|
||
|
||
|
||
# ==========================================
|
||
# КЛАСС БОТА И ЛОГИКА ОБРАБОТКИ
|
||
# ==========================================
|
||
|
||
class VKBotApplication:
|
||
def __init__(self):
|
||
self.vk_session = None
|
||
self.vk = None
|
||
self.longpoll = None
|
||
|
||
# Инициализируем динамические услуги из базы данных
|
||
config.SERVICES = db.get_services()
|
||
|
||
if vk_api and config.VK_TOKEN != "your_vk_community_token_here":
|
||
try:
|
||
self.vk_session = vk_api.VkApi(token=config.VK_TOKEN)
|
||
self.vk = self.vk_session.get_api()
|
||
self.longpoll = VkBotLongPoll(self.vk_session, config.GROUP_ID)
|
||
logger.info("Успешное подключение к VK API!")
|
||
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):
|
||
"""Отправка сообщения пользователю с сохранением в лог."""
|
||
db.log_message(peer_id, "bot", message)
|
||
|
||
if self.vk:
|
||
try:
|
||
params = {
|
||
"peer_id": peer_id,
|
||
"message": message,
|
||
"random_id": get_random_id()
|
||
}
|
||
if keyboard:
|
||
params["keyboard"] = keyboard
|
||
if attachment:
|
||
params["attachment"] = attachment
|
||
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})")
|
||
|
||
def handle_message(self, user_id: int, text: str):
|
||
"""Маршрутизатор входящих сообщений на основе состояний пользователя."""
|
||
text_clean = text.strip()
|
||
text_lower = text_clean.lower()
|
||
|
||
# Логируем входящее сообщение от пользователя
|
||
db.log_message(user_id, "user", text_clean)
|
||
|
||
# Получаем текущее состояние и заполненные данные
|
||
user_data = db.get_user_data(user_id)
|
||
state = user_data["state"]
|
||
|
||
# Глобальная обработка админ-команд (доступна только пользователям из ADMIN_IDS)
|
||
if hasattr(config, "ADMIN_IDS") and user_id in config.ADMIN_IDS:
|
||
if text_lower in ["админка", "/admin", "услуги"]:
|
||
self._send_admin_menu(user_id)
|
||
return
|
||
elif text_lower.startswith("ред "):
|
||
self._handle_admin_edit(user_id, text_clean)
|
||
return
|
||
elif text_lower.startswith("доб "):
|
||
self._handle_admin_add(user_id, text_clean)
|
||
return
|
||
elif text_lower.startswith("удал "):
|
||
self._handle_admin_delete(user_id, text_clean)
|
||
return
|
||
|
||
logger.info(f"User {user_id} in state {state} sent: {text_clean}")
|
||
|
||
# Глобальная обработка отмены формы в любых состояниях заполнения опроса
|
||
if state.startswith("FORM_") and text_lower in ["отменить заполнение ❌", "отмена", "отменить"]:
|
||
db.reset_form(user_id)
|
||
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_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_CONFIRM_QUESTION":
|
||
self._handle_form_confirm_question(user_id, text_clean, user_data)
|
||
elif state == "FORM_REVIEW":
|
||
self._handle_form_review(user_id, text_clean, user_data)
|
||
elif state == "FORM_EDIT_MENU":
|
||
self._handle_form_edit_menu(user_id, text_clean, user_data)
|
||
elif state == "FORM_EDIT_NAME":
|
||
self._handle_form_edit_name(user_id, text_clean)
|
||
elif state == "FORM_EDIT_PHONE":
|
||
self._handle_form_edit_phone(user_id, text_clean)
|
||
elif state == "FORM_EDIT_QUESTION":
|
||
self._handle_form_edit_question(user_id, text_clean)
|
||
elif state == "FORM_CONSENT_1":
|
||
self._handle_form_consent_1(user_id, text_clean, text_lower)
|
||
elif state == "FORM_CONSENT_2":
|
||
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 == "OPERATOR_CHAT":
|
||
self._handle_operator_chat(user_id, text_clean, text_lower)
|
||
else:
|
||
# Предотвращение неопределенного состояния
|
||
db.update_user_state(user_id, "FREE_CHAT")
|
||
self.send_message(
|
||
user_id,
|
||
"Произошла непредвиденная ошибка в системе. Возвращаю вас в главное меню.",
|
||
keyboard=get_main_keyboard()
|
||
)
|
||
|
||
# ------------------------------------------
|
||
# ОБРАБОТЧИКИ СОСТОЯНИЙ (FSM)
|
||
# ------------------------------------------
|
||
|
||
def _handle_free_chat(self, user_id: int, text: str, text_lower: str):
|
||
"""Логика Свободного Чата (Главное меню, FAQ, переключение на форму)."""
|
||
|
||
# 1. Запуск формы опроса
|
||
if text_lower in ["заполнить заявку 📝", "заявка", "начать опрос", "опрос"]:
|
||
db.update_user_state(user_id, "FORM_NAME")
|
||
self.send_message(
|
||
user_id,
|
||
"📝 Начинаем заполнение заявки!\n\n"
|
||
"Шаг 1 из 3: Как к вам обращаться? Пожалуйста, введите ваше имя.",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
return
|
||
|
||
# 2. Переход к списку частых вопросов (FAQ)
|
||
if text_lower in ["частые вопросы ❓", "faq", "вопросы", "помощь"]:
|
||
self.send_message(
|
||
user_id,
|
||
"❓ Выберите тему, которая вас интересует, на клавиатуре ниже:",
|
||
keyboard=get_faq_keyboard()
|
||
)
|
||
return
|
||
|
||
# 3. Возврат в главное меню
|
||
if text_lower in ["назад в меню ↩️", "меню", "назад"]:
|
||
self.send_message(
|
||
user_id,
|
||
"🏠 Вы вернулись в главное меню. Выберите интересующее действие:",
|
||
keyboard=get_main_keyboard()
|
||
)
|
||
return
|
||
|
||
# 4. Вызов оператора
|
||
if text_lower in ["позвать оператора 🧑💼", "позвать оператора", "оператор", "менеджер", "человек", "позови оператора", "связь с оператором"]:
|
||
self._handoff_to_operator(user_id)
|
||
return
|
||
|
||
# 5. Обработка конкретных ответов FAQ
|
||
for key, answer in config.FAQ_ANSWERS.items():
|
||
if key in text_lower:
|
||
self.send_message(user_id, answer, keyboard=get_faq_keyboard())
|
||
return
|
||
|
||
# 6. Приветственные сообщения
|
||
if text_lower in ["начать", "привет", "здравствуйте", "ку", "hi", "hello"]:
|
||
self.send_message(
|
||
user_id,
|
||
"👋 Здравствуйте! Я умный чат-бот школы киберспорта 'Cyber Pro'.\n\n"
|
||
"Я могу помочь вам быстро оформить заявку на наши услуги или ответить на частые вопросы. "
|
||
"Выберите действие на кнопках ниже или просто напишите ваш вопрос!",
|
||
keyboard=get_main_keyboard()
|
||
)
|
||
return
|
||
|
||
# 7. Fallback (Бот не понял, но пытается помочь, предлагая клавиатуру)
|
||
self.send_message(
|
||
user_id,
|
||
"🤔 Я пока только учусь и не смог полностью распознать ваш запрос.\n\n"
|
||
"Пожалуйста, воспользуйтесь кнопками меню ниже или напишите 'оператор', чтобы я пригласил специалиста.",
|
||
keyboard=get_main_keyboard()
|
||
)
|
||
|
||
def _handle_form_name(self, user_id: int, text: str):
|
||
"""Сохранение имени и переход к номеру телефона."""
|
||
# Валидация имени: длина от 2 до 50 символов
|
||
if len(text) < 2 or len(text) > 50:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Имя выглядит слишком коротким или длинным. Пожалуйста, введите корректное имя.",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
return
|
||
|
||
# Сохраняем имя в БД
|
||
db.update_user_field(user_id, "name", text)
|
||
db.update_user_state(user_id, "FORM_PHONE")
|
||
|
||
self.send_message(
|
||
user_id,
|
||
f"👍 Приятно познакомиться, {text}!\n\n"
|
||
"Шаг 2 из 3: Введите ваш контактный номер телефона (например, +79991234567) для связи с менеджером.",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
|
||
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
|
||
|
||
# Валидация регулярным выражением
|
||
if not PHONE_REGEX.match(phone_cleaned):
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Некорректный формат телефона. Пожалуйста, отправьте номер в формате +79991234567.",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
return
|
||
|
||
# Сохраняем телефон в БД
|
||
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()
|
||
)
|
||
|
||
def _handle_form_question(self, user_id: int, text: str, user_data: Dict[str, Any]):
|
||
"""Обработка выбора услуги с прямым переходом к проверке данных."""
|
||
selected_service = None
|
||
text_lower = text.lower().strip()
|
||
|
||
# 1. Прямое сопоставление синонимов дисциплин для надежности
|
||
synonyms = {
|
||
"1": ["дота", "dota", "дотку", "дотка", "dota2"],
|
||
"2": ["кс", "cs", "ксго", "кс2", "csgo", "cs2", "контра", "кондра", "counter"]
|
||
}
|
||
for key_opt, words in synonyms.items():
|
||
if any(w in text_lower for w in words):
|
||
selected_service = config.SERVICES.get(key_opt)
|
||
break
|
||
|
||
# 2. Поиск по названию или ключу (если синонимы не сработали)
|
||
if not selected_service:
|
||
for key, svc in config.SERVICES.items():
|
||
name_clean = svc["name"].lower().strip()
|
||
# Убираем эмодзи и спецсимволы для гибкого сравнения
|
||
name_no_emoji = re.sub(r"[^\w\s\d]", "", name_clean).strip()
|
||
text_no_emoji = re.sub(r"[^\w\s\d]", "", text_lower).strip()
|
||
|
||
if (text_lower == key or
|
||
text_lower == f"{key}." or
|
||
text_lower == f"{key}️⃣" or
|
||
name_clean in text_lower or
|
||
text_lower in name_clean or
|
||
(name_no_emoji and text_no_emoji and (text_no_emoji in name_no_emoji or name_no_emoji in text_no_emoji))):
|
||
selected_service = svc
|
||
break
|
||
|
||
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)
|
||
else:
|
||
# Формируем список услуг с ценами для повторного показа
|
||
services_text = (
|
||
"⚠️ К сожалению, я не смог распознать выбранную услугу.\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"
|
||
|
||
self.send_message(
|
||
user_id,
|
||
services_text,
|
||
keyboard=get_services_keyboard()
|
||
)
|
||
|
||
def _handle_form_confirm_question(self, user_id: int, text: str, user_data: Dict[str, Any]):
|
||
"""Резервный обработчик для совместимости с устаревшим шагом подтверждения."""
|
||
db.update_user_state(user_id, "FORM_REVIEW")
|
||
self._send_review_message(user_id, user_data)
|
||
|
||
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", "Не указано")
|
||
|
||
review_text = (
|
||
"🔍 **Пожалуйста, проверьте правильность заполненных данных:**\n\n"
|
||
f"👤 **Имя:** {name}\n"
|
||
f"📞 **Телефон:** {phone}\n"
|
||
f"🎮 **Выбранная услуга:** {question}\n\n"
|
||
"Если всё верно, нажмите «Да, всё верно! ✅» для продолжения. Если хотите исправить — нажмите «Изменить данные 🔄»."
|
||
)
|
||
self.send_message(user_id, review_text, keyboard=get_review_keyboard())
|
||
|
||
def _handle_form_review(self, user_id: int, text: str, user_data: Dict[str, Any]):
|
||
"""Обработка решений на экране финальной проверки данных."""
|
||
text_lower = text.lower().strip()
|
||
|
||
if text_lower in ["да, всё верно! ✅", "да, всё верно", "все верно", "да", "верно"]:
|
||
# Переходим к шагу оферты и согласий
|
||
db.update_user_state(user_id, "FORM_CONSENT_1")
|
||
|
||
# Загружаем документ оферты для ознакомления
|
||
doc_attachment = self.get_offer_document_attachment(user_id)
|
||
|
||
# Отправляем значительно сокращенное и дружелюбное сообщение-согласие
|
||
self.send_message(
|
||
user_id,
|
||
"📄 **Согласие с условиями и офертой**\n\n"
|
||
"Для завершения отправки заявки подтвердите, пожалуйста, ваше согласие с условиями Публичной оферты, "
|
||
"Политикой обработки персональных данных и записью разговора.\n\n"
|
||
"«Я подтверждаю, что ознакомлен(а) с договором оферты (прикреплен ниже), принимаю его условия, "
|
||
"соглашаюсь на обработку персональных данных и запись консультации.»",
|
||
keyboard=get_binary_keyboard(show_skip=False),
|
||
attachment=doc_attachment
|
||
)
|
||
|
||
elif text_lower in ["изменить данные 🔄", "изменить данные", "изменить", "редактировать", "поменять"]:
|
||
# Переходим в меню изменения полей
|
||
db.update_user_state(user_id, "FORM_EDIT_MENU")
|
||
self.send_message(
|
||
user_id,
|
||
"🔄 **Меню изменения данных**\n\n"
|
||
"Пожалуйста, выберите на клавиатуре ниже, какое поле вы хотите изменить:",
|
||
keyboard=get_edit_menu_keyboard()
|
||
)
|
||
|
||
else:
|
||
self.send_message(
|
||
user_id,
|
||
"❓ Пожалуйста, подтвердите ваши данные кнопкой «Да, всё верно! ✅» или нажмите «Изменить данные 🔄» для редактирования.",
|
||
keyboard=get_review_keyboard()
|
||
)
|
||
|
||
def _handle_form_edit_menu(self, user_id: int, text: str, user_data: Dict[str, Any]):
|
||
"""Обработка меню выбора поля для редактирования."""
|
||
text_lower = text.lower().strip()
|
||
|
||
if text_lower in ["изменить имя 👤", "изменить имя", "имя", "1"]:
|
||
db.update_user_state(user_id, "FORM_EDIT_NAME")
|
||
self.send_message(
|
||
user_id,
|
||
"👤 Введите ваше новое имя:",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
elif text_lower in ["изменить телефон 📞", "изменить телефон", "телефон", "2"]:
|
||
db.update_user_state(user_id, "FORM_EDIT_PHONE")
|
||
self.send_message(
|
||
user_id,
|
||
"📞 Введите ваш новый номер телефона в формате +79991234567:",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
elif text_lower in ["изменить услугу 🎮", "изменить услугу", "услугу", "услуга", "3"]:
|
||
db.update_user_state(user_id, "FORM_EDIT_QUESTION")
|
||
|
||
services_text = (
|
||
"🎮 **Выберите новую услугу из списка:**\n\n"
|
||
)
|
||
for key, svc in config.SERVICES.items():
|
||
services_text += f"{key}️⃣ **{svc['name']}**\n"
|
||
services_text += f" 💰 Стоимость: {svc['price']}\n\n"
|
||
|
||
self.send_message(
|
||
user_id,
|
||
services_text,
|
||
keyboard=get_services_keyboard()
|
||
)
|
||
elif text_lower in ["назад к проверке ↩️", "назад к проверке", "назад", "вернуть", "4"]:
|
||
db.update_user_state(user_id, "FORM_REVIEW")
|
||
self._send_review_message(user_id, user_data)
|
||
else:
|
||
self.send_message(
|
||
user_id,
|
||
"❓ Выберите нужный пункт меню на клавиатуре или нажмите «Назад к проверке ↩️» для возврата к карточке данных.",
|
||
keyboard=get_edit_menu_keyboard()
|
||
)
|
||
|
||
def _handle_form_edit_name(self, user_id: int, text: str):
|
||
"""Сохранение нового имени и возврат к проверке."""
|
||
if len(text) < 2 or len(text) > 50:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Имя выглядит слишком коротким или длинным. Пожалуйста, введите корректное имя.",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
return
|
||
|
||
db.update_user_field(user_id, "name", text)
|
||
db.update_user_state(user_id, "FORM_REVIEW")
|
||
|
||
updated_data = db.get_user_data(user_id)
|
||
self.send_message(user_id, "✅ Имя успешно обновлено!")
|
||
self._send_review_message(user_id, updated_data)
|
||
|
||
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
|
||
|
||
if not PHONE_REGEX.match(phone_cleaned):
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Некорректный формат телефона. Пожалуйста, отправьте номер в формате +79991234567.",
|
||
keyboard=get_cancel_keyboard()
|
||
)
|
||
return
|
||
|
||
db.update_user_field(user_id, "phone", phone_cleaned)
|
||
db.update_user_state(user_id, "FORM_REVIEW")
|
||
|
||
updated_data = db.get_user_data(user_id)
|
||
self.send_message(user_id, "✅ Номер телефона успешно обновлен!")
|
||
self._send_review_message(user_id, updated_data)
|
||
|
||
def _handle_form_edit_question(self, user_id: int, text: str):
|
||
"""Сохранение новой услуги и возврат к проверке."""
|
||
selected_service = None
|
||
text_lower = text.lower().strip()
|
||
|
||
# 1. Прямое сопоставление синонимов дисциплин для надежности
|
||
synonyms = {
|
||
"1": ["дота", "dota", "дотку", "дотка", "dota2"],
|
||
"2": ["кс", "cs", "ксго", "кс2", "csgo", "cs2", "контра", "кондра", "counter"]
|
||
}
|
||
for key_opt, words in synonyms.items():
|
||
if any(w in text_lower for w in words):
|
||
selected_service = config.SERVICES.get(key_opt)
|
||
break
|
||
|
||
# 2. Поиск по названию или ключу (если синонимы не сработали)
|
||
if not selected_service:
|
||
for key, svc in config.SERVICES.items():
|
||
name_clean = svc["name"].lower().strip()
|
||
name_no_emoji = re.sub(r"[^\w\s\d]", "", name_clean).strip()
|
||
text_no_emoji = re.sub(r"[^\w\s\d]", "", text_lower).strip()
|
||
|
||
if (text_lower == key or
|
||
text_lower == f"{key}." or
|
||
text_lower == f"{key}️⃣" or
|
||
name_clean in text_lower or
|
||
text_lower in name_clean or
|
||
(name_no_emoji and text_no_emoji and (text_no_emoji in name_no_emoji or name_no_emoji in text_no_emoji))):
|
||
selected_service = svc
|
||
break
|
||
|
||
if selected_service:
|
||
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_message(user_id, "✅ Услуга успешно обновлена!")
|
||
self._send_review_message(user_id, updated_data)
|
||
else:
|
||
services_text = (
|
||
"⚠️ К сожалению, я не смог распознать выбранную услугу.\n\n"
|
||
"Пожалуйста, выберите одну из доступных услуг на клавиатуре:\n\n"
|
||
)
|
||
for key, svc in config.SERVICES.items():
|
||
services_text += f"{key}️⃣ **{svc['name']}**\n"
|
||
services_text += f" 💰 Стоимость: {svc['price']}\n\n"
|
||
|
||
self.send_message(
|
||
user_id,
|
||
services_text,
|
||
keyboard=get_services_keyboard()
|
||
)
|
||
|
||
|
||
def get_offer_document_attachment(self, peer_id: int) -> Optional[str]:
|
||
"""Загружает документ оферты на сервер VK и возвращает attachment-строку."""
|
||
if hasattr(self, "_cached_doc_attachment") and self._cached_doc_attachment:
|
||
return self._cached_doc_attachment
|
||
|
||
import os
|
||
doc_path = os.path.join(os.path.dirname(__file__), "Dogovor_oferty_kibersport_versia_izmenennaya.docx")
|
||
if not os.path.exists(doc_path):
|
||
logger.error(f"Файл оферты не найден по пути: {doc_path}")
|
||
return None
|
||
|
||
if not self.vk:
|
||
# Режим эмуляции (консольный)
|
||
logger.info(f"[EMULATOR] Файл {os.path.basename(doc_path)} прикреплен к сообщению.")
|
||
return None
|
||
|
||
try:
|
||
import requests
|
||
logger.info("Загрузка документа оферты в VK API...")
|
||
upload_server = self.vk.docs.getMessagesUploadServer(peer_id=peer_id, type="doc")
|
||
upload_url = upload_server["upload_url"]
|
||
|
||
title = "Dogovor_oferty_kibersport.docx"
|
||
with open(doc_path, "rb") as f:
|
||
response = requests.post(upload_url, files={"file": (title, f)})
|
||
|
||
response_json = response.json()
|
||
if "file" not in response_json:
|
||
logger.error(f"Ошибка загрузки файла в VK: {response_json}")
|
||
return None
|
||
|
||
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
|
||
|
||
attachment = f"doc{doc_info['owner_id']}_{doc_info['id']}"
|
||
self._cached_doc_attachment = attachment
|
||
logger.info(f"Документ загружен успешно: {attachment}")
|
||
return attachment
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при загрузке документа в VK API: {e}")
|
||
return None
|
||
|
||
def _handle_form_consent_1(self, user_id: int, text: str, text_lower: str):
|
||
"""Обработка объединенного шага согласий."""
|
||
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"
|
||
|
||
# Отправляем подтверждение пользователю
|
||
self.send_message(
|
||
user_id,
|
||
"🎉 Заявка и все необходимые согласия успешно оформлены!\n\n"
|
||
f"Наш менеджер уже изучает детали и свяжется с вами {callback_time} по номеру "
|
||
f"{phone}. Спасибо за обращение!\n\n"
|
||
"Вы вернулись в главное меню бота.",
|
||
keyboard=get_main_keyboard()
|
||
)
|
||
|
||
# Уведомляем оператора о новой заявке со всеми согласиями
|
||
self._notify_operator_about_lead(user_id, updated_data)
|
||
elif is_disagree:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ **Для продолжения оформления заявки необходимо принять условия Публичной оферты и дать согласие на обработку данных и запись разговора.**\n\n"
|
||
"Пожалуйста, подтвердите согласие кнопкой «Согласен(а) и подтверждаю ✅» или нажмите «Отменить заполнение ❌» для выхода.",
|
||
keyboard=get_binary_keyboard(show_skip=False)
|
||
)
|
||
else:
|
||
self.send_message(
|
||
user_id,
|
||
"❓ Пожалуйста, подтвердите согласие кнопкой «Согласен(а) и подтверждаю ✅» или выберите «Не согласен(а) ❌» / «Отменить заполнение ❌».",
|
||
keyboard=get_binary_keyboard(show_skip=False)
|
||
)
|
||
|
||
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)
|
||
|
||
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", "Согласен")
|
||
|
||
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)
|
||
|
||
def _handle_operator_chat(self, user_id: int, text: str, text_lower: str):
|
||
"""Поведение бота в режиме активного диалога с живым человеком."""
|
||
# Пользователь хочет выйти из диалога с оператором
|
||
if text_lower in ["завершить диалог 🚪", "завершить", "/exit", "выход"]:
|
||
db.update_user_state(user_id, "FREE_CHAT")
|
||
self.send_message(
|
||
user_id,
|
||
"🚪 Диалог с оператором завершен. Вы вернулись к автоматическому ассистенту.",
|
||
keyboard=get_main_keyboard()
|
||
)
|
||
# Уведомляем оператора о закрытии диалога
|
||
self.send_message(
|
||
config.OPERATOR_PEER_ID,
|
||
f"ℹ️ Пользователь vk.com/id{user_id} завершил сессию общения с оператором."
|
||
)
|
||
return
|
||
|
||
# В остальное время бот МОЛЧИТ в диалог с пользователем.
|
||
logger.info(f"[OPERATOR MODE] Message from {user_id} bypasses bot: {text}")
|
||
self._forward_to_operator(user_id, text)
|
||
|
||
# ------------------------------------------
|
||
# СЦЕНАРИИ ПЕРЕДАЧИ ОПЕРАТОРУ И СВЯЗИ
|
||
# ------------------------------------------
|
||
|
||
def _handoff_to_operator(self, user_id: int):
|
||
"""Переводит пользователя в режим оператора и уведомляет команду поддержки."""
|
||
db.update_user_state(user_id, "OPERATOR_CHAT")
|
||
|
||
self.send_message(
|
||
user_id,
|
||
"🧑💼 Переключаю вас на оператора...\n\n"
|
||
"Я отправил уведомление специалисту поддержки. Он ответит вам прямо в этом диалоге в ближайшее время.\n"
|
||
"Все ваши сообщения сейчас направляются напрямую человеку. Чтобы вернуться к боту, нажмите кнопку ниже.",
|
||
keyboard=get_operator_keyboard()
|
||
)
|
||
|
||
# Получаем историю последних 10 сообщений для контекста оператора
|
||
history = db.get_dialogue_history(user_id, limit=10)
|
||
formatted_history = ""
|
||
for sender, msg, ts in history:
|
||
role = "Вы" if sender == "user" else "Бот"
|
||
formatted_history += f"[{ts}] {role}: {msg}\n"
|
||
|
||
# Отправляем алерт оператору с историей диалога
|
||
operator_msg = (
|
||
f"🚨 **Запрос оператора!**\n"
|
||
f"Пользователь: vk.com/id{user_id}\n"
|
||
f"Тип запроса: Прямой вызов из меню\n\n"
|
||
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]):
|
||
"""Отправляет структурированный лид оператору после заполнения формы с указанием всех согласий."""
|
||
name = user_data["name"]
|
||
phone = user_data["phone"]
|
||
question = user_data["question"]
|
||
consent_1 = user_data.get("consent_1", "Согласен")
|
||
consent_2 = user_data.get("consent_2", "Согласен")
|
||
consent_3 = user_data.get("consent_3", "Согласен")
|
||
|
||
operator_msg = (
|
||
f"⚡ **Получена новая заявка!**\n\n"
|
||
f"👤 **Имя:** {name}\n"
|
||
f"📞 **Телефон:** {phone}\n"
|
||
f"🎯 **Выбранная услуга:** {question}\n\n"
|
||
f"⚖️ **Согласия пользователя:**\n"
|
||
f"1. Оферта и обработка ПД (Обяз.): {consent_1}\n"
|
||
f"2. Маркетинговые рассылки (Необяз.): {consent_2}\n"
|
||
f"3. Запись консультации (Обяз.): {consent_3}\n\n"
|
||
f"🔗 Профиль клиента: vk.com/id{user_id}\n"
|
||
f"Переписка сохранена в базе данных."
|
||
)
|
||
self.send_message(config.OPERATOR_PEER_ID, operator_msg)
|
||
|
||
def _forward_to_operator(self, user_id: int, text: str):
|
||
"""Пересылка сообщений от клиента в чат оператора."""
|
||
forward_msg = (
|
||
f"💬 **Сообщение от vk.com/id{user_id}:**\n{text}"
|
||
)
|
||
self.send_message(config.OPERATOR_PEER_ID, forward_msg)
|
||
|
||
# ------------------------------------------
|
||
# ЗАПУСК И ОБРАБОТКА LONGPOLL
|
||
# ------------------------------------------
|
||
|
||
def _send_admin_menu(self, user_id: int):
|
||
"""Отправляет администратору панель управления услугами с текущим списком."""
|
||
services = db.get_services()
|
||
|
||
services_list_text = ""
|
||
for key, svc in services.items():
|
||
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"
|
||
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`"
|
||
)
|
||
self.send_message(user_id, admin_text)
|
||
|
||
def _handle_admin_edit(self, user_id: int, text: str):
|
||
"""Обрабатывает редактирование услуги администратором."""
|
||
parts = text.split(" ", 3)
|
||
if len(parts) < 4:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Некорректный формат команды редактирования.\n"
|
||
"Правильный формат: `ред [номер] [поле] [новое значение]`\n"
|
||
"Пример: `ред 1 цена 1 800 ₽/час`"
|
||
)
|
||
return
|
||
|
||
_, service_id, field_name, new_value = parts
|
||
field_name = field_name.lower().strip()
|
||
new_value = new_value.strip()
|
||
|
||
field_mapping = {
|
||
"имя": "name",
|
||
"название": "name",
|
||
"цена": "price",
|
||
"стоимость": "price",
|
||
"описание": "description"
|
||
}
|
||
|
||
if field_name not in field_mapping:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Неизвестное поле для редактирования. Допустимые поля: `имя`, `цена`, `описание`."
|
||
)
|
||
return
|
||
|
||
db_field = field_mapping[field_name]
|
||
services = db.get_services()
|
||
|
||
if service_id not in services:
|
||
self.send_message(
|
||
user_id,
|
||
f"⚠️ Услуга с номером '{service_id}' не найдена в списке активных услуг."
|
||
)
|
||
return
|
||
|
||
try:
|
||
db.update_service(service_id, db_field, new_value)
|
||
config.SERVICES = db.get_services()
|
||
|
||
self.send_message(
|
||
user_id,
|
||
f"✅ Услуга [{service_id}] успешно обновлена!\n"
|
||
f"Поле `{field_name}` изменено на: `{new_value}`."
|
||
)
|
||
self._send_admin_menu(user_id)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при редактировании услуги в БД: {e}")
|
||
self.send_message(user_id, f"⚠️ Произошла ошибка при обновлении базы данных: {e}")
|
||
|
||
def _handle_admin_add(self, user_id: int, text: str):
|
||
"""Обрабатывает добавление новой услуги администратором."""
|
||
content = text[4:].strip()
|
||
parts = [p.strip() for p in content.split("|")]
|
||
|
||
if len(parts) < 3:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Некорректный формат команды добавления.\n"
|
||
"Правильный формат: `доб [Имя] | [Цена] | [Описание]`\n"
|
||
"Пример: `доб Тренировка по Valorant 🎯 | 1 500 ₽/час | Разбор стрельбы и aim.`"
|
||
)
|
||
return
|
||
|
||
name, price, description = parts
|
||
|
||
if not name or not price or not description:
|
||
self.send_message(user_id, "⚠️ Все три поля (имя, цена, описание) должны быть заполнены!")
|
||
return
|
||
|
||
try:
|
||
new_id = db.add_service(name, price, description)
|
||
config.SERVICES = db.get_services()
|
||
|
||
self.send_message(
|
||
user_id,
|
||
f"✅ Новая услуга успешно добавлена с номером **[{new_id}]**!"
|
||
)
|
||
self._send_admin_menu(user_id)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при добавлении услуги в БД: {e}")
|
||
self.send_message(user_id, f"⚠️ Произошла ошибка при обновлении базы данных: {e}")
|
||
|
||
def _handle_admin_delete(self, user_id: int, text: str):
|
||
"""Обрабатывает мягкое удаление услуги администратором."""
|
||
parts = text.split(" ")
|
||
if len(parts) < 2:
|
||
self.send_message(
|
||
user_id,
|
||
"⚠️ Укажите номер услуги для удаления.\n"
|
||
"Пример: `удал 4`"
|
||
)
|
||
return
|
||
|
||
service_id = parts[1].strip()
|
||
services = db.get_services()
|
||
|
||
if service_id not in services:
|
||
self.send_message(
|
||
user_id,
|
||
f"⚠️ Услуга с номером '{service_id}' не найдена в списке активных услуг."
|
||
)
|
||
return
|
||
|
||
try:
|
||
db.delete_service(service_id)
|
||
config.SERVICES = db.get_services()
|
||
|
||
self.send_message(
|
||
user_id,
|
||
f"✅ Услуга [{service_id}] успешно удалена из активного списка!"
|
||
)
|
||
self._send_admin_menu(user_id)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при деактивации услуги в БД: {e}")
|
||
self.send_message(user_id, f"⚠️ Произошла ошибка при обновлении базы данных: {e}")
|
||
|
||
def run(self):
|
||
"""Запуск бесконечного цикла прослушивания событий VK LongPoll."""
|
||
if not self.longpoll:
|
||
logger.warning("Бот запущен в режиме ЭМУЛЯЦИИ (без валидных токенов VK API).")
|
||
print("Симуляция работы бота в консоли. Напишите 'выход' для завершения.")
|
||
|
||
# Эмулятор консольного ввода для тестирования на Windows локально
|
||
test_user_id = 99999
|
||
# Эмулируем приветствие при старте
|
||
self.handle_message(test_user_id, "начать")
|
||
|
||
while True:
|
||
try:
|
||
user_input = input("Вы: ")
|
||
if user_input.lower() == "выход":
|
||
break
|
||
self.handle_message(test_user_id, user_input)
|
||
except (KeyboardInterrupt, EOFError):
|
||
break
|
||
return
|
||
|
||
logger.info("Бот успешно запущен в режиме прослушивания LongPoll...")
|
||
for event in self.longpoll.listen():
|
||
if event.type == VkBotEventType.MESSAGE_NEW:
|
||
message_obj = event.obj.message
|
||
user_id = message_obj["from_id"]
|
||
text = message_obj["text"]
|
||
self.handle_message(user_id, text)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
bot = VKBotApplication()
|
||
bot.run()
|