![](https://habrastorage.org/getpro/habr/upload_files/f48/0bb/2d6/f480bb2d66166a2d9dfe088292d17c65.png)
Экономическая ситуация нынче нестабильная, лишних денег у людей нет. И вот значится обратились ко мне товарищи со словами: "Ты же когда-то там сайты делал, помоги разобраться". После такой просьбы пришлось мне сдувать пыль вековую с постаревших связей нейронных, да бубен шаманский доставать.
Но не переживайте, эта статья не о том, как я спустя 12 лет снова сел ковырять сайты на PHP. Всё это банально и писать об этом на Хабр я бы не стал.
А вот, что мне действительно было интересно, так это поковыряться в настройках голосового бота. Никогда раньше с таким не сталкивался и настолько меня это увлекло, что по горячим следам начал писать статью.
Вы готовы загибать пальцы?
Итак, сегодня мы с вами:
Настроим сценарий голосового бота для подтверждения интернет заказа.
Одним глазом взглянем на API для управления ботом и отправим пару запросов через Postman.
Добавим пару кастомных действий в WooCommerce, чтобы робот вместо оператора разными голосами подтверждал заказ.
Немного забегая вперед, скажу, что у меня есть план сделать еще парочку статей на эту тему, поэтому сегодня мы с вами рассмотрим "минимальный набор", чтобы мне было о чем писать дальше.
Также, я должен сделать привычный дисклеймер:
"Я техпис, а не программист. поэтому все программные решения в этой статье, скорее всего являются далеко не лучшими практиками".
Ну вот с формальностями покончили можем смело приступать к делу.
Статья получилась объемная поэтому я оставлю оглавление:
Настройка сценария в личном кабинете
Первым делом, нам необходимо создать проект в личном кабинете MTT VoiceBox
Вопрос регистрации В ЛК я пропущу, потому что мне доступ уже предоставили и сам я не испил чашу регистрации в сервисе личного кабинета.
Также я пропущу описание некоторых очевидных шагов. У разработчиков в Wiki есть инструкция по работе с личным кабинетом и я не буду её дублировать.
После входа в личный кабинет, перейдем на вкладку "Сценарии" и нажмем кнопку "Создание сценария"
![Страница "Сценарии" Страница "Сценарии"](https://habrastorage.org/getpro/habr/upload_files/14c/fd1/aa0/14cfd1aa0d36c3a976c0ffc8c0997fd4.png)
Мы не будем использовать сложные шаблоны, а просто создадим сценарий для исходящих звонков.
Перед нашим взором предстанет окно редактора сценариев. Редактор интуитивно понятен.
![Сверху диаграмма сразу после создания, снизу состояние при добавлении первого блока "Проигрыватель" Сверху диаграмма сразу после создания, снизу состояние при добавлении первого блока "Проигрыватель"](https://habrastorage.org/getpro/habr/upload_files/96f/09d/daa/96f09ddaa80ceb2bfdeedc8dbcfae846.png)
Принцип работы такой: берем с правой панели блоки, кликаем два раза чтобы добавить их в рабочую область и для выбранного блока настраиваем его параметры.
Для меня стало некоторым неудобством то, что нельзя потянуть мышкой связи от одного блока к другому (актуально на декабрь 2021), приходится выбирать следующий шаг для блока в панели свойств. Правда со временем к этому привыкаешь.
Для каждого блока можно посмотреть подсказку.
Сохранить изменения в блоке можно только после того, как корректно заполнены все обязательные поля.
Давайте посмотрим на итоговый сценарий:
![Итоговый сценарий Итоговый сценарий](https://habrastorage.org/getpro/habr/upload_files/c9d/cd5/cb4/c9dcd5cb463d88899afe4cd2e9b7b4be.png)
Всего 10 блоков. Вы можете достаточно быстро повторить его самостоятельно.
Но для тех кому не терпится поскорее попробовать, я оставлю в GitHub возможность быстро клонировать сценарий аж целыми двумя способами:
через специальную ссылку;
через запрос к API;
Прежде чем посмотреть настройки каждого блока расскажу вам о еще одной особенности редактора, на линиях связи между блоками указано в каком случае связь будет активна (например, "Успешно", 'Лимит").
В случае, когда для одной связи назначено несколько условий, то будет показано только один статус, для примера выше "Лимит", поэтому не удивляйтесь если не увидите на схеме каких-то связей.
Давайте пройдемся по всем блокам. Я оставлю везде скриншоты настроек, чтобы вы могли свериться.
Блоки сценария
Исходящий сценарий
Самый первый блок нашего сценария. Устанавливается автоматически.
В этом блоке можно добавить переменные, передаваемые через тело POST запроса к API. Как вы увидите позже переменные могут быть массивами, например goods
.
Еще в настройках блока можно выбрать голос бота (есть и мужские и женские).
Далее идет следующий шаг сценария в случае успешного завершения блока, в данном случае мы перейдем на блок "Вызов".
Я рекомендую давать блокам осмысленные названия, потому что в противном случае будет очень легко запутаться при выборе между: "Состояние 5" и "Состояние 7".
В дальнейшем я не буду подробно останавливаться на блоках перехода, потому что логика везде похожа.
!["Исходящий сценарий" "Исходящий сценарий"](https://habrastorage.org/getpro/habr/upload_files/0cd/c2a/ebe/0cdc2aebe367c9180ed27b5c925768f2.png)
Исходящий вызов ("Вызов")
Блок, который непосредственно совершит звонок на номер клиента.
Опция "Номер из кампании" позволит нам выбрать любой номер, который мы позже вызовем через API.
Перейдем к блоку "Проигрыватель".
![Исходящий вызов (Вызов) Исходящий вызов (Вызов)](https://habrastorage.org/getpro/habr/upload_files/0b0/6b2/a57/0b06b2a5790d5c0b167837bb339d9a50.png)
Проигрыватель ("Зачитать список товаров")
Блок проигрывает абоненту заданное сообщение
Можно заранее подготовить медиа файл (это мы отложим на другой раз), а можно просто зачитать текст голосом, добавив переменные из блока "Исходящий сценарий".
Переменные в тексте выделяются двойными фигурными скобками, например, {{name}}.
Как вы, уже догадались, робот в данном месте проговорит переменную name
из POST запроса.
Для меня оказалось приятной неожиданностью, то что робот умеет читать числительные. Таким образом, отправленные мной в поле total
10000, были прочитаны именно как десять тысяч, а не "один - ноль - ноль - ноль - ноль - ноль".
Блок похоже умеет распознавать автоответчики, но мне пока было не на чем проверить этот функционал.
Но я всё равно решил его сделать для демонстрации отправки HTTP запроса в случае негативного сценария. К описанию блока "Неуспех" мы придем, ближе к концу раздела, а пока перейдем дальше по успешному пути.
![Проигрыватель ("Зачитать список товаров") Проигрыватель ("Зачитать список товаров")](https://habrastorage.org/getpro/habr/upload_files/554/b61/7f3/554b617f3297d8dd79a340eaeb7ece49.png)
Интерактивный ввод ("Подтверждение")
Интерактивное меню, отвечает за пользовательский ввод.
Пользователь может осуществить ввод несколькими способами:
с помощью клавиатуры телефона;
голосом;
комбинацией первых двух способов;
Мне показался любопытным именно вариант с голосом.
В данном случае можно самостоятельно подобрать слова (с учётом маски), а можно выбрать готовый набор ответов, как в итоге сделал я.
Рекомендую оставить пользователю голосовое сообщение, о том что надо подтвердить заказ.
После данного блока у нас намечается еще одно ветвление.
Допустим пользователь не может нам ответить, что-то внятное за 10 секунд, тогда мы переключим его на живого оператора, но об этом также ближе к концу раздела.
А пока мы пойдем к следующему блоку по успешному пути.
![Интерактивный ввод ("Подтверждение") Интерактивный ввод ("Подтверждение")](https://habrastorage.org/getpro/habr/upload_files/92a/123/80d/92a12380d1a4c0cf66b59a93281560db.png)
HTTP (Обновить статус)
Данный блок отправляет запрос в интернет-магазин на обновление статуса заказа. Мы будем использовать WooCommerce и соответственного его API. После выполнения запроса в магазине статус заказа изменится на "Заказ подтвержден".
По сути, блок просто представляет поля для сборки запроса. На всякий случай предупрежу, что я проверял отправку запроса из сценария, только для случая, когда магазин имеет сертификат безопасности и следовательно мы обращаемся к нему через "https". С обычным "http" у WooCommerce есть некоторые сложности, с которыми я не хотел бороться.
В принципе параметры запроса я брал из экспорта в cURL через Postman, о чем немного подробнее будет рассказано в следующем разделе.
Обратите внимание, что в параметрах пути запроса, мы можем использовать переменную. В данном случае orderid
, которую мы ранее передали в исходящем запросе.
Я не стал усложнять и продумывать действия в случае неудачного запроса.
После успешного HTTP запроса нам остается только попрощаться с клиентом.
![HTTP (Обновить статус) HTTP (Обновить статус)](https://habrastorage.org/getpro/habr/upload_files/f80/9b0/628/f809b06284ddc2d9ff7c77f8100ae871.png)
Проигрыватель ("Спасибо за подтверждение")
Аналогичный блок мы уже разбирали, поэтому просто оставлю скрин параметров.
Осталось только повесить трубку.
![](https://habrastorage.org/getpro/habr/upload_files/0db/ac9/0cb/0dbac90cb246fd55d86db687a608d6ed.png)
Отбой
Поскольку я уже слегка устал к седьмому по счету блоку, этот блок один из моих любимых.
Проходите дальше, тут не на что смотреть, мы просто положим трубку.
![Отбой Отбой](https://habrastorage.org/getpro/habr/upload_files/433/4b1/560/4334b156073de8513d2093b897e2299c.png)
На этом успешный путь завершен. Давайте рассмотрим случай, когда пользователь не смог справиться с интерактивным вводом.
Проигрыватель ("Дождитесь оператора")
Просто предупредим пользователя, что ему не стоит класть трубку пока мы соединяем его с оператором.
![Проигрыватель ("Дождитесь оператора") Проигрыватель ("Дождитесь оператора")](https://habrastorage.org/getpro/habr/upload_files/8f2/baa/0ec/8f2baa0ecc88cd2dd8f78061672b9b6d.png)
Переадресация ("Звонок оператору")
В данном блоке мы можем перенаправить вызов оператору. Если честно, этот блок я тоже не тестировал, потому что пока не было такой цели. Но наверняка, все будет работать.
После переадресации по идее этот вызов должен завершиться на блоке "Отбой".
![Переадресация ("Звонок оператору") Переадресация ("Звонок оператору")](https://habrastorage.org/getpro/habr/upload_files/5a1/63b/24b/5a163b24b61d0f71cde3047c2effe99c.png)
Остался последний "неуспешный" блок.
HTTP ("Неуспех")
Запрос практически полностью идентичен "успешному" HTTP запросу, с разницей только в статусе заказа, который мы после запроса изменим на "Нужен звонок".
![HTTP ("Неуспех") HTTP ("Неуспех")](https://habrastorage.org/getpro/habr/upload_files/a0f/635/b4d/a0f635b4dd2046b8e38bb5bedbef0ab4.png)
Настройки кампании
Для того, чтобы отправлять запрос к API нам необходимо знать, параметры авторизации и значение поля method.
Для этого надо создать кампанию.
Перейдём на вкладку "Кампании"
Создадим новую, кампанию и выберем в её настройках опцию "Запуск - по HTTP запросу".
Остальные параметры, интуитивно понятны.
Вот так выглядит кампания для нашего сценария.
![Страница настроек кампании Страница настроек кампании](https://habrastorage.org/getpro/habr/upload_files/46c/ec8/f21/46cec8f214002ec010b95d51d0adc8e3.png)
Само собой ключевые значения я замазал, но в целом можно понять, откуда брать данные для запроса к API.
Есть один важный момент. Как я понял, после привязки сценария к кампании, многие его параметры нельзя будет изменить пока кампания не удалена. Например, нельзя будет удалить блок, или добавить новый. Зато можно будет изменить голос робота. Именно, этим мы с вами воспользуемся в следующих разделах.
Работа с API через Postman
При написании статьи я ориентировался на эту версию документации от разработчиков.
Для вашего удобства я собрал в Postman коллекцию тех методов, которые мы используем для статьи.
На всякий случай помимо методов Voicebox я добавил пару методов для проверки API WooCommerce.
Скачать коллекцию и окружение можно в GitHub.
Не забудьте в окружении указать ваши значения для переменных, потому что свои данные я удалил.
Собственно дальше, мы будем ориентироваться именно на коллекцию в Postman.Как я уже говорил выше, логин, пароль и метод можно найти на странице кампании, с которой мы связали наш сценарий.
Запрос на звонок
Наверняка у вас уже горят руки наконец-то попробовать звонок. Не будем откладывать это дело в долгий ящик.
Звонок, это самое простое. Нам надо отправить POST запрос на адрес
https://voicebox.mtt.ru/v1/sb, выбрав базовую авторизацию с указанием логина и пароля из настроек кампании.
Вот как это выглядит в Postman
![Пример запроса в Postman Пример запроса в Postman](https://habrastorage.org/getpro/habr/upload_files/b48/817/cb2/b48817cb23f9d6256f66c023449099a9.png)
Вот пример cURL запроса (не забудьте подставить свои данные):
curl -L -X POST 'https://voicebox.mtt.ru/api/v1/sb' -H 'Authorization: Basic some_token' -H 'Content-Type: application/json' --data-raw '{
"method": "10dXXXXXXXXXXXXXXXXXXXbe1",
"data": {
"number": "796NNNNNNNN",
"goods": [
"Tapki",
"Kepka"
],
"name": "Roman",
"total":10000,
"orderid":17
}
}'
В ответ метод вернёт, какой-то ID, возможно это ID запроса на звонок (я так и не понял из документации).
Но самое главное, что нам позвонит робот и проговорит то, что мы ожидаем услышать.
Создание сценария
В первой главе статьи я обещал, что покажу, как клонировать сценарий через запрос к API.
Но есть одна маленькая трудность, мы не знаем наш customerID, ну по крайней мере мне его "заказчик" не дал.
Я не стал мучать техподдержку разработчиков, тем более, что личный кабинет оформлен не на меня, поэтому просто воспользовался старой доброй консолью разработчика.
Открываем страницу со сценариями, а в консоли разработчика (я использую Chrome) вкладку "Network".
![](https://habrastorage.org/getpro/habr/upload_files/2d0/a0b/602/2d0a0b602f40c383762ab2fbf01b1261.png)
Забираем customerID
(подчеркнуто синим) из Request URL запроса к методу scenarios.
Есть еще одна вещь, которая нам пригодится. У меня почему-то не работает для этого метода базовая авторизация, API ругается, что нет поля token.
Я думаю, это однажды поправят, а пока просто заберем token из authorization (подчеркнуто синим). Теперь вместо, Basic Auth используем Bearer token.
![](https://habrastorage.org/getpro/habr/upload_files/43e/0b7/ef0/43e0b7ef0534cb94e7467195f47a661f.png)
Метод создаст сценарий и вернет нам всю его структуру вместе с ID.
cUrl запрос спрячу под спойлер.
cURL запрос на создание
curl -L -X POST 'https://voicebox-api.mtt.ru/v1/customers/XX/scenarios' -H 'Authorization: Bearer NDFKXXGITNTUXXXX1YJUTODE2MTQXXXXXXZDRK' -H 'Content-Type: application/json' --data-raw '{ "name": "Для хабра клон1", "type": "outgoing", "comment": "", "states": [ { "type": "Initial", "name": "State", "x": -668, "y": 102, "error": null, "success": { "title": "", "newState": "Вызов" } }, { "type": "OutgoingCall", "name": "Вызов", "x": -512, "y": 205, "error": null, "success": { "title": "", "newState": "Зачитать список товаров" }, "phone": "{{numberB}}" }, { "type": "VoiceMessage", "name": "Зачитать список товаров", "x": -307, "y": 331, "error": null, "success": { "title": "", "newState": "Подтверждение" }, "detectAI": true, "onDetectAI": { "title": "", "newState": "Неуспех" }, "announcement": { "type": "text", "file": "", "text": "Здравствуйте {{name}}, вы заказали {{goods}}, на сумму {{total}} рублей" }, "onNoInput": null, "onExpiry": null, "onAParty": null, "onInvalidInput": null }, { "type": "ReleaseCall", "name": "Отбой", "x": -72, "y": 988, "error": null, "success": null }, { "type": "HTTPRequest", "name": "Неуспех", "x": -472, "y": 669, "error": null, "success": { "title": "", "newState": "Отбой" }, "method": "PUT", "host": "XXXXX.ru", "protocol": "https", "port": 443, "url": "/wp-json/wc/v3/orders/{orderid}", "headers": { "Authorization": "Basic Y2tfMjBiYTI0NjQ0XXXXXXXY2Y1ZXXXXXXkZTE2N2YTQ4YTpjc182NjU2ODVlYjUzMWEwN2JkY2Y3Y2FmYjcwNjY5ODQ4YmRlNmRjZjQ0" }, "requestParameters": null, "requestParametersRaw": "{"status": "need_call"}", "requestParametersType": "raw", "responseCodeVariable": "RESPONSE_CODE_1", "responseBodyVariables": {} }, { "type": "IVR", "name": "Подтверждение", "x": 25, "y": 364, "error": { "title": "", "newState": "Дождитесь оператора" }, "success": null, "onDetectAI": null, "awaitingTime": 10, "announcement": { "type": "text", "file": "", "text": "Вы подтверждаете заказ?" }, "repeatCount": 3, "detectAI": false, "voiceInput": true, "keyboardInput": false, "immediateStt": false, "inputAnalysis": [ { "success": { "title": "", "newState": "Обновить статус" }, "dtmf": "", "words": null, "keywords": [ { "name": "Согласие", "id": "5", "lost": false } ] } ], "onNoInput": { "title": "", "newState": "Дождитесь оператора" }, "onExpiry": { "title": "", "newState": "Дождитесь оператора" }, "onAParty": null, "onInvalidInput": null }, { "type": "HTTPRequest", "name": "Обновить статус", "x": 91, "y": 606, "error": null, "success": { "title": "", "newState": "Спасибо за подтверждение" }, "method": "PUT", "host": "XXXXX.ru", "protocol": "https", "port": 443, "url": "/wp-json/wc/v3/orders/{{orderid}}", "headers": { "Authorization": "Basic Y2tfMjBiYTI0NjQ0MXXXXXXY2FmYjcwNjY5ODQ4YmRlNmRjZjQ0" }, "requestParameters": null, "requestParametersRaw": "{\n "status": "confirmed_ordr"\n}", "requestParametersType": "raw", "responseCodeVariable": "RESPONSE_CODE_2", "responseBodyVariables": {} }, { "type": "Redirection", "name": "Звонок оператору", "x": -136, "y": 722, "error": null, "success": { "title": "", "newState": "Отбой" }, "numberB": "{{RESPONSE_CODE_1}}", "extension": "", "callingPartyDisplay": "forwarder_number", "forwardType": "number", "sipUri": "", "credentialInfo": null }, { "type": "VoiceMessage", "name": "Дождитесь оператора", "x": -194, "y": 499, "error": null, "success": { "title": "", "newState": "Звонок оператору" }, "detectAI": false, "onDetectAI": null, "announcement": { "type": "text", "file": "", "text": "Дождитесь оператора" }, "onNoInput": null, "onExpiry": null, "onAParty": null, "onInvalidInput": null }, { "type": "VoiceMessage", "name": "Спасибо за подтверждение", "x": 185, "y": 842, "error": null, "success": { "title": "", "newState": "Отбой" }, "detectAI": false, "onDetectAI": null, "announcement": { "type": "text", "file": "", "text": "Спасибо за подтверждение" }, "onNoInput": null, "onExpiry": null, "onAParty": null, "onInvalidInput": null } ], "customVariables": [ "goods", "total", "name", "orderid" ], "createdAt": "2021-12-12T22:05:44.441989969Z", "updatedAt": "2021-12-12T22:05:44.442003632Z", "settings": { "voice": "omazh", "speed": 1, "emotion": "neutral" }, "shareLink": "" }'
Обновление сценария
Обновление делается аналогично, только с методом PUT.
Я решил не маяться с обязательными / необязательными параметрами, а просто взять все поля из прошлого ответа.
Нас будет волновать только замена этого фрагмента:
"settings": {
"voice": "omazh",
"speed": 1,
"emotion": "neutral"
},
Будем менять голос робота (значение поля voice) на "oksana" или "filipp".
![](https://habrastorage.org/getpro/habr/upload_files/83a/fc4/b5b/83afc4b5b3bfc3eaeb06241440669fe6.png)
Пример запроса в cURL, я приводить не буду он есть в коллекции Postman, а также внутри одной из функций следующего раздела.
Думаю, пора переходить к завершающей стадии.
Интеграция с WooCommerce (WordPress)
Интеграция — это конечно громко сказано.
Мы просто добавим пару новых статусов, чтобы их мог менять наш бот, а также добавим пару действий, чтоб менять голос бота на мужской и женский.
Такая вот конвергенция двух систем.
Я не буду вдаваться в подробности установки WordPress и WooCommerce.
На момент написания статьи я использовал:
WooCommerce - версия 5.9.0
WordPress - версия 5.8.2
Первым делом добавим новые статусы в файл functions.php текущей темы.
Напомню, что отредактировать этот файл можно через настройки перейдя в раздел
"Внешний вид" -> "Редактор тем" и открыв "Функции темы".
Вы можете просто забрать код из GitHub и вставить фрагмент, куда-нибудь в конец файла.
Вначале статьи я уже говорил, что я не программист, но не грех будет напомнить.
Я если честно, последний раз смотрел на код на PHP лет 12 назад, а с WordPress и вовсе не работал толком никогда, поэтому не буду врать, я просто позаимствовал код отсюда и адаптировал под себя.
Код под спойлером
/**
Adding custom status for hanr
*/
// Регистрируем наши новые статусы заказа в системе
function register_voicebox_call_statuses() {
register_post_status( 'wc-need_call', array(
'label' => 'Нужен звонок',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop( 'Нужен звонок <span class="count">(%s)</span>', 'Нужен звонок <span class="count">(%s)</span>' )
) );
register_post_status( 'wc-confirmed_ordr', array(
'label' => 'Заказ подтвержден',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop( 'Заказ подтвержден <span class="count">(%s)</span>', 'Заказ подтвержден <span class="count">(%s)</span>' )
) );
}
add_action( 'init', 'register_voicebox_call_statuses' );
// добавляем статусы куда-то
function add_voicebox_call_to_order_statuses( $order_statuses ) {
$new_order_statuses = array();
foreach ( $order_statuses as $key => $status ) {
$new_order_statuses[ $key ] = $status;
if ( 'wc-processing' === $key ) {
$new_order_statuses['wc-need_call'] = 'Нужен звонок';
$new_order_statuses['wc-confirmed_ordr'] = 'Заказ подтвержден';
}
}
return $new_order_statuses;
}
add_filter( 'wc_order_statuses', 'add_voicebox_call_to_order_statuses' );
Как я понимаю мы вначале создаем сущности для новых статусов, а потом добавляем их в лист-бокс. Не забудьте сохранить обновления.
Теперь наш сценарий полностью укомплектован, мы можем позвонить через Postman и статусы поменяются.
Но нам явно не хватает возможности сделать вызов через админ-панель WooCommerce.
Добавьте данный код ниже:
Код под спойлером
/**
Adding custom actions for hanr
*/
// Добавим действие со звонком в список
add_action( 'woocommerce_order_actions', 'call_voice1' );
function call_voice1( $actions ) {
$actions['call_voice'] = __( 'Звонок бота', 'text_domain' );
return $actions;
}
// Добавим непосредственно логику для звонка
add_action( 'woocommerce_order_action_call_voice', 'call_voice2' );
function call_voice2( ) {
// Получим данные о заказе
global $woocommerce, $post;
$order = new WC_Order($post->ID);
//to escape # from order id
$order_id = trim(str_replace('#', '', $order->get_order_number()));
//Получим основные данные о заказе и заказчике
$billing_phone = $order->get_billing_phone();
$billing_phone =str_replace("+","",$billing_phone);
$order_total = $order->get_total();
$billing_first_name = $order->get_billing_first_name();
$goods = "";
// Пройдемся циклически по всем продуктам заказа и заберем их названия
foreach ($order->get_items() as $item_key => $item_values):
$item_name = $item_values->get_name(); // Name of the product
$goods=$goods.'"'.$item_name.'",';
endforeach;
// cURL запрос автоматически сгенерирован в Postman, я просто добавил переменные в разрывы строк.
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://voicebox.mtt.ru/api/v1/sb',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"method": "10dada97-XXXX-450d-b681-6a46a6484be1",
"data": {
"number": "'.$billing_phone.'",
"goods": ['.rtrim($goods, ",").'],
"name": "'.$billing_first_name.'",
"total":'.$order_total.',
"orderid":'.$order_id.'}
}',
CURLOPT_HTTPHEADER => array(
'Authorization: Basic XXXXXXhhNjgtMjAxZi00Mjg3LWEyZjItMjRhNmM4NGEyMDc5Ok93NyZAfjZyMUc=',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
}
Тут код тоже далеко не весь мой. Я взял сами действия тут, а запросы к API просто экспортировал из Postman, добавив в разрывы текста нужные данные.
Остался последний шаг - добавить возможность изменять голос бота из админ-панели.
Мне было лень разбираться с обязательными и необязательными полями в запросе на обновление сценария, поэтому я просто экспортировал его из Postman и добавил смену голоса бота в разрыв текста.
Код под спойлером
// Функция для обновления голоса, через полное обновление сценария
function change_voice($voice) {
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://voicebox-api.mtt.ru/v1/customers/XX/scenarios/XX2536',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS =>'{
"id": 252536,
"name": "Для Хабра 1",
"type": "outgoing",
"comment": "",
"states": [
{
"type": "Initial",
"name": "State",
"x": -668,
"y": 102,
"error": null,
"success": {
"title": "",
"newState": "Вызов"
}
},
{
"type": "OutgoingCall",
"name": "Вызов",
"x": -512,
"y": 205,
"error": null,
"success": {
"title": "",
"newState": "Зачитать список товаров"
},
"phone": "{{numberB}}"
},
{
"type": "VoiceMessage",
"name": "Зачитать список товаров",
"x": -307,
"y": 331,
"error": null,
"success": {
"title": "",
"newState": "Подтверждение"
},
"detectAI": true,
"onDetectAI": {
"title": "",
"newState": "Неуспех"
},
"announcement": {
"type": "text",
"file": "",
"text": "Здравствуйте {{name}}, вы заказали {{goods}}, на сумму {{total}} рублей"
},
"onNoInput": null,
"onExpiry": null,
"onAParty": null,
"onInvalidInput": null
},
{
"type": "ReleaseCall",
"name": "Отбой",
"x": -72,
"y": 988,
"error": null,
"success": null
},
{
"type": "HTTPRequest",
"name": "Неуспех",
"x": -472,
"y": 669,
"error": null,
"success": {
"title": "",
"newState": "Отбой"
},
"method": "PUT",
"host": "XXXX.ru",
"protocol": "https",
"port": 443,
"url": "/wp-json/wc/v3/orders/{orderid}",
"headers": {
"Authorization": "Basic XXXXXjBiYTI0NjQ0MmUxYjU0ZThhM2RjY2Y1ZDFkY2JkZTE2N2Y5NTQ4YTpjc182NjU2ODVlYjUzMWEwN2JkY2Y3Y2FmYjcwNjY5ODQ4YmRlNmRjZjQ0"
},
"requestParameters": null,
"requestParametersRaw": "{\\"status\\": \\"need_call\\"}",
"requestParametersType": "raw",
"responseCodeVariable": "RESPONSE_CODE_1",
"responseBodyVariables": {}
},
{
"type": "IVR",
"name": "Подтверждение",
"x": 25,
"y": 364,
"error": {
"title": "",
"newState": "Дождитесь оператора"
},
"success": null,
"onDetectAI": null,
"awaitingTime": 10,
"announcement": {
"type": "text",
"file": "",
"text": "Вы подтверждаете заказ?"
},
"repeatCount": 3,
"detectAI": false,
"voiceInput": true,
"keyboardInput": false,
"immediateStt": false,
"inputAnalysis": [
{
"success": {
"title": "",
"newState": "Обновить статус"
},
"dtmf": "",
"words": null,
"keywords": [
{
"name": "Согласие",
"id": "5",
"lost": false
}
]
}
],
"onNoInput": {
"title": "",
"newState": "Дождитесь оператора"
},
"onExpiry": {
"title": "",
"newState": "Дождитесь оператора"
},
"onAParty": null,
"onInvalidInput": null
},
{
"type": "HTTPRequest",
"name": "Обновить статус",
"x": 91,
"y": 606,
"error": null,
"success": {
"title": "",
"newState": "Спасибо за подтверждение"
},
"method": "PUT",
"host": "XXXX.ru",
"protocol": "https",
"port": 443,
"url": "/wp-json/wc/v3/orders/{{orderid}}",
"headers": {
"Authorization": "Basic XXXXjBiYTI0NjQ0MmUxYjU0ZThhM2RjY2Y1ZDFkY2JkZTE2N2Y5NTQ4YTpjc182NjU2ODVlYjUzMWEwN2JkY2Y3Y2FmYjcwNjY5ODQ4YmRlNmRjZjQ0"
},
"requestParameters": null,
"requestParametersRaw": "{\\n \\"status\\": \\"confirmed_ordr\\"\\n}",
"requestParametersType": "raw",
"responseCodeVariable": "RESPONSE_CODE_2",
"responseBodyVariables": {}
},
{
"type": "Redirection",
"name": "Звонок оператору",
"x": -136,
"y": 722,
"error": null,
"success": {
"title": "",
"newState": "Отбой"
},
"numberB": "{{RESPONSE_CODE_1}}",
"extension": "",
"callingPartyDisplay": "forwarder_number",
"forwardType": "number",
"sipUri": "",
"credentialInfo": null
},
{
"type": "VoiceMessage",
"name": "Дождитесь оператора",
"x": -194,
"y": 499,
"error": null,
"success": {
"title": "",
"newState": "Звонок оператору"
},
"detectAI": false,
"onDetectAI": null,
"announcement": {
"type": "text",
"file": "",
"text": "Дождитесь оператора"
},
"onNoInput": null,
"onExpiry": null,
"onAParty": null,
"onInvalidInput": null
},
{
"type": "VoiceMessage",
"name": "Спасибо за подтверждение",
"x": 185,
"y": 842,
"error": null,
"success": {
"title": "",
"newState": "Отбой"
},
"detectAI": false,
"onDetectAI": null,
"announcement": {
"type": "text",
"file": "",
"text": "Спасибо за подтверждение"
},
"onNoInput": null,
"onExpiry": null,
"onAParty": null,
"onInvalidInput": null
}
],
"customVariables": [
"goods",
"total",
"name",
"orderid"
],
"campaign": {
"id": 252729,
"name": "Хабр1",
"campaignType": "outbound",
"scenario": {
"id": 252536,
"name": "",
"type": "",
"comment": "",
"states": null,
"customVariables": null,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"settings": {
"voice": "",
"speed": 0,
"emotion": ""
},
"shareLink": ""
},
"callerNumber": {
"id": 157409,
"number": "74997059704",
"type": "ABC",
"owner": "MTT",
"regionName": "Moscow",
"regionCode": "MOW",
"category": "REGULAR",
"description": "number buying",
"status": "Active",
"campaigns": null
},
"entryPoint": {
"id": 252728,
"url": ""
},
"launchBy": "http",
"routeId": 26589,
"createdAt": "2021-12-12T17:21:50Z",
"updatedAt": "2021-12-12T17:21:50Z"
},
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"settings": {
"voice": "'.$voice.'",
"speed": 1,
"emotion": "neutral"
},
"shareLink": "XXXXX-c3d9-4d71-b23d-c0c47ca206f2"
}',
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer XXXXXMMZKTNWEYYY0ZNJQXLWJLYTETOGRMYTY4ZGEZZMZK',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo ($response);
// exit('Раскомментируй чтобы посмотреть вывод')
}
// добавляем действие в список действий
add_action( 'woocommerce_order_actions', 'woman_voice1' );
function woman_voice1( $actions ) {
$actions['woman_voice'] = __( 'Женский голос у бота', 'text_domain' );
return $actions;
}
// добавляем логику по замене голоса на женский
add_action( 'woocommerce_order_action_woman_voice', 'woman_voice2' );
function woman_voice2( ) {
change_voice("oksana");
}
// добавляем действие в список действий
add_action( 'woocommerce_order_actions', 'man_voice1' );
function man_voice1( $actions ) {
$actions['man_voice'] = __( 'Мужской голос у бота', 'text_domain' );
return $actions;
}
// добавляем логику по замене голоса на женский
add_action( 'woocommerce_order_action_man_voice', 'man_voice2' );
function man_voice2( ) {
change_voice("filipp");
}
Должно получиться примерно вот так:
![Новые действия и новый статус в админ-панели WooCommerce Новые действия и новый статус в админ-панели WooCommerce](https://habrastorage.org/getpro/habr/upload_files/aba/fe5/e2f/abafe5e2f80c72274a3f4434873065d0.png)
Если выбрать "Звонок бота", то на номер из заказа позвонит бот и скажет дословно "Здравствуйте Роман, Вы заказали: кот в мешке, крот в горшке, на сумму 120 рублей. Вы подтверждаете заказ?".
Ну и само собой мы можем теперь изменить голос бота кнопками "Мужской (женский) голос у бота".
Миссия успешно выполнена.
В благодарность за то, что я показал людям, как работать с голосовым ботом, мне пообещали дать поиграться с ним в свое удовольствие. Так что я думаю написать еще 1–2 статьи по данной теме.
Спасибо всем, кто дочитал до конца, если будут ошибки или битые ссылки пишите мне в "личку" или в комментарии.