import json import logging from typing import Optional from app.max_api import MaxAPIClient from app.settings_cache import SettingsCache from app.fsm import BotState, FSM from app.conversation import ( get_or_create_user, add_message, get_template_rendered, find_in_knowledge_base ) from app.utils.emotion_detector import detect_emotion logger = logging.getLogger(__name__) async def handle_update(bot, update: dict, db, max_api: MaxAPIClient): update_type = update.get("update_type", "") user_data = update.get("user", {}) max_user_id = user_data.get("user_id") if not max_user_id: return await SettingsCache.refresh(db) if update_type == "bot_started": await _handle_bot_started(bot, user_data, db, max_api) return if update_type == "message_created": await _handle_message_created(bot, user_data, update, db, max_api) return if update_type == "message_callback": await _handle_callback(bot, user_data, update, db, max_api) return async def _handle_bot_started(bot, user_data, db, max_api): await SettingsCache.refresh(db) if not SettingsCache.is_work_hours(): from app.handlers.out_of_hours import handle_out_of_hours await handle_out_of_hours(bot, user_data, db, max_api) return from app.handlers.greeting import handle_start await handle_start(bot, user_data, db, max_api) async def _handle_message_created(bot, user_data, update, db, max_api): max_user_id = user_data["user_id"] message = update.get("message", {}) body = message.get("body", {}) text = body.get("text", "").strip() attachments = body.get("attachments", []) user_data["text"] = text user_data["ip"] = update.get("ip", "") has_attachment = len(attachments) > 0 attachment_type = "" attachment_path = "" if has_attachment: att = attachments[0] attachment_type = att.get("type", "") if attachment_type == "contact": payload = att.get("payload", {}) vcf_info = payload.get("vcf_info", "") import re phone_match = re.search(r'TEL[^:]*:(\+?\d+)', vcf_info) name_match = re.search(r'FN:(.+)', vcf_info) if phone_match: user_data["phone"] = phone_match.group(1) if name_match: user_data["first_name"] = name_match.group(1).strip() await _handle_contact_shared(bot, user_data, db, max_api) return attachment_path = att.get("url", "") user_data["has_attachment"] = has_attachment user_data["attachment_type"] = attachment_type user_data["attachment_path"] = attachment_path state = FSM.get_state(max_user_id) if state == BotState.IDLE: if text.lower() in ("/start", "/help"): if not SettingsCache.is_work_hours(): from app.handlers.out_of_hours import handle_out_of_hours await handle_out_of_hours(bot, user_data, db, max_api) return from app.handlers.greeting import handle_start await handle_start(bot, user_data, db, max_api) return if not SettingsCache.is_work_hours(): from app.handlers.out_of_hours import handle_out_of_hours await handle_out_of_hours(bot, user_data, db, max_api) return emotion = await detect_emotion(text) if emotion == "negative": tmpl = await get_template_rendered(db, "emotion_escalate") await max_api.send_message(max_user_id, tmpl) from app.handlers.handoff import handle_handoff_request await handle_handoff_request(bot, user_data, db, max_api) from app.max_notifications import notify_handoff_escalation user = await get_or_create_user(db, max_user_id) conv = None try: from app.conversation import get_open_conversation conv = await get_open_conversation(db, user.id) except Exception: pass await notify_handoff_escalation( db, max_api, user, conv.id if conv else 0, text ) return kb_entry = await find_in_knowledge_base(db, text) if kb_entry: await max_api.send_message(max_user_id, kb_entry.answer, format="markdown") return from app.conversation import get_or_create_user user = await get_or_create_user(db, max_user_id) if not user.consent_given: from app.handlers.greeting import handle_start await handle_start(bot, user_data, db, max_api) return from app.handlers.main_menu import handle_main_menu user_data["payload"] = "new_inquiry" await handle_main_menu(bot, user_data, db, max_api) return if state == BotState.NAME_REQUEST: if not text: await max_api.send_message(max_user_id, "Как к вам обращаться?") return user = await get_or_create_user(db, max_user_id) user.first_name = text await db.flush() from app.handlers.consent import handle_consent_request await handle_consent_request(bot, user_data, db, max_api) return if state == BotState.CONSENT_REQUEST: if text.lower() in ("да", "даю согласие", "согласен", "yes"): from app.handlers.consent import handle_consent_given await handle_consent_given(bot, user_data, db, max_api) elif text.lower() in ("нет", "не даю", "не согласен", "no"): from app.handlers.consent import handle_consent_refused await handle_consent_refused(bot, user_data, db, max_api) else: from app.handlers.main_menu import handle_main_menu user_data["payload"] = text.lower() await handle_main_menu(bot, user_data, db, max_api) return if state == BotState.CONTACT_REQUEST: from app.handlers.contact import handle_contact_text await handle_contact_text(bot, user_data, db, max_api) return if state == BotState.INQUIRY_REQUEST: from app.handlers.inquiry import handle_inquiry await handle_inquiry(bot, user_data, db, max_api) return if state == BotState.CATEGORY_SELECT: from app.handlers.inquiry import handle_inquiry await handle_inquiry(bot, user_data, db, max_api) return if state == BotState.OUT_OF_HOURS: from app.handlers.out_of_hours import handle_out_of_hours_callback await handle_out_of_hours_callback(bot, user_data, db, max_api, text.lower()) return _feature_handlers = { BotState.FEATURE_RISK_SCORE: ("app.handlers.features.risk_score", "handle_risk_score"), BotState.FEATURE_SLA_STATUS: ("app.handlers.features.sla_status", "handle_sla_status"), BotState.FEATURE_PHOTO_REPORT: ("app.handlers.features.photo_report", "handle_photo_report"), BotState.FEATURE_APPOINTMENT: ("app.handlers.features.appointment", "handle_appointment"), BotState.FEATURE_COMMERCIAL: ("app.handlers.features.commercial", "handle_commercial"), BotState.FEATURE_MY_OBJECTS: ("app.handlers.features.my_objects", "handle_my_objects"), BotState.FEATURE_REMINDERS: ("app.handlers.features.reminders", "handle_reminders"), BotState.FEATURE_QUALITY_RATE: ("app.handlers.features.quality_rate", "handle_quality_rate"), BotState.FEATURE_NOTIFICATIONS: ("app.handlers.features.notifications", "handle_notifications"), BotState.FEATURE_BROADCAST: ("app.handlers.features.broadcasts", "handle_broadcast_send"), BotState.SLA_TICKET_CREATE: ("app.handlers.features.sla_ticket", "handle_sla_ticket"), } if state in _feature_handlers: mod_path, func_name = _feature_handlers[state] import importlib mod = importlib.import_module(mod_path) func = getattr(mod, func_name) await func(bot, user_data, db, max_api) return if state == BotState.KB_SEARCH: kb_entry = await find_in_knowledge_base(db, text) if kb_entry: await max_api.send_message(max_user_id, kb_entry.answer, format="markdown") FSM.transition(max_user_id, "found") else: await max_api.send_message(max_user_id, "К сожалению, не нашёл ответа на ваш вопрос. " "Опишите подробнее или нажмите «Оставить заявку» для создания обращения.") FSM.transition(max_user_id, "not_found") from app.keyboards.inline import build_main_menu_buttons buttons = build_main_menu_buttons() menu_text = await get_template_rendered(db, "main_menu") await max_api.send_message_with_keyboard(max_user_id, menu_text, buttons) return from app.handlers.main_menu import handle_main_menu user_data["payload"] = text.lower() await handle_main_menu(bot, user_data, db, max_api) async def _handle_callback(bot, user_data, update, db, max_api): max_user_id = user_data["user_id"] callback = update.get("callback", {}) payload = callback.get("payload", "") callback_id = callback.get("id", "") user_data["payload"] = payload if payload == "cancel": FSM.reset(max_user_id) from app.keyboards.inline import build_main_menu_buttons buttons = build_main_menu_buttons() menu_text = await get_template_rendered(db, "main_menu") await max_api.send_message_with_keyboard(max_user_id, menu_text, buttons) await max_api.answer_callback(callback_id) return if payload.startswith("rate_"): from app.handlers.features.quality_rate import handle_quality_rate await handle_quality_rate(bot, user_data, db, max_api) await max_api.answer_callback(callback_id) return from app.handlers.main_menu import handle_main_menu await handle_main_menu(bot, user_data, db, max_api) await max_api.answer_callback(callback_id) async def _handle_contact_shared(bot, user_data, db, max_api): max_user_id = user_data["user_id"] user = await get_or_create_user(db, max_user_id) if user_data.get("phone"): user.phone = user_data["phone"] if user_data.get("first_name"): user.first_name = user_data["first_name"] await db.flush() state = FSM.get_state(max_user_id) if state == BotState.CONTACT_REQUEST: from app.handlers.contact import handle_contact_provided await handle_contact_provided(bot, user_data, db, max_api) else: from app.handlers.main_menu import handle_main_menu user_data["payload"] = "new_inquiry" await handle_main_menu(bot, user_data, db, max_api)