Initial clean history
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import vk_api
|
||||
from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType
|
||||
from vk_api.keyboard import VkKeyboard, VkKeyboardColor
|
||||
from loguru import logger
|
||||
from config import settings, phrases
|
||||
from core.fsm import DialogManager
|
||||
from core.exporter import ExcelExporter
|
||||
from utils.backup import schedule_daily_backup
|
||||
from utils.middleware import MiddlewareChain
|
||||
from vk_api.exceptions import ApiError
|
||||
import requests.exceptions
|
||||
import re
|
||||
|
||||
class VKBot:
|
||||
def __init__(self, fsm: DialogManager, exporter: ExcelExporter):
|
||||
self.fsm = fsm
|
||||
self.exporter = exporter
|
||||
self.vk_session = vk_api.VkApi(token=settings.VK_TOKEN)
|
||||
self.longpoll = VkBotLongPoll(self.vk_session, settings.VK_GROUP_ID)
|
||||
self.vk = self.vk_session.get_api()
|
||||
self.middlewares = MiddlewareChain()
|
||||
|
||||
def _is_direct_message(self, user_id: int, peer_id: int) -> bool:
|
||||
return user_id == peer_id
|
||||
|
||||
def _send_message(self, peer_id: int, text: str, keyboard=None):
|
||||
"""Отправка сообщения с возможной клавиатурой"""
|
||||
try:
|
||||
self.vk.messages.send(
|
||||
peer_id=peer_id,
|
||||
message=text,
|
||||
random_id=0,
|
||||
keyboard=keyboard.get_keyboard() if keyboard else None
|
||||
)
|
||||
|
||||
except ApiError as e:
|
||||
if e.code == 901:
|
||||
logger.warning(f"Can't send to peer {peer_id}: {e}")
|
||||
# Здесь можно отправить уведомление администратору или сохранить ID
|
||||
else:
|
||||
logger.error(f"VK API error {e.code} for peer {peer_id}: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send message to {peer_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def _get_keyboard_for_state(self, state):
|
||||
"""Генерирует клавиатуру в зависимости от состояния"""
|
||||
keyboard = VkKeyboard(one_time=False)
|
||||
if state == "ASK_CONSENT":
|
||||
keyboard.add_button(phrases.BUTTON_CONSENT_YES, color=VkKeyboardColor.POSITIVE)
|
||||
keyboard.add_button(phrases.BUTTON_CONSENT_NO, color=VkKeyboardColor.NEGATIVE)
|
||||
elif state in ("CONFIRM", "confirm"):
|
||||
keyboard.add_button(phrases.BUTTON_YES, color=VkKeyboardColor.POSITIVE)
|
||||
keyboard.add_button(phrases.BUTTON_NO, color=VkKeyboardColor.NEGATIVE)
|
||||
elif state == "START" or state is None:
|
||||
# Начальное состояние — кнопка "Зарегистрироваться"
|
||||
keyboard.add_button(phrases.BUTTON_REGISTER, color=VkKeyboardColor.POSITIVE, payload='register_btn')
|
||||
else:
|
||||
keyboard.add_button(phrases.BUTTON_RESTART, color=VkKeyboardColor.SECONDARY)
|
||||
return keyboard
|
||||
|
||||
def _handle_command(self, user_id: int, text: str) -> bool:
|
||||
"""Обработка команд администрирования"""
|
||||
if user_id not in settings.ADMIN_IDS:
|
||||
return False
|
||||
if text == phrases.CMD_STATUS:
|
||||
self._send_message(user_id, "✅ Бот работает и принимает сообщения.")
|
||||
return True
|
||||
elif text == phrases.CMD_EXPORT:
|
||||
path = self.exporter.export()
|
||||
self._send_message(user_id, f"Экспорт создан: {path}")
|
||||
return True
|
||||
elif text == phrases.CMD_BACKUP:
|
||||
self.exporter.backup()
|
||||
self._send_message(user_id, "Резервная копия создана.")
|
||||
return True
|
||||
elif text == phrases.CMD_STATS:
|
||||
stats = self.exporter.get_stats()
|
||||
msg = (f"📊 Статистика:\nВсего лидов: {stats['total']}\n"
|
||||
f"За сегодня: {stats['today']}\nСтатусы: {stats['statuses']}")
|
||||
self._send_message(user_id, msg)
|
||||
return True
|
||||
elif text == phrases.CMD_RELOAD:
|
||||
import importlib
|
||||
import config.phrases
|
||||
importlib.reload(config.phrases)
|
||||
# Обновляем ссылку на phrases в текущем модуле
|
||||
globals()['phrases'] = config.phrases
|
||||
self._send_message(user_id, "Конфигурация перезагружена.")
|
||||
return True
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
logger.info("Bot started")
|
||||
schedule_daily_backup(self.exporter)
|
||||
|
||||
while True:
|
||||
try:
|
||||
for event in self.longpoll.listen():
|
||||
try:
|
||||
if event.type == VkBotEventType.MESSAGE_NEW and event.message:
|
||||
user_id = event.message.from_id
|
||||
peer_id = event.message.peer_id
|
||||
text = event.message.text
|
||||
|
||||
if not self._is_direct_message(user_id, peer_id):
|
||||
logger.debug(f"Ignoring chat message from {user_id} in peer {peer_id}")
|
||||
continue
|
||||
|
||||
# Очистка текста от VK-ссылок вида [club123456|текст] и [user123|текст]
|
||||
text = re.sub(r'\[club\d+\|([^]]*)\]', r'\1', text)
|
||||
text = re.sub(r'\[user\d+\|([^]]*)\]', r'\1', text)
|
||||
text = re.sub(r'\[id\d+\|([^]]*)\]', r'\1', text)
|
||||
|
||||
logger.debug(f"Message from {user_id} in peer {peer_id}: {text}")
|
||||
|
||||
if not self.middlewares.process(user_id, text):
|
||||
continue
|
||||
|
||||
if self._handle_command(user_id, text):
|
||||
continue
|
||||
|
||||
response = self.fsm.handle_message(user_id, text)
|
||||
logger.info(f"Response to user {user_id}: {response[:100]}")
|
||||
state = self.fsm.sessions.get(user_id, {}).get("state", "")
|
||||
keyboard = self._get_keyboard_for_state(state)
|
||||
self._send_message(peer_id, response, keyboard)
|
||||
|
||||
# Обработка нажатия callback-кнопки "Зарегистрироваться"
|
||||
elif event.type == VkBotEventType.MESSAGE_NEW_CALLBACK and event.message:
|
||||
user_id = event.message.from_id
|
||||
peer_id = event.message.peer_id
|
||||
callback_text = event.message.get_text() if event.message.get_text() else ""
|
||||
|
||||
if not self._is_direct_message(user_id, peer_id):
|
||||
logger.debug(f"Ignoring chat callback from {user_id} in peer {peer_id}")
|
||||
continue
|
||||
logger.debug(f"Callback from {user_id} in peer {peer_id}: {callback_text}")
|
||||
|
||||
if not self.middlewares.process(user_id, callback_text):
|
||||
continue
|
||||
|
||||
# Проверяем, что нажата кнопка "Зарегистрироваться"
|
||||
if event.message.get_payload() and 'register_btn' in str(event.message.get_payload()):
|
||||
# Начинаем диалог заново
|
||||
response = self.fsm._reset_dialog(user_id)
|
||||
# Если сессии нет, значит диалог ещё не начат
|
||||
if user_id not in self.fsm.sessions:
|
||||
response = self.fsm._start_dialog(user_id)
|
||||
state = self.fsm.sessions.get(user_id, {}).get("state", "START")
|
||||
keyboard = self._get_keyboard_for_state(state)
|
||||
self._send_message(peer_id, response, keyboard)
|
||||
|
||||
except ApiError as e:
|
||||
logger.error(f"VK API error while processing event: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing event: {e}", exc_info=True)
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
logger.warning(f"LongPoll connection lost: {e}. Reconnecting in 5 seconds...")
|
||||
import time
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error in longpoll loop: {e}", exc_info=True)
|
||||
logger.warning("Reconnecting in 10 seconds...")
|
||||
import time
|
||||
time.sleep(10)
|
||||
Reference in New Issue
Block a user