Создать чат-бот
Создать чат-бот
Blog

Интеграция чат-бота от Domino CRM с таблицей в Google за счет HTTP-запроса

От современных конструкторов чат-ботов нередко можно услышать: мы работаем как платформа, которая вообще не требует написания кода. Domino CRM позиционирует себя именно так — и зачастую вы действительно можете создать и настроить чат-бота без необходимости программировать. 

Но есть исключения.

Одно из таких исключений — использование HTTP-запросов для получения данных из внешних сервисов. Эта функция становится актуальной, если вам требуется интегрировать большие массивы данных или поддерживать их актуальность в реальном времени.

Когда нужна интеграция?

HTTP-запросы становятся незаменимыми в двух ситуациях:

  • Фильтрация больших массивов информации. Например, в сфере eCommerce или гостиничного бизнеса, где объемы информации слишком велики для наших встроенных шаблонов-каталогов; 
     
  • Частые обновления информации. Если данные меняются регулярно, интеграция с внешними сервисами позволяет избежать постоянной ручной правки.

Прямое подключение через HTTP-запрос решает эти проблемы, упрощая работу.

Как настроить интеграцию на платформе Domino CRM?

Для примера рассмотрим процесс подключения Google-таблицы к чат-боту, работающему на базе Domino CRM. Для этой цели вам понадобятся:

  1. Google-таблица с информацией. При этом сама таблица должна быть обязательно открыта для просмотра. То есть вам надо убедиться,  доступ к источнику данных, вашей таблице, выставлен на “Просмотр”. Без этого никак;

  2. Ключ Google API. Он необходим для выполнения запросов к таблице;

  3. API-эндпоинт. Чтобы упростить процесс, можно воспользоваться облачными функциями;

  4. HTTP-блок в Domino CRM. Этот элемент добавляется в сценарий вашего бота и отвечает за выполнение запросов.

Нижеследующие шаги позволят вам интегрировать внешние данные в диалоги вашего чат-бота, сохраняя актуальность информации и оптимизируя рабочие процессы.

Подготовка

Для демонстрации работы с HTTP-блоком сгенерируем небольшую тестовую базу. Помним, что доступ к ней обязан быть открыт. 

Логично, что нам потребуется знать номер строки для каждой записи. В первом столбце таблицы добавим уникальный идентификатор, который будет совпадать с номером строки. 

1.png

Вот как мы предоставим доступ для просмотра таблицы всем, у кого есть ссылка. 

2.png

Затем потребуется активировать Sheets API. Проследуйте по https://developers.google.com/workspace/guides/enable-apis#sheets-api. Нажмите кнопку "Enable Sheets API" и выполните действия согласно указаниям.

3.png

Сгенерируем ключ API, если он у вас еще не создан. Пройдите по https://console.cloud.google.com/apis/credentials, выберите "Create Credentials" и далее "API key".

4.png

Затем откроется модальное окно, где будет отображен ваш новый ключ. Он понадобится нам на следующих этапах. Пример ключа:

GET https://sheets.googleapis.com/v4/spreadsheets/SPREADSHEET_ID/values/Sheet1!A1:D5

Встанет также необходимость настроить сам API. В данном примере мы воспользуемся универсальной облачной функцией на JS. 

Создание Curl-запроса

Для работы с узлом-нодой HTTP-запроса необходимо сформировать curl-запрос. Ингредиенты для этого:

  • URL вашего API;

  • ID Google-таблицы;

  • Ключ Google API;

  • Критерии для фильтрации данных из таблицы.

Пример URL для API для нашей статьи: 

https://functions.yandexcloud.net/xxx.

Также потребуется указать идентификатор документа. Его можно найти в адресной строке открытой таблицы.

5.png

Как можете убедиться, идентификатор таблицы выглядит так: 

1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E. В каждом индивидуальном случае он, конечно, будет отличаться.

Теперь определим, по каким полям данные будут подвергаться фильтрации. Для этого снова обратимся к таблице, содержащей обозначения позиций, их цены и товарные остатки:

6.png

Представим, что мы желаем дать пользователю чат-бота рычаг для фильтровки данных по самой высокой цене. Для этого нами была сгенерирована облачная функция, доступная через вот такой GET-запрос:

'{API-URL}?doc={DOC-ID}&sheet={SHEET-NAME}&cell={CELLS}&key={G-KEY}&filters={FILTERS}'

Вот что обозначают параметры:

  • {API-URL} — адрес API-эндпоинта (вроде https://functions.yandexcloud.net/xxx);

  • {DOC-ID} — идентификатор таблицы (как то, 1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E);

  • {SHEET-NAME} — обозначение листа в таблице (в данном конкретном сценарии это Sheet1); 

  • {CELLS} — диапазон данных для обработки. Поскольку первая строка таблицы содержит названия столбцов, диапазон начинается с A2 и заканчивается столбцом E (A2:E); 

  • {G-KEY} — ключ Google API (как то: xxxxXxXXXXXXXX-XXXXXXxXXXXXxXxXxXxXxxxx);

  • {FILTERS} — параметры для фильтрации данных. Формат фильтра указан ниже.

В этой статье мы фильтруем данные по ценнику. Чтобы получить товары по цене до 20 включительно, фильтр должен выглядеть так:

{"2":{"lte":"20"}}.

Тут: 

  • "2" — номер столбца для фильтрации. Применительно к нашей функции столбцы нумеруются с 0, поэтому столбец с ценой имеет индекс 2;

  • "lte" — условие фильтрации. Коль скоро нам нужно найти товары, стоимость которых не превышает заданное значение, используется оператор "меньше или равно";

  • "20" — значение, передаваемое пользователем чат-бота, которое задает максимальную цену.

Если требуется дополнительно фильтровать товары по наличию складских остатков, фильтр можно расширить следующим образом: {"2":{"lte":"20"},"3":{"gte":"1"}}.

Иными словами, вы можете одновременно применять несколько условий фильтрации.

7.png

Наша функция позволяет фильтровать информацию по столбцам, используя следующие операторы:

  • eq — "=" (подходит для текстовых значений);

  • lte — "≤" (применяется для чисел);

  • lt — "<" (для чисел); 

  • gt — ">" (для чисел);

  • gte — "≥" (для чисел).

Зафиксировав критерии фильтрации, можно сформировать следующий URL для curl-запроса:

https://functions.yandexcloud.net/xxx?doc=1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E&sheet=Sheet1&cell=A2:E&key=xxxxXxXXXXXXXX-XXXXXXxXXXXXxXxXxXxXxxxx&filters={"2":{"lte":"20"},"3":{"gte":"1"}}

Теперь, когда запрос перед нами, можно переходить к созданию сценария.

Создание сценария

Прежде чем приступить к созданию сценария, рекомендуется сначала настроить поле для хранения ключа Google API в параметрах бота. Это делается в разделе для редактирования бота.

8.png

Пропишем детали:

  1. Сгенерируйте новый сценарий;
  2. Ставим триггер /google;
  3. Ставим текстовый блок с кнопкой;
  4. Ставим блок с вопросом.
9.png

В параметрах блока с вопросом задайте сохранение ответа в числовое поле сценария. Если такого поля еще нет, вы можете создать его прямо в настройках блока, указав подходящий тип данных и имя поля.

10.png

Это позволит боту сохранять ответ пользователя, который затем можно использовать для фильтрации информации.

Далее добавляем HTTP-блок и вставляем в него ранее подготовленный запрос.

11.png

В приведенном примере используется переменная бота и ответ пользователя. Это достигается с помощью функции вставки переменных. Финальная версия запроса будет смотреться так:

https://functions.yandexcloud.net/xxx?doc=1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E&sheet=Sheet1&cell=A2:E&key=@{Bot:G-key}&filters={"2":{"lte":"20"},"3":{"gte":"1"}}

12.png

В настройках HTTP-блока укажите, что информация, полученная из API, будут сохраняться в переменной типа "Массив" с именем "Товары". Убедитесь, что эта переменная была своевременно создана.

13.png

Наша облачная функция возвращает ответ, состоящий из двух объектов: f и r. 

  • Объект f — содержит данные фильтрации, где каждый ключ обозначает конкретное условие; 

  • Объект r — это массив, включающий найденные товары. Каждый элемент массива r представляет собой массив с информацией о товаре.

{

  "f": { "2": { "lte": "20" }, "3": { "gte": "1" } },

  "r": [

    ["1", "Wheel", "20", "4", "3/1/2016"],

    ["2", "Door", "15", "2", "3/15/2016"]

  ]

}

 

14.png

Для текстового блока, связанного с успешным выполнением условия (зеленая стрелка), добавьте динамические кнопки. Также настройте сохранение данных в отдельное поле, чтобы фиксировать, какую кнопку выбрал пользователь. Эти данные можно использовать для дальнейшего отображения детальной информации о выбранном товаре.

15.png

Задайте заголовок кнопки как [1], чтобы отображать название товара из 2-го столбца. В качестве содержимого кнопки укажите идентификатор товара [0], соответствующий данным из 1-го столбца.

18.png

Сохраняем сценарий и проводим тестирование. Если все настроено корректно, на экране появится список найденных товаров в виде динамических кнопок.

Теперь мы можем реализовать отображение подробной информации о каждом товаре. Для этого потребуется выполнить еще один запрос к источнику данных через HTTP-блок:

  1. Создайте новый HTTP-блок и свяжите его с динамическими кнопками;

  2. В настройках HTTP-блока измените предыдущий запрос так, чтобы он возвращал данные только по одному товару;

  3. Укажите, что запрос должен получать данные начиная со столбца B и до столбца E; 

  4. Определите необходимую строку данных с помощью переменной, которая сохраняется при выборе пользователем динамической кнопки. В данном случае используйте переменную "ВыбранныйТовар".

curl 'https://functions.yandexcloud.net/xxx?doc=1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E&sheet=Sheet1&cell=B@{Flow:ВыбранныйТовар}:E@{Flow:ВыбранныйТовар}&key=@{Bot:G-key}'

В настройках блока необходимо задать сохранение полученных данных в соответствующие переменные для их последующего использования.

Если все настроено корректно, HTTP-блок вернет следующий пример ответа:

19.png

Теперь нужно вывести сохраненные переменные в удобном формате. Для этого создайте текстовый блок и используйте в нем созданные переменные, например:

20.png

Теперь, при нажатии пользователем на динамическую кнопку, он получит подробную информацию о выбранном товаре, извлеченную из Google-таблицы.

Ваш сценарий успешно настроен и готов к использованию!

Код облачной функции для текущего примера

module.exports.handler = async function (event, context) {

    const cell = event.queryStringParameters.cell;

    const key = event.queryStringParameters.key;

    const doc = event.queryStringParameters.doc;

    const sheet = event.queryStringParameters.sheet;

    const filters = event.queryStringParameters.filters ? JSON.parse(event.queryStringParameters.filters) : {};

    const isMultiVal = /:/g.test(cell);

 

    let url = 'https://sheets.googleapis.com/v4/spreadsheets';

    url += '/' + doc; // document id

    url += '/values';

    url += '/' + sheet; // sheet name

    url += '!' + cell;

    url += '?key=' + key;

 

    const response = await fetch(url);

    const body     = await response.json();

 

    if (body && body.values){

        let result = body.values;

 

        if(isMultiVal){

            let index = result.length - 1;

 

            while (index >= 0) {

                let item = result[index];

                let hasMatch = true;

                

                for (var f in filters) {

                    for(op in filters[f]) {

                        if (op == "eq" && !(filters[f][op].toString() === item[Number(f)].toString().trim())) {

                            console.log('item:', item[Number(f)], 'filter:', filters[f][op])

                            hasMatch = false;

                            // result.splice(index, 1);

                            continue;

                        }

 

                        if (op == "gt" && !(Number(item[Number(f)]) > Number(filters[f][op]))) {

                            console.log('item:', item[Number(f)], 'filter:', filters[f][op])

                            hasMatch = false;

                            continue;

                        }

 

                        if (op == "gte" && !(Number(item[Number(f)]) >= Number(filters[f][op]))) {

                            console.log('item:', item[Number(f)], 'filter:', filters[f][op])

                            hasMatch = false;

                            continue;

                        }

 

                        if (op == "lt" && !(Number(item[Number(f)]) < Number(filters[f][op]))) {

                            console.log('item:', item[Number(f)], 'filter:', filters[f][op])

                            hasMatch = false;

                            continue;

                        }

 

                        if (op == "lte" && !(Number(item[Number(f)]) <= Number(filters[f][op]))) {

                            console.log('item:', item[Number(f)], 'filter:', filters[f][op])

                            hasMatch = false;

                            continue;

                        }

                    }

                }

 

                if (!hasMatch){

                    result.splice(index, 1);

                }

                

                index -= 1;

            }

        }

        else {

            result = body.values[0][0];

        }       

 

        return {

            body: { f: filters, r: result },

        };

    } else {

         return {

            code: response.status,

            body: { message: "" } 

        };

    }

};