WhatsApp¶
В "Первой Форме" доступна интеграция с мессенджером WhatsApp. Интеграция происходит с использованием чат-бота - пользователю достаточно написать в чат ключевое слово или числовой код. Сценарии общения с чат-ботом задаются администраторами "Первой Формы".
Для ведения групповых чатов участники не обязательно должны быть пользователями "Первой Формы". Для пользователей без учетной записи сообщения будут отправлены от имени служебного пользователя (например, 1FWhatsAppBot).
ℹ️ Если в WhatsApp отсутствует бизнес-аккаунт или подписка на chat-api, работа с групповыми чатами будет возможна, только если в них добавлен чат-бот "Первой Формы" и если вручную инициировать с ним диалог.
В отличие от Telegram в WhatsApp чат-бот может самостоятельно инициировать диалог - для этого в "Первой Форме" необходимо иметь список контактов, к которым бот может обращаться. Передача файлов по WhatsApp возможна в обе стороны: как из WhatsApp в "Первую Форму", так и из "Первой Формы" в WhatsApp.
Способы интеграции с WhatsApp¶
Интеграция возможна с помощью публикации путем отправки запросов либо на бизнес-аккаунт в WhatsApp, либо на шлюз Chat API с платной подпиской (например, https://app.chat-api.com/login)).
Способ 1: Бизнес-аккаунт WhatsApp
Преимущества бизнес-аккаунта WhatsApp:
-
Более высокий уровень безопасности по сравнению с chat-api.
-
Возможность самостоятельно инициировать диалог после постановки задачи в "Первой Форме".
Недостатки бизнес-аккаунта в WhatsApp:
-
Долгая процедура оформления бизнес-аккаунта, которая не всегда завершается успешно.
-
Сложная и недостаточно подробная документация WhatsApp по интеграции.
Способ 2: Подписка на chat-api в WhatsApp
Преимущества подписки на chat-api в WhatsApp:
-
Быстрая процедура оформления.
-
Подробная документация по интеграции.
Недостатки подписки на chat-api в WhatsApp:
-
Платная подписка.
-
Более низкий уровень безопасности по сравнению с бизнес-аккаунтом.
Публикации для отправки запросов в WhatsApp полностью аналогичны публикациям для интеграции с Telegram с использованием WhatsApp Business API.
Способ 3: Сервис WAZZUP¶
При отсутствии бизнес-аккауната можно настроить интеграцию с WhatsApp при помощи внешнего сервиса WAZZUP.
Шаги по настройке:
-
Зарегистрируйтесь в Wazzup.
-
Перейдите в раздел "Каналы", выберите пункт "Добавить канал".
-
Затем выберите WhatsApp.
-
Отсканируйте QR-код.
После добавления канал появится в "Списке каналов". К Wazzup можно подключить множество номеров WhatsApp.
ℹ️ Если вы только зарегистрировали аккаунт WhatsApp или перед подключением к сервису зашли в аккаунт с другого телефона — нужно переписываться в мессенджере с телефона в течение суток. Достаточно пообщаться с 10-15 людьми.
ℹ️ Чтобы канал работал стабильно, открывайте WhatsApp на телефоне раз в пару недель. Если этого не делать, WhatsApp разлогинит вас из Wazzup и других связанных устройств. Тогда придется переподключать канал.
- Скопируйте ключ API. Он понадобится для подключения из "Первой Формы".
Перейдите в раздел "Интеграция с CRM", выберите пункт "API" и нажмите "Подключить".
- Подключитесь к Wazzup из "Первой Формы".
Подключение к Wazzup из "Первой Формы"
Отправка сообщений
Для отправки сообщений необходимо вызвать API-метод POST https://api.wazzup24.com/v3/message
В теле запроса нужно передать:
-
Ключ API в заголовке (см. п.5).
-
Параметры сообщения.
Обязательные параметры запроса представлены в таблице ниже. Со всеми необязательными параметрами можно ознакомиться в официальной документации Wazzup.
| Параметр | Тип | Описание |
|---|---|---|
| channelId | String | Id канала (uuidv4), через который нужно отправить сообщение. |
| chatType | String | Тип чата. Доступные значения: |
-whatsapp — для индивидуальных чатов в WhatsApp,
-whatsgroup — для групповых чатов в WhatsApp Пример запроса:
local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
headers['Content-Type'] = 'application/json'
local rawBody = {}
rawBody['channelId'] = chatInfo.channelId
rawBody['chatType'] = 'whatsapp'
rawBody['chatId'] = chatInfo.chatId
rawBody['text'] = text
rawBody['crmMessageId'] = tostring(postedComment)
if sendedCommentInfo.refMessageId ~= nil then
rawBody['refMessageId'] = sendedCommentInfo.refMessageId
end
local rawBodyStr = UTILS:json_encode(rawBody):gsub('<br/>', '\\n')
local options = nil
Чтобы подписаться на Webhooks, вызовите метод PATCH https://api.wazzup24.com/v3/webhooks
В теле должен быть JSON с параметрами:
| Параметр | Тип | Описание |
|---|---|---|
| webhooksUri | String | Адрес для получения webhooks. Не более 200 символов |
| subscriptions | Object | Настройки webhooks |
| subscriptions.messagesAndStatuses | Boolean | Новые сообщения и изменение статуса исходящих |
| subscriptions.contactsAndDealsCreation | Boolean | Необходимость создания нового контакта или сделки |
| subscriptions.channelsUpdates | Boolean | Изменение статуса канала |
| subscriptions.tepmplateStatus | Boolean | Изменение статуса модерации шаблона WABA |
При подключении на указанный URL будет отправлен тестовый POST-запрос с телом {test: true }. В ответ сервер должен вернуть 200 при успешном подключении webhooks. Иначе вернется ошибка: "Webhooks request not valid. Response status must be 200".
Примеры смарт-скриптов
Пример отправки ответа по цифре:
function postComment (author, comment, taskid, recip, isQuestion)
local res = SMART:execute_action('PostComment', taskid, 'task', {
CommentAuthor = author,
CommentText = comment,
Task = taskid,
Recipients = recip,
RecipientGroups = nil,
ForcedEmail = false,
CommentSMS = false,
NoSubscription = false,
TextAsHTML = false,
MarkAsQuestion = isQuestion,
RecipCopies = nil,
CommentType = 3,
CommentVisibleOnlyToRealRecipients = false,
SilentComment = false,
RealUserId = nil
})
return res
end
--получаем контент входящего комментария
local commentContent = SQL:scalar(
[
select c.Content
from Comments c (nolock)
where c.CommentID = @commentid
]],
{
commentid = EVENTPARAMS['CommentId']
}
)
--var_dump(commentInfo)
local text = ''
if commentContent == '1' then
text = 'Если вы хотите сделать расчет заказа - заполните форму https://www.grandline.ru/services/zakazat-raschet/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
elseif commentContent == '2' then
text = 'Если вы хотите узнать стоимость и наличие товара - посмотрите в нашем интернет-магазине https://www.grandline.ru/katalog/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
elseif commentContent == '3' then
text = 'Если вы хотите узнать адреса точек продажи - прейдите по ссылке https://www.grandline.ru/gde-kupit/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
elseif commentContent == '4' then
text = 'Если у вас вопрос по документам - перейдите по ссылке https://www.grandline.ru/dokumenty/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
end
local postedComment = postComment(2110, text, CONTEXT["Id"], {2349}, false)
--получаем инфо об отправленном комментарии
local sendedCommentInfo = SQL:query_one(
[[
select
c.CommentID,
u.DisplayName,
--c.Content,
iif(u.userid <> 2110, '[' + u.DisplayName + ' ', '') + c.Content as message,
c.InReplyToCommentID,
lg.messageId as refMessageId
from Comments c (nolock)
join Users u (nolock) on u.UserID = c.UserID
left join cm_whatsapp_webhooks_log lg (nolock)
on lg.commentId = c.InReplyToCommentID
where c.CommentID = @commentid
]],
{
commentid = postedComment
}
)
--получаем инфо по задаче чата
local chatInfo = SQL:query_one(
[
select
kl.extparam177value as surname,
kl.extparam171value as name,
t.extparam888value as channelId,
t.extparam889value as chatId
from tasksinsubcat117denormalized t (nolock)
join tasksinsubcat49denormalized kl (nolock)
on kl.taskid = t.extparam580nativevalue
where t.taskid = @contextid
]],
{
contextid = CONTEXT["Id"]
}
)
--var_dump(chatInfo)
--параметры запроса
local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
headers['Content-Type'] = 'application/json'
local rawBody = {}
rawBody['channelId'] = chatInfo.channelId
rawBody['chatType'] = 'whatsapp'
rawBody['chatId'] = chatInfo.chatId
rawBody['text'] = text
rawBody['crmMessageId'] = tostring(postedComment)
if sendedCommentInfo.refMessageId ~= nil then
rawBody['refMessageId'] = sendedCommentInfo.refMessageId
end
local rawBodyStr = UTILS:json_encode(rawBody):gsub('<br/>', '\\n')
local options = nil
-- var_dump(rawBodyStr)
--логируем в базу запрос
local insertedId = SQL:scalar(
[[
insert into cm_whatsapp_webhooks_log
select
getdate(), --date
1, --direction
null, --webhook
@message, --messages
null, --statuses
null, --messageId
null, --status
null, --isEcho
@commentId --commentId
select IDENT_CURRENT('cm_whatsapp_webhooks_log')
]]
{
commentId = postedComment,
message = rawBody
}
)
local response = HTTP:send_http_request("POST", url, parameters, headers, rawBodyStr, options)
local responseMessageId = UTILS:json_decode(response["HttpResponse"]["ResponseContent"])
local postedMessageId = responseMessageId["messageId"]
--если ID есть, то обновляем комментарии
if postedMessageId ~= nil then
SQL:query(
'update cm_whatsapp_webhooks_log set messageId = @postedMessageId where commentId = @commentId',
{commentId = postedComment, postedMessageId = postedMessageId}
)
end
--получаем инфо о комментарии
local commentInfo = SQL:query_one(
[[
select
c.CommentID,
u.DisplayName,
--c.Content,
iif(u.userid <> 2110, '[' + u.DisplayName + ' ', '') + c.Content as message,
c.InReplyToCommentID,
lg.messageId as refMessageId
from Comments c (nolock)
join Users u (nolock) on u.UserID = c.UserID
left join cm_whatsapp_webhooks_log lg (nolock)
on lg.commentId = c.InReplyToCommentID
where c.CommentID = @commentid
]],
{
commentid = EVENTPARAMS['CommentId']
}
)
--получаем инфо по задаче чата
local chatInfo = SQL:query_one(
[
select
kl.extparam177value as surname,
kl.extparam171value as name,
t.extparam888value as channelId,
t.extparam889value as chatId
from tasksinsubcat117denormalized t (nolock)
join tasksinsubcat49denormalized kl (nolock)
on kl.taskid = t.extparam580nativevalue
where t.taskid = @contextid
]],
{
contextid = CONTEXT["Id"]
}
)
-- var_dump(chatInfo)
--параметры запроса
local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
headers['Content-Type'] = 'application/json'
local rawBody = {}
rawBody['channelId'] = chatInfo.channelId
rawBody['chatType'] = 'whatsapp'
rawBody['chatId'] = chatInfo.chatId
rawBody['text'] = commentInfo.message
rawBody['crmMessageId'] = tostring(EVENTPARAMS['CommentId'])
if commentInfo.refMessageId ~= nil then
rawBody['refMessageId'] = commentInfo.refMessageId
end
local rawBodyStr = UTILS:json_encode(rawBody):gsub('<br/>', '\\n')
local options = nil
--логируем в базу запрос
local insertedId = SQL:scalar(
[[
insert into cm_whatsapp_webhooks_log
select
getdate(), --date
1, --direction
null, --webhook
@message, --messages
null, --statuses
null, --messageId
null, --status
null, --isEcho
@commentId --commentId
select IDENT_CURRENT('cm_whatsapp_webhooks_log')
]]
{
commentId = EVENTPARAMS['CommentId'],
message = rawBody
}
)
local response = HTTP:send_http_request("POST", url, parameters, headers, rawBodyStr, options)
--var_dump(response)
--временное логирование при отправке
SQL:query(
'update cm_whatsapp_webhooks_log set statuses = @response where commentId = @commentId',
{commentId = EVENTPARAMS['CommentId', response = response}
)
local responseMessageId = UTILS:json_decode(response["HttpResponse"]["ResponseContent"])
local postedMessageId = responseMessageId["messageId"]
--если ID есть, то обновляем комментарии
if postedMessageId ~= nil then
SQL:query(
'update cm_whatsapp_webhooks_log set messageId = @postedMessageId where commentId = @commentId',
{commentId = EVENTPARAMS['CommentId', postedMessageId = postedMessageId}
)
end
CTX = CONTEXT["Id"]
FILEID = EVENTPARAMS["FileId"]["Id"]
local fileInfo = SQL:scalar(
[
select
--fs.fileid,
replace(convert(nvarchar(36), fs.GUID), '-', '')
from FileStorageFileToTaskLinks tl (nolock)
join FileStorageFiles fs (nolock) on fs.FileID = tl.FileId
join Tasks t (nolock) on t.TaskID = tl.TaskId
where t.SubcatID = 117
and fs.fileid = @fileid
]],
{
fileid = FILEID
}
)
-- var_dump(fileInfo)
local chatInfo = SQL:query_one(
[
select
kl.extparam177value as surname,
kl.extparam171value as name,
t.extparam888value as channelId,
t.extparam889value as chatId
from tasksinsubcat117denormalized t (nolock)
join tasksinsubcat49denormalized kl (nolock)
on kl.taskid = t.extparam580nativevalue
where t.taskid = @contextid
]],
{
contextid = CTX
}
)
-- var_dump(chatInfo)
local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
headers['Content-Type'] = 'application/json'
local rawBody = {}
rawBody['channelId'] = chatInfo.channelId
rawBody['chatType'] = 'whatsapp'
rawBody['chatId'] = chatInfo.chatId
rawBody['contentUri'] = 'https://f1.grandline.ru/app/v1.2/api/publications/action/sync_whatsapp_getattachment?file=' .. fileInfo
local rawBodyStr = UTILS:json_encode(rawBody)
local options = nil
local response = HTTP:send_http_request("POST", url, parameters, headers, rawBodyStr, options)
local responseMessageId = UTILS:json_decode(response["HttpResponse"]["ResponseContent"])
local postedMessageId = responseMessageId["messageId"]
SYS_ROBOT = 3
WA_ROBOT = 2349 --пользователь whatsapp_robot
WA_SUBCAT = 117 --категория чатов
CONTACT_SUBCAT = 49 --категория контактных лиц
PARAMS = UTILS:json_decode(EVENTPARAMS["PublishedObjectParameters"]); --json входящих параметров
function createTask (owner, subcat, tasktext, performers, extparams)
local res = SMART:execute_action("CreateTask", nil, "task",
{
Owner = owner,
Subcat = subcat,
TaskText = tasktext,
CreateLink = false,
CreateSubtask = false,
Performers = performers,
DueTime = nil,
TaskStartTime = nil,
ExtParams = extparams,
NewTaskCopySubscribers = false,
CreateCopyFiles = false,
CopyParentText = false,
Priority = 1,
Notify = nil,
UsersToSubscribe = nil,
CreateCopiesForEachPerformer = false,
LinkFiles = nil,
AssignLetterWithTask = nil,
Confidentiality = nil,
LinkAsUser = nil
}
)
return res
end
function postComment (comment, taskid, recip, isQuestion)
local res = SMART:execute_action('PostComment', taskid, 'task', {
CommentAuthor = WA_ROBOT,
CommentText = comment,
Task = taskid,
Recipients = recip,
RecipientGroups = nil,
ForcedEmail = false,
CommentSMS = false,
NoSubscription = false,
TextAsHTML = false,
MarkAsQuestion = isQuestion,
RecipCopies = nil,
CommentType = 3,
CommentVisibleOnlyToRealRecipients = false,
SilentComment = false,
RealUserId = nil
})
return res
end
--postComment (PARAMS, 925710, nil, false)
--[=[
SMART:execute_action('PostComment', 925710, 'task', {
CommentAuthor = 3,
CommentText = EVENTPARAMS["PublishedObjectParameters"],
Task = 925710,
Recipients = nil,
RecipientGroups = nil,
ForcedEmail = false,
CommentSMS = false,
NoSubscription = false,
TextAsHTML = false,
MarkAsQuestion = false,
RecipCopies = nil,
CommentType = 3,
CommentVisibleOnlyToRealRecipients = false,
SilentComment = false,
RealUserId = nil
})
=]]
-- Обработка входящего вебхука с ошибкой
if PARAMS["requestBody"]["messages"] ~= nil and PARAMS["requestBody"]["messages"][1]["status"] == 'error' then]
-- Логирование в базу
INSERTED_ID = SQL:scalar(
[[
insert into cm_whatsapp_webhooks_log
select
getdate(), --date
0, --direction - входящее
@webhook, --webhook
JSON_QUERY(@webhook, '$.requestBody.messages'), --messages
JSON_QUERY(@webhook, '$.requestBody.statuses'), --statuses
isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].messageId'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].messageId')), --messageId
isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].status'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].status')), --status
cast(JSON_VALUE(@webhook, '$.requestBody.messages[0].isEcho') as bit), --isEcho
null --commentId
--select @@identity
select IDENT_CURRENT('cm_whatsapp_webhooks_log')
]],
{webhook = PARAMS}
)
-- Получаем задачу чата
local chatTask = SQL:scalar(
[
select t.taskid
from tasksinsubcat117denormalized t (nolock)
join Comments c (nolock)
on c.taskid = t.taskid
join cm_whatsapp_webhooks_log lg (nolock)
on lg.commentid = c.commentid
where lg.messageid = @messageid
and t.extparam888value = @channelId
and t.extparam889value = @chatid
and t.isclosed = 0
]],
{
messageid = PARAMS["requestBody"]["messages"][1]["messageId"],
chatid = PARAMS["requestBody"]["messages"][1]["chatId"],
channelId = PARAMS["requestBody"]["messages"][1]["channelId"]
}
)
local errText = 'Возникла непредвиденная ошибка при получении данных из WhatsApp.'
if PARAMS["requestBody"]["messages"][1]["error"]["error"] == 'BAD_CONTACT' then
--local errText = PARAMS["requestBody"]["messages"][1]["error"]["error"] .. '. ' .. PARAMS["requestBody"]["messages"][1]["error"]'description']{.f_CodeExample style="color: #008000;"}
errText = 'WhatsApp на данном номере не зарегистрирован.'
end
--Комментарий об ошибке
postComment (errText, chatTask, nil, false)
--Отклоняем задачу чата
SMART:execute_action('SetState', chatTask, 'task', {
Task = chatTask,
Steper = WA_ROBOT,
State = 2,
ThrowErrorOnNoStep = true,
Reason = nil,
BreakTaskLinkForAll = false
})
end
-- Обработка входящего вебхука со статусами
if PARAMS["requestBody"]["statuses"] ~= nil then]
-- Логирование в базу
INSERTED_ID = SQL:scalar(
[[
insert into cm_whatsapp_webhooks_log
select
getdate(), --date
0, --direction - входящее
@webhook, --webhook
JSON_QUERY(@webhook, '$.requestBody.messages'), --messages
JSON_QUERY(@webhook, '$.requestBody.statuses'), --statuses
isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].messageId'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].messageId')), --messageId
isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].status'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].status')), --status
cast(JSON_VALUE(@webhook, '$.requestBody.messages[0].isEcho') as bit), --isEcho
null --commentId
--select @@identity
select IDENT_CURRENT('cm_whatsapp_webhooks_log')
]],
{webhook = PARAMS}
)
if PARAMS["requestBody"]["statuses"][1]["status"] == 'read' then
local commentid = SQL:scalar(
[[
select top 1 commentId
from cm_whatsapp_webhooks_log
where messageId = @messageId
and commentid is not null
]]
{messageid = PARAMS["requestBody"]["statuses"][1"messageId"}
)
if commentid ~= nil then
SQL:query(
[[
update commentrecipients
set isunread = 0, readdate = getdate()
where commentid = @commentid
]]
{commentid = commentid}
)
end
end
end
-- Обработка входящего вебхука
if PARAMS["requestBody"]["messages"] ~= nil and PARAMS["requestBody"]["messages"][1]["status"] == 'inbound' then]
-- Логирование в базу
INSERTED_ID = SQL:scalar(
[[
insert into cm_whatsapp_webhooks_log
select
getdate(), --date
0, --direction - входящее
@webhook, --webhook
JSON_QUERY(@webhook, '$.requestBody.messages'), --messages
JSON_QUERY(@webhook, '$.requestBody.statuses'), --statuses
isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].messageId'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].messageId')), --messageId
isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].status'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].status')), --status
cast(JSON_VALUE(@webhook, '$.requestBody.messages[0].isEcho') as bit), --isEcho
null --commentId
--select @@identity
select IDENT_CURRENT('cm_whatsapp_webhooks_log')
]],
{webhook = PARAMS}
)
-- INSERTED_ID = 701
-- Получение сообщений из вебхука
MESSAGES = PARAMS["requestBody"]["messages"]
if MESSAGES ~= nil then
CHANNEL_ID = MESSAGES[1].channelId
CONTACT_NAME = MESSAGES[1].contact.name
CHAT_ID = MESSAGES[1].chatId
-- Проверка контактного лица по телефону
CONTACT_TASK_ID = SQL:scalar(
[
select t49.taskid
from tasksinsubcat47denormalized t47 (nolock)
join extparamtable565denormalized tb (nolock)
on tb.Column58NativeValue = t47.taskid
join tasksinsubcat49denormalized t49 (nolock)
on t49.taskid = tb.taskid
where t47.extparam73value = @phone
and isnull(t49.extparam94value, '') <> ''
--select tb.taskid
--from extparamtable1141denormalized tb (nolock)
--where tb.column110value = @phone
]],
{phone = CHAT_ID}
)
if CONTACT_TASK_ID == nil then
-- Создание новое контактное лицо
local contactExtParams = {}
contactExtParams[1] = '{"ExtParamId": 171, "FixedValue": "' .. CONTACT_NAME .. '"}'
contactExtParams[2] = '{"ExtParamId": 613, "FixedValue": "' .. CHAT_ID .. '"}'
--contactExtParams[3 = '+{111:{"First":"' + ДП1 + '"}, 222:{"First":"'+ ВСтроку(ДП2) +'"} }']{.f_CodeExample style="color: #008000;"}
local createdContact = createTask (WA_ROBOT, CONTACT_SUBCAT, "", {}, contactExtParams)
CONTACT_TASK_ID = createdContact[0]
end -- Конец создания контактного лица
-- var_dump(CONTACT_TASK_ID)
-- Получение данных контактного лица по телефону
CONTACT_INFO = SQL:query_one(
[
select
t47.taskid as identTaskid,
t49.taskid as contactTaskid,
-- t49.extparam177value as contactLastname,
-- t49.extparam171value as contactName,
iif(t49.extparam177value is null or t49.extparam177value = '', t49.extparam171value, t49.extparam177value + ' ' + t49.extparam171value) as contactName,
t49.extparam271value as contactResponsible,
t49.extparam271nativevalue as contactResponsibleTaskId,
t30.extparam367nativevalue as contactResponsibleId
from tasksinsubcat47denormalized t47 (nolock)
join extparamtable565denormalized tb (nolock)
on tb.Column58NativeValue = t47.taskid
join tasksinsubcat49denormalized t49 (nolock)
on t49.taskid = tb.taskid
left join tasksinsubcat30denormalized t30 (nolock)
on t30.taskid = t49.extparam271nativevalue
where t47.extparam73value = @phone
]],
{phone = CHAT_ID}
)
-- var_dump(CONTACT_INFO)
-- Получаем задачу чата, если она существует
local chatTaskInfo = SQL:query_one(
[
select t.taskid, t.stateid
from tasksinsubcat117denormalized t (nolock)
where t.extparam580nativevalue = @contact
and t.extparam888value = @channelId
and t.extparam889value = @chatid
and t.isclosed = 0
]],
{contact = CONTACT_TASK_ID, chatid = CHAT_ID, channelId = CHANNEL_ID}
)
-- CHAT_TASK_ID = chatTaskInfo.taskid
-- CHAT_STATE_ID = chatTaskInfo.stateid
-- var_dump(CHAT_TASK_ID)
-- var_dump(CHAT_STATE_ID)
-- Если задачи чата нет, то создаем ее
if chatTaskInfo == nil then
local chatTaskText = CONTACT_INFO.contactName .. ' (' .. CHAT_ID .. ')'
local chatPerformers = {}
chatPerformers[1] = WA_ROBOT
local chatExtParams = {}
chatExtParams[1] = '{"ExtParamId": 580, "FixedValue": ' .. CONTACT_TASK_ID .. '}'
chatExtParams[2] = '{"ExtParamId": 888, "FixedValue": "' .. CHANNEL_ID .. '"}'
chatExtParams[3] = '{"ExtParamId": 889, "FixedValue": "' .. CHAT_ID .. '"}'
if CONTACT_INFO.contactResponsibleTaskId ~= nil then
chatExtParams[4] = '{"ExtParamId": 271, "FixedValue": ' .. CONTACT_INFO.contactResponsibleTaskId .. '}'
end
local createdTask = createTask (SYS_ROBOT, WA_SUBCAT, chatTaskText, chatPerformers, chatExtParams)
CHAT_TASK_ID = createdTask[0]
CHAT_STATE_ID = 1
else
CHAT_TASK_ID = chatTaskInfo.taskid
CHAT_STATE_ID = chatTaskInfo.stateid
end -- Конец создания задачи чата
-- адресат комментария и вопрос в зависимости от статуса
recipients = {}
isQuestion = true
if CHAT_STATE_ID ~= 99 then
table.insert(recipients, WA_ROBOT)
isQuestion = false
else
if CONTACT_INFO.contactResponsibleId ~= nil then
table.insert(recipients, CONTACT_INFO.contactResponsibleId)
else
local usersInGroup928 = SQL:query('select ug.userid from usergroups ug (nolock) where ug.groupid = 928', {})
for k, v in pairs(usersInGroup928) do
table.insert(recipients, v.userid)
end
end
isQuestion = false
end
-- Обработка сообщений
for num, message in pairs(MESSAGES) do
-- на всякий случай проверим на скобки
-- local msgchunked = message:gsub('^%[', ''):gsub('%$', '')]{.f_CodeExample style="color: #008000;"}
-- local msg = UTILS:json_decode(msgchunked)
local isEcho = message.isEcho
local msgType = message.type
local msgText = message.text
local msgAuthorName = message.authorName
local quotedMsgId = nil
if message.quotedMessage ~= nil then
quotedMsgId = message.quotedMessage.messageId
end
local msgContentUri = nil
local msgFileName = nil
if message.contentUri ~= nil then
msgContentUri = message.contentUri
msgFileName = message.contentUri:match('%=(.+%.%w+)$')
-- ext2 = str:match('%.(%w+)$')
end
-- если сообщение типа текст
if msgType == 'text' and msgText ~= '' and msgText ~= nil then
if CHAT_STATE_ID == 1 then
postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
SMART:execute_action('MakeStep', CHAT_TASK_ID, 'task', {
Task = CHAT_TASK_ID,
Steper = SYS_ROBOT,
Step = 926,
Reason = nil,
DoNotWriteComment = false
})
elseif CHAT_STATE_ID == 98 and msgText ~= '1' and msgText ~= '2' and msgText ~= '3' and msgText ~= '4' then
SMART:execute_action('MakeStep', CHAT_TASK_ID, 'task', {
Task = CHAT_TASK_ID,
Steper = WA_ROBOT,
Step = 927,
Reason = nil,
DoNotWriteComment = false
})
--CHAT_STATE_ID = 99
recipients = {}
if CONTACT_INFO.contactResponsibleId ~= nil then
table.insert(recipients, CONTACT_INFO.contactResponsibleId)
else
local usersInGroup928 = SQL:query('select ug.userid from usergroups ug (nolock) where ug.groupid = 928', {})
for k, v in pairs(usersInGroup928) do
table.insert(recipients, v.userid)
end
end
isQuestion = true
postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
SMART:execute_action('PostComment', CHAT_TASK_ID, 'task', {
CommentAuthor = 2110,
CommentText = 'Ваш вопрос направлен менеджеру',
Task = CHAT_TASK_ID,
Recipients = {2349},
RecipientGroups = nil,
ForcedEmail = false,
CommentSMS = false,
NoSubscription = false,
TextAsHTML = false,
MarkAsQuestion = false,
RecipCopies = nil,
CommentType = 3,
CommentVisibleOnlyToRealRecipients = false,
SilentComment = false,
RealUserId = nil
})
else
postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
end
--msgText = '[' .. contactInfo.contactName .. ' ' .. msgText]{.f_CodeExample style="color: #008000;"}
--postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
--SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
end
-- если сообщение типа image, audio, video, document
if msgType == 'image' or msgType == 'audio' or msgType == 'video' or msgType == 'document' then
--скачивание файла
local download = SMART:execute_action('DownloadFile', CHAT_TASK_ID, 'task', {
Task = CHAT_TASK_ID,
UploadingUserName = WA_ROBOT,
TargetExternalParameter = nil,
FileLink = msgContentUri,
Comment = nil,
DownloadManyFiles = false,
FileMask = nil,
FileCreateDateFrom = nil,
FileCreateDateTo = nil,
FileName = msgFileName,
HttpMethod = nil,
HttpParameters = {},
HttpHeaders = {},
HttpRequestBody = nil
})
local fileId = UTILS:json_decode(download)["Id"]
postedComment = SQL:scalar('select CommentId from FileStorageFileToCommentLinks (nolock) where FileId = @fileid and Taskid = @taskid', {fileid = fileId, taskid = CHAT_TASK_ID})
if msgText ~= nil and msgText ~= '' then
--msgText = '[' .. contactInfo.contactName .. ' ' .. msgText]{.f_CodeExample style="color: #008000;"}
-- postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
-- SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
SQL:query('update comments set content = @content, typeid = 3, eventid = null where commentid = @commentid', {commentid = postedComment, content = msgText})
-- SQL:query('update cm_whatsapp_webhooks_log set commentId = @commentid where id = @logId', {commentid = postedComment, logId = INSERTED_ID})
end
SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
end --msgType == 'image'
-- Если есть цитированное сообщение
if quotedMsgId ~= nil then
--ищем ID комментария в логе
local replyComment = SQL:scalar('select commentId from cm_whatsapp_webhooks_log (nolock) where messageId = @quotedMessageId', {quotedMessageId = quotedMsgId})
--если ID есть, то обновляем комментарии
if replyComment ~= nil then
SQL:query('update Comments set InReplyToCommentID = @replyComment where CommentID = @postedComment', {postedComment = postedComment, replyComment = replyComment})
end
end -- Конец цитирования
end -- Конец цикла по сообщениям
end -- Конец if MESSAGES
end -- Конец обработки входящего вебхука
Предварительная настройка
Создается задача в справочнике контактных лиц: имя и телефон должны совпадать с данными в WhatsApp. Затем создается задача в категории "Общение WhatsApp" с параметрами:
-
Контактное лицо -- тип Lookup поле, категория-источник "Контактные лица".
-
Телефон -- тип "Текст", телефон нового контакта.
-
ID чата -- тип "Текст", ID чата из Wazzup (соответствует номеру телефона). Заполняется автоматически.
-
ID канала -- тип "Текст", ID канала из Wazzup (для всех сообщений с одного канала одинаковый). Заполняется автоматически.
Если в "Первой Форме" нет активной задачи с этим контактом в категории "Общение WhatsApp" создастся новый чат и задача перейдет в статус "Диалог с ботом", а пользователю отправляется автоматическое сообщение. Если есть активный чат, то в нем отобразится комментарий.
Общение с ботом¶
Пока задача чата находится в статусе "Диалог с ботом", на сообщения с кодом "1", "2", "3" или "4" будет возвращаться соответствующий запросу автоматический ответ.
Если пользователь отправит сообщение с иным текстом, задача чата будет переведена на другой статус в соответствии с бизнес-процессом.
Пример: пользователь отправляет произвольный текст, после чего задача автоматически переходит в статус "Диалог с менеджером". Сообщение клиента будет адресовано ответственному сотруднику по контактному лицу, если ответственный сотрудник по контактному лицу не назначен, то на группу контакт-центра.
Сообщения клиента из WhatsApp отображаются в комментариях задачи от имени пользователя WhatsAppRobot.
В WhatsApp клиента сообщения из "Перфой Формы" приходят с указанием ФИО менеджера, написавшего комментарий.
Ответ на входящее сообщение¶
В комментариях к задаче в "Первой Фоме" выбирается необходимое сообщение от пользователя WhatsAppRobot и на него отправляется ответ.
В WhatsApp комментарий отображается как ответ на сообщение.
Аналогично в обратную сторону: в WhatsApp сотрудник отвечает на сообщение, а в "Первой Форме" это отображается как ответ на комментарий.
Вложения¶
Приложенный файл или картинка в WhatsApp после отправки отображается в "Первой Форме" как вложение к комментарию.
Аналогично в обратную сторону: вложенный в комментарий к задаче "Общение WhatsApp" файл или картинка после отправки отображаются в WhatsApp.