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

Современные конструкторы чат-ботов обещают, что для создания и настройки сценария для бота писать код не потребуется. Мы в Domino CRM тоже так говорим. И мы с коллегами из других команд в большинстве случаев правы. Но всегда есть оговорки.  Самый частый случай, когда кодить придется: если нужно встроить HTTP-запрос как элемент диалога чат-бота, чтобы получить данные из внешних сервисов.

Для большинства задач это не обязательно. В конструкторах и так есть скачиваемые шаблоны для каталогов.

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

Покажем, как это сделать в нашем сервисе на примере интеграции Google Таблицы с чат-ботом в Domino CRM (в данном случае, чат-бот будет работать в Telegram).

Чтобы интегрировать данные из Google-таблицы с Domino CRM, потребуется:  

  • Google-таблица с данными, открытая для просмотра;  

  • Ключ Google API;  

  • API-эндпоинт (для упрощения можно использовать облачные функции — пример ниже);  

  • HTTP-блок в сценарии Domino CRM.

Подготовка 

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

db348d10686ee3142ba0744c3aaffbff.png

Затем установим доступ с правами для просмотра документа для всех по ссылке. 

ae9ddd0862aaaee6cda73c4d22ab0db9.png

Далее необходимо включить Sheets API. Для этого перейдите по ссылке https://developers.google.com/workspace/guides/enable-apis#sheets-api. Затем нажмите на «Enable Sheets API», далее следуйте инструкциям.

8a58836a746de5d1cb0c9a23948d7f31.png

Затем необходимо создать ключ API, если у вас его еще нет. Для этого перейдите по ссылке https://console.cloud.google.com/apis/credentials и создайте его (Create Credentials > API key)

9f53b284d771174f1bacb70782110967.png

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

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

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

Формирование Curl запроса 

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

  • Url нашего API;  

  • ID гугл таблицы;  

  • Ключ Google API;  

  • Понимание критериев для фильтрации данных из таблицы. 

URL для API в нашем примере будет выглядеть примерно так: https://functions.yandexcloud.net/xxx

Аналогично, нам нужно задать идентификатор нашего документа. Здесь достаточно зайти в саму таблицу и посмотреть на адресную строку:

88e0ec96d611efd9370c2c7593dfa984.png

В нашем примере это будет: 1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E. У вас, конечно, будет другой пример. 

Определимся, по каким полям будем фильтровать данные. Еще раз взглянем на таблицу с названиями товаров, ценами, остатками на складе:  

f98d9eeca003dc3be99e3c45d5d5b992.png

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

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

Где:

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

  • {DOC-ID} — ID таблицы (например, 1EV0eFjfDsiW8cO8HAs2SNcV5xlltbQHrsuW_ewJxU5E);

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

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

  • {G-KEY} — ключ Google API (например, xxxxXxXXXXXXXX-XXXXXXxXXXXXxXxXxXxXxxxx);

  • {FILTERS} — параметр для фильтрации данных. Описание написания фильтра приведено ниже.

Мы хотим фильтровать данные по стоимости, поэтому наш фильтр примет следующий вид: {»2»:{«lte»:»20»}}, если необходимо получить товары стоимостью до 20 включительно.

Где:

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

  • «lte» — условие фильтрации. Поскольку мы хотим найти товары до указанной цены, используем условие «меньше или равно».

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

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

Таким образом, можно добавлять несколько фильтров одновременно.

8973db13432a20f8f83271f4ddb1479f.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. Это можно сделать на странице редактирования бота:

b228af1444563eb80b91fc433ac6de1d.png

Теперь приступим к непосредственному созданию сценария:

  1. Создаем новый сценарий;

  2. Для удобства доступа добавляем триггер /google;

  3. Добавляем текстовый блок с кнопкой;

  4. Добавляем блок с вопросом.

ede47e990e10516a48df96af233f0b91.png

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

06432cf800b98611e0724d4849c1738d.png

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

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

d3be2d1cf95ab82a50e51df7940e168a.png

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

curl

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

92aeb67d01ded49948dcc6b211378327.png

Далее, в настройках HTTP-блока, укажем, что данные, полученные из API, будут сохраняться в переменную «Товары» типа «Массив». Перед этим убедимся, что переменная была создана заранее.

c2be2e2aa0823d9d2fea8009187fae66.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»]

  ]

}

Далее в настройках HTTP-блока укажем что мы хотим сохранять полученные из API данные в переменную «Товары» типа массив. Предварительно создав ее.

c4f5706419b0ab6f3f594983f08bcb6a.png

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

4181ab878cbb3a89e5b234bed3773495.png

Укажем заголовок как [1], так как хотим выводить название товара (2-й столбец), а контент кнопки укажем как идентификатор товара [0] (1-й столбец).

0427e481ed051e812cfebb01c9c14ce0.png

Сохраним сценарий и протестируем результат.

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

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

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

В новом запросе укажем, что нам нужны данные, начиная со столбца B и до столбца E. Необходимую строку определим из переменной, которая сохраняется при выборе контактом динамической кнопки — в нашем случае это переменная «ВыбранныйТовар».

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

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

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

{  «f»: {},   «r»: [    [      «Door»,       »15»,       »2»,       »15/2/2024»    ]  ]}

Следовательно путь до наименования товара будет следующий r[0][0], для цены r[0][1], для остатков r[0][2], для даты доставки r[0][2]. Настройки блока будут выглядеть следующим образом:  

212000e6b5cfd7fe3e3e9a3f1a4bba29.png

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

21eb7294ab522620117e91efa0ddc63c.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: «» } 

        };

    }

};

© Habrahabr.ru