
16 декабря, 2024
Интеграция чат-бота от Domino CRM с таблицей в Google за счет HTTP-запроса
От современных конструкторов чат-ботов нередко можно услышать: мы работаем как платформа, которая вообще не требует написания кода. Domino CRM позиционирует себя именно так — и зачастую вы действительно можете создать и настроить чат-бота без необходимости программировать.
Но есть исключения.
Одно из таких исключений — использование HTTP-запросов для получения данных из внешних сервисов. Эта функция становится актуальной, если вам требуется интегрировать большие массивы данных или поддерживать их актуальность в реальном времени.
Когда нужна интеграция?
HTTP-запросы становятся незаменимыми в двух ситуациях:
- Фильтрация больших массивов информации. Например, в сфере eCommerce или гостиничного бизнеса, где объемы информации слишком велики для наших встроенных шаблонов-каталогов;
- Частые обновления информации. Если данные меняются регулярно, интеграция с внешними сервисами позволяет избежать постоянной ручной правки.
Прямое подключение через HTTP-запрос решает эти проблемы, упрощая работу.
Как настроить интеграцию на платформе Domino CRM?
Для примера рассмотрим процесс подключения Google-таблицы к чат-боту, работающему на базе Domino CRM. Для этой цели вам понадобятся:
Google-таблица с информацией. При этом сама таблица должна быть обязательно открыта для просмотра. То есть вам надо убедиться, доступ к источнику данных, вашей таблице, выставлен на “Просмотр”. Без этого никак;
Ключ Google API. Он необходим для выполнения запросов к таблице;
API-эндпоинт. Чтобы упростить процесс, можно воспользоваться облачными функциями;
HTTP-блок в Domino CRM. Этот элемент добавляется в сценарий вашего бота и отвечает за выполнение запросов.
Нижеследующие шаги позволят вам интегрировать внешние данные в диалоги вашего чат-бота, сохраняя актуальность информации и оптимизируя рабочие процессы.
Подготовка
Для демонстрации работы с HTTP-блоком сгенерируем небольшую тестовую базу. Помним, что доступ к ней обязан быть открыт.
Логично, что нам потребуется знать номер строки для каждой записи. В первом столбце таблицы добавим уникальный идентификатор, который будет совпадать с номером строки.

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

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

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

Затем откроется модальное окно, где будет отображен ваш новый ключ. Он понадобится нам на следующих этапах. Пример ключа:
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.
Также потребуется указать идентификатор документа. Его можно найти в адресной строке открытой таблицы.

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

Представим, что мы желаем дать пользователю чат-бота рычаг для фильтровки данных по самой высокой цене. Для этого нами была сгенерирована облачная функция, доступная через вот такой 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"}}.
Иными словами, вы можете одновременно применять несколько условий фильтрации.

Наша функция позволяет фильтровать информацию по столбцам, используя следующие операторы:
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 в параметрах бота. Это делается в разделе для редактирования бота.

Пропишем детали:
- Сгенерируйте новый сценарий;
- Ставим триггер /google;
- Ставим текстовый блок с кнопкой;
- Ставим блок с вопросом.

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

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

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

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

Наша облачная функция возвращает ответ, состоящий из двух объектов: 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"]
]
}

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

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

Сохраняем сценарий и проводим тестирование. Если все настроено корректно, на экране появится список найденных товаров в виде динамических кнопок.
Теперь мы можем реализовать отображение подробной информации о каждом товаре. Для этого потребуется выполнить еще один запрос к источнику данных через 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-блок вернет следующий пример ответа:

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

Теперь, при нажатии пользователем на динамическую кнопку, он получит подробную информацию о выбранном товаре, извлеченную из 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: "" }
};
}
};