diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64ea464 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/VOICE_MANAGER_README.md b/VOICE_MANAGER_README.md new file mode 100644 index 0000000..2e984dc --- /dev/null +++ b/VOICE_MANAGER_README.md @@ -0,0 +1,108 @@ +# Голосовий Менеджер для Інтернет Магазину + +## Опис + +Цей модуль реалізує голосового менеджера для інтернет магазину, який автоматично відповідає на вхідні дзвінки та надає інформацію про товари, замовлення та контакти. + +## Функціональність + +### Основні можливості: +1. **Автоматична відповідь на дзвінки** - привітальне повідомлення з меню опцій +2. **Інформація про товари** - загальна інформація про каталог товарів +3. **Перевірка статусу замовлення** - можливість дізнатися про стан замовлення +4. **Контактна інформація** - телефон, години роботи +5. **Переведення на оператора** - зв'язок з живим оператором +6. **Логування дзвінків** - всі дзвінки автоматично зберігаються в Creatio CRM + +### Меню опцій: +- **1** - Інформація про товари +- **2** - Перевірка статусу замовлення +- **3** - Зв'язок з оператором +- **4** - Контактна інформація + +## Технічна реалізація + +### Використані технології: +- **FastAPI** - веб-фреймворк для обробки HTTP запитів +- **Twilio** - сервіс для обробки голосових дзвінків (TwiML) +- **Text-to-Speech** - перетворення тексту у мову (Polly.Ruslana для української мови) +- **Creatio API** - інтеграція з CRM системою для логування + +### Архітектура: +``` +Вхідний дзвінок → Twilio → /voice/incoming → Voice Manager → TwiML відповідь +Введення цифр → Twilio → /voice/handle-input → Voice Manager → TwiML відповідь +Статус дзвінка → Twilio → /voice/status → Логування в Creatio +``` + +## Налаштування + +### Необхідні змінні середовища: +```bash +# Основні налаштування бота +TELEGRAM_BOT_TOKEN=your_bot_token +WEBHOOK_BASE_URL=https://your-domain.com + +# Налаштування Creatio +CREATIO_BASE_URL=https://your-creatio-instance.com/0 +CREATIO_IDENTITY_SERVICE_URL=https://your-creatio-instance.com/0 +CREATIO_CLIENT_ID=your_client_id +CREATIO_CLIENT_SECRET=your_client_secret + +# Налаштування голосового менеджера +STORE_PHONE_NUMBER=+380991234567 +``` + +### Налаштування Twilio: + +1. **Створіть акаунт на Twilio** +2. **Купіть телефонний номер** +3. **Налаштуйте webhook URLs:** + - Voice URL: `https://your-domain.com/voice/incoming` + - Voice Method: `POST` + - Status Callback URL: `https://your-domain.com/voice/status` + - Status Callback Method: `POST` + +### Endpoints: + +- `POST /voice/incoming` - обробка вхідних дзвінків +- `POST /voice/handle-input` - обробка введення користувача +- `POST /voice/status` - отримання статусу дзвінків + +## Використання + +### Сценарій типового дзвінка: + +1. **Клієнт дзвонить на номер магазину** +2. **Система автоматично відповідає:** + > "Вітаємо у Інтернет Магазин! Для отримання інформації про товари натисніть 1, для перевірки статусу замовлення натисніть 2, для зв'язку з оператором натисніть 3, для отримання контактної інформації натисніть 4." + +3. **Клієнт натискає цифру 1** (інформація про товари) +4. **Система відповідає:** + > "Наш каталог включає електроніку, одяг, товари для дому та спорту. Для детальної консультації натисніть 0 для зв'язку з консультантом, або відвідайте наш сайт." + +5. **Всі дії логуються в Creatio CRM** + +### Можливості розширення: + +- **Інтеграція з базою даних товарів** для надання детальної інформації +- **Система перевірки замовлень** за номером +- **Багатомовність** (додавання інших мов) +- **Розширене меню** з додатковими опціями +- **Інтеграція з календарем** для резервування консультацій + +## Моніторинг та логування + +Всі дзвінки автоматично логуються в Creatio CRM з наступною інформацією: +- Номер телефону дзвінка +- Час дзвінка +- Тривалість розмови +- Обрані опції меню +- Статус завершення дзвінка + +## Безпека + +- Всі дані передаються через HTTPS +- API ключі зберігаються як змінні середовища +- Логування не включає особисті дані клієнтів +- Інтеграція з Creatio використовує OAuth2 \ No newline at end of file diff --git a/main.py b/main.py index c491617..1868e66 100644 --- a/main.py +++ b/main.py @@ -4,11 +4,12 @@ from bot import bot, dp from aiogram.types import Update from dotenv import load_dotenv +from voice_manager import handle_incoming_call, handle_voice_input, handle_voice_status_callback load_dotenv() logging.basicConfig(level=logging.INFO) -app = FastAPI() +app = FastAPI(title="Telegram Creatio ChatAPI с голосовым менеджером") WEBHOOK_URL = f"{os.getenv('WEBHOOK_BASE_URL')}/webhook" @@ -31,4 +32,24 @@ async def telegram_webhook(request: Request): @app.get("/") async def health_check(): - return {"status": "ok"} + return { + "status": "ok", + "services": ["telegram_bot", "voice_manager"], + "description": "Telegram Bot с интеграцией Creatio и голосовым менеджером для интернет магазина" + } + +# Голосовые endpoints +@app.post("/voice/incoming") +async def voice_incoming_call(request: Request): + """Обработка входящих звонков через Twilio""" + return await handle_incoming_call(request) + +@app.post("/voice/handle-input") +async def voice_handle_input(request: Request): + """Обработка пользовательского ввода во время звонка""" + return await handle_voice_input(request) + +@app.post("/voice/status") +async def voice_status_callback(request: Request): + """Обработка статусных сообщений о звонках""" + return await handle_voice_status_callback(request) diff --git a/render.yaml b/render.yaml index 505120b..c4747af 100644 --- a/render.yaml +++ b/render.yaml @@ -10,8 +10,12 @@ services: value: твій_бот_токен - key: CREATIO_BASE_URL value: https://knajpapro.creatio.com/0 + - key: CREATIO_IDENTITY_SERVICE_URL + value: https://knajpapro.creatio.com/0 - key: CREATIO_CLIENT_ID value: твій_client_id - key: CREATIO_CLIENT_SECRET value: твій_client_secret + - key: STORE_PHONE_NUMBER + value: "+380991234567" plan: free diff --git a/requirements.txt b/requirements.txt index 413c478..090d469 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ httpx==0.27.0 python-dotenv==1.0.1 uvicorn==0.29.0 pydantic==2.5.3 +python-multipart==0.0.6 diff --git a/voice_manager.py b/voice_manager.py new file mode 100644 index 0000000..95778d0 --- /dev/null +++ b/voice_manager.py @@ -0,0 +1,181 @@ +import os +import logging +from fastapi import Request, Response +from fastapi.responses import PlainTextResponse +from creatio_api import create_or_get_chat, post_message +from dotenv import load_dotenv + +load_dotenv() + +logger = logging.getLogger(__name__) + +class VoiceManager: + """Голосовий менеджер для інтернет магазину""" + + def __init__(self): + self.store_name = "Інтернет Магазин" + self.phone_number = os.getenv("STORE_PHONE_NUMBER", "+380991234567") + self.working_hours = "Пн-Пт 9:00-18:00, Сб-Нд 10:00-16:00" + + def generate_welcome_message(self) -> str: + """Привітальне повідомлення""" + return ( + f"Вітаємо у {self.store_name}! " + "Для отримання інформації про товари натисніть 1, " + "для перевірки статусу замовлення натисніть 2, " + "для зв'язку з оператором натисніть 3, " + "для отримання контактної інформації натисніть 4." + ) + + def handle_product_inquiry(self) -> str: + """Обробка запитів про товари""" + return ( + "Наш каталог включає електроніку, одяг, товари для дому та спорту. " + "Для детальної консультації натисніть 0 для зв'язку з консультантом, " + "або відвідайте наш сайт." + ) + + def handle_order_status(self) -> str: + """Обробка запитів про статус замовлення""" + return ( + "Для перевірки статусу замовлення введіть номер замовлення після звукового сигналу. " + "Або натисніть 0 для зв'язку з оператором." + ) + + def handle_contact_info(self) -> str: + """Контактна інформація""" + return ( + f"Наші контакти: телефон {self.phone_number}, " + f"режим роботи: {self.working_hours}. " + "Ми також доступні в Telegram боті для швидкого зв'язку." + ) + + def handle_transfer_to_operator(self) -> str: + """Переведення на оператора""" + return ( + "Перевожу вас на оператора. Будь ласка, залишайтесь на лінії. " + "Час очікування може становити до 3 хвилин." + ) + + def generate_twiml_response(self, message: str, gather_options: bool = True) -> str: + """Генерація TwiML відповіді для Twilio""" + if gather_options: + return f''' + + + {message} + + Дякуємо за дзвінок. До побачення! + +''' + else: + return f''' + + {message} + +''' + +voice_manager = VoiceManager() + +async def handle_incoming_call(request: Request) -> Response: + """Обробка вхідного дзвінка""" + form_data = await request.form() + caller_phone = form_data.get("From", "Unknown") + call_sid = form_data.get("CallSid", "Unknown") + + logger.info(f"📞 Вхідний дзвінок від {caller_phone}, CallSid: {call_sid}") + + try: + # Створюємо чат у Creatio для логування дзвінка + chat_id = await create_or_get_chat(None, f"phone:{caller_phone}") + await post_message( + chat_id, + "Voice Manager", + f"Вхідний дзвінок від {caller_phone}" + ) + logger.info(f"✅ Дзвінок залогований у Creatio, ChatId: {chat_id}") + + except Exception as e: + logger.error(f"❌ Помилка при логуванні дзвінка: {e}") + + welcome_message = voice_manager.generate_welcome_message() + twiml_response = voice_manager.generate_twiml_response(welcome_message) + + return PlainTextResponse(twiml_response, media_type="application/xml") + +async def handle_voice_input(request: Request) -> Response: + """Обробка введення користувача через телефон""" + form_data = await request.form() + digits = form_data.get("Digits", "") + caller_phone = form_data.get("From", "Unknown") + call_sid = form_data.get("CallSid", "Unknown") + + logger.info(f"🔢 Отримано цифру {digits} від {caller_phone}") + + try: + # Логування дії користувача + chat_id = await create_or_get_chat(None, f"phone:{caller_phone}") + await post_message( + chat_id, + caller_phone, + f"Обрано опцію: {digits}" + ) + + # Обробка введення користувача + if digits == "1": + response_message = voice_manager.handle_product_inquiry() + await post_message(chat_id, "Voice Manager", "Інформація про товари") + elif digits == "2": + response_message = voice_manager.handle_order_status() + await post_message(chat_id, "Voice Manager", "Перевірка статусу замовлення") + elif digits == "3": + response_message = voice_manager.handle_transfer_to_operator() + await post_message(chat_id, "Voice Manager", "Переведення на оператора") + elif digits == "4": + response_message = voice_manager.handle_contact_info() + await post_message(chat_id, "Voice Manager", "Контактна інформація") + else: + response_message = "Невірний вибір. " + voice_manager.generate_welcome_message() + await post_message(chat_id, "Voice Manager", f"Невірний вибір: {digits}") + + logger.info(f"📝 Дія залогована у Creatio") + + except Exception as e: + logger.error(f"❌ Помилка при обробці введення: {e}") + response_message = "Виникла технічна помилка. Спробуйте пізніше або зв'яжіться з нами за телефоном." + + # Для опцій 1, 2, 4 даємо можливість повернутися до меню + gather_options = digits in ["1", "2", "4"] + if gather_options: + response_message += " Натисніть 0 для повернення до головного меню." + + twiml_response = voice_manager.generate_twiml_response( + response_message, + gather_options + ) + + return PlainTextResponse(twiml_response, media_type="application/xml") + +async def handle_voice_status_callback(request: Request) -> Response: + """Обробка статусних повідомлень про дзвінки""" + form_data = await request.form() + call_sid = form_data.get("CallSid", "Unknown") + call_status = form_data.get("CallStatus", "Unknown") + caller_phone = form_data.get("From", "Unknown") + duration = form_data.get("CallDuration", "0") + + logger.info(f"📈 Статус дзвінка {call_sid}: {call_status}, тривалість: {duration}с") + + try: + # Логування завершення дзвінка + chat_id = await create_or_get_chat(None, f"phone:{caller_phone}") + await post_message( + chat_id, + "Voice Manager", + f"Дзвінок завершено. Статус: {call_status}, тривалість: {duration}с" + ) + + except Exception as e: + logger.error(f"❌ Помилка при логуванні статусу дзвінка: {e}") + + return {"status": "ok"} \ No newline at end of file