Рубрики
Битрикс 24 кейсы

Разработка кастомного коннектора для открытых линий Битрикс24 — из опыта

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

В данном посте я расскажу о том, как мы реализовали кастомный коннектор для интеграции WhatsApp в открытые линии Битрикс24, используя API сервиса Chat2desk.

Как мы уже писали в предыдущих постах, мы отказались от использования приложения, написанного под облачные версии Битрикс в коробке. Главная причина – для коробки можно сделать интеграцию на более глубоком уровне, из-за доступности API Bitrix Framework. В отличии от облачной версии – в коробке мы можем создавать свои собственные коннекторы для открытых линий. Что позволит интегрировать мессенджеры через механизм открытых линий максимально близко к тому, как это делают «стандартные» коннекторы.

Существует официальный курс

https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=48&LESSON_ID=2184&LESSON_PATH=3918.4635.8395.2184 ).

по разработке кастомных коннекторов для Битрикс24. Однако не все технические моменты освещены в нем подробно.

Начинаем писать код с создания нового модуля под Битрикс Маркетплейс.

Снимок экрана 2018-09-12 в 15.38.46

Рис 1. Модуль в списке Доступных решений.

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

В библиотеке модуля реализовываем обработчик события OnImConnectorBuildList. Привязку обработчика к событию производим при установке модуля. Соответственно при удалении модуля – привязку снимаем.

Снимок экрана 2018-09-12 в 15.39.04

Рис 2. Коннектор с списке подключенных каналов открытой линии

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

Снимок экрана 2018-09-12 в 15.14.44

Рис 3. Форма настроек коннектора

В нашем случае нам необходимо хранить токен для доступа к агрегатору. Здесь сталкиваемся с первой проблемой. Стандартный механизм не предлагает нам способа хранения настроек. Вернее, компонент imconnector.baseconnector нам предлагает хранить настройки в именованном кэше. У нас этот кэш очищался раз в сутки, соответственно настройки сбрасывались. Поэтому мы таким способом не воспользовались и сохранили настройки, воспользовавшись COption::SetOptionString().

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

Начнем с приема сообщений в линию. Для этого нам понадобиться иметь точку входа – доступный извне скрипт, который будет принимать вебхуки от агрегатора, подключать наш модуль и передавать ему данные.

В самом общем виде код приема сообщений выглядит так:

CModule::IncludeModule("imconnector");

CModule::IncludeModule("im");

//message data must be array of arrays

$message_unique_id = $data['message_id'];

$message_data = array

(

'user' => array('id'=>$data['client_id'], 'name'=>$data['client_name']),

'message' => array('id'=>$message_unique_id, 'date'=>'', 'text'=>$data['message_text']),

'chat' => array('id'=>$data['client_id']."_".$transport, 'name'=>'chat_'.$data['client_id']."_".$transport, 'url'=>''),

);

\Bitrix\ImConnector\CustomConnectors::sendMessages(self::CONNECTOR_ID, $line,array($message_data));

Как видите, за отправку сообщений в линию отвечает функция \Bitrix\ImConnector\CustomConnectors::sendMessages. При ее выполнении Битрикс сам создаст новый чат (если нужно), пригласит туда ответственного, опубликует в чате сообщение.

Здесь всплывает следующий подводный камень. Как видно – в массиве message_data в поле user можно задать name (имя, с которым пользователь мессенджера будет добавлен в чат). При его задании – Битрикс будет проводить поиск по базе CRM и пытаться привязать диалог к сущностям CRM. Вроде бы все ничего, но делает он это по имени, которое может быть совсем не уникально. Соответственно диалог может прикрепиться не туда, куда вы можете ожидать. В тоже время он не ищет по номерам телефонов, email и значениям полей мессенджеров, что странно. Обычно внешняя система может прислать вам номер телефона, или id мессенджера, который зачастую является email. Намного больше смысла искать по ним.

Собственно сама привязка заключается в том, что Битрикс добавляет открытую линию в контакты сущности, и создает дела у этой сущности.

В нашем случае, было необходимо привязывать диалог к сделкe в CRM по номеру телефона. Для этого мы написали функцию, которая ищет номера телефонов в CRM сущностях (контактах, лидах, компаниях) и возвращает список сделок связанных с найденными сущностями, к которым нужно осуществить привязку. Тут важно учитывать то, что телефоны могут храниться в разных форматах, поэтому поиск нужно вести с помощью Bitrix\Crm\Integrity\DuplicateCommunicationCriterion

Здесь снова возникает проблема. Дело в том, что \Bitrix\ImConnector\CustomConnectors::sendMessages не возвращает данных о том, что за сообщение она создала, и самое главное — в какой чат. Она возвращает только id пользователя который написал сообщение. А не зная идентификатор чата – мы не сможем его привязать к CRM. Получить id чата можно через само сообщение. В \Bitrix\ImConnector\CustomConnectors::sendMessages мы передавали уникальный идентификатор из внешней системы. По нему можно получить информацию о сообщение, в том числе и чат:

$res=\Bitrix\ImConnector\CustomConnectors::sendMessages(self::CONNECTOR_ID, $line,array($message_data));
$sender_id = $res->getData();
$sender_id = $sender_id['RESULT'][0]['user']; //получим ид пользователя(внешнего) отправившего сообщение.
$message_id = CIMMessageParam::GetMessageIdByParam("CONNECTOR_MID", $message_unique_id)[0]; //получим идентификатор сообщения
$message_res = new CIMMessage($sender_id);
$message = $message_res->GetMessage($message_id); //получим информацию о сообщении
$chat_id = $message['CHAT_ID'];

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

Получить идентификатор сессии можно таким образом:

$chat = new \Bitrix\ImOpenLines\Chat(ид чата);
$session_res = Bitrix\ImOpenLines\Model\SessionTable::getList(array(
'select' => Array('ID', 'CHAT_ID'),
'filter' => array(
'=USER_CODE' => $chat->chat['ENTITY_ID'],
'=CLOSED' => 'N'
)
));

$session = $session_res->fetch(); //идентификатор лежит в $session['ID']

В функцию \CCrmActivity::Add нужно передать параметр ORIGIN_ID со значением вида  IMOL_#ид_сессии#


Наконец-то, мы создаем «Дело» на каждый новый чат и привязываем его к найденным сделкам.  В результате в карточке отображаются наши диалоги:

Снимок экрана 2018-09-12 в 15.22.03

Рис 4. Диалог в карточке CRM

Для добавления ссылки на чат в контакт или лид можно воспользоваться таким кодом:

$chat = new \Bitrix\ImOpenLines\Chat(ид чата);
$CrmFieldMultiUpdate=array(

'ENTITY_ID'=>'CONTACT',
'ELEMENT_ID'=>intval($params['bindings']['CONTACT'][0]),
TYPE_ID"=>"IM",
"VALUE_TYPE"=>"IMOL",
"COMPLEX_ID"=>"IM_IMOL",
"VALUE"=>'imol|'.$chat->chat['ENTITY_ID'],

);

$multi = new CCrmFieldMulti();

$multi->add($CrmFieldMultiUpdate);

Снимок экрана 2018-09-12 в 15.24.44

Рис 5. Ссылка на чат в карточке CRM

Далее, выясняется, что \Bitrix\ImConnector\CustomConnectors::sendMessages не запускает триггеры автоматизации. Запустить их принудительно можно с помощью Bitrix\ImOpenLines\Crm ->executeAutomationTrigger();

Дополнительно к вышеописанным проблемам на нашем сервере – не отрабатывали нотификации по новым сообщениям. Push-and-Pull был настроен верно, его события создавались, но не запускались «в фоне», а только по перезагрузке страницы. То есть нотификации не приходили не в мобильное приложение, не в десктопное, ни в браузер. Проблема решилась принудительным вызовом:

if (\Bitrix\Main\Loader::includeModule('pull'))

{
\Bitrix\Pull\Event::send();
}

Теперь прием сообщений работает как надо. Можно переходить к передаче.

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


Здесь также не обошлось без «особенностей». Мы столкнулись с такой проблемой: внешней системе, для передачи файлов (картинок, pdf) мессенджерам, требуются прямые ссылки на файл.  Битрикс, при отправке файла во внешнюю линию передает такой массив данных о файле:

Пример данных, приходящих в обработчик:

[0] => Array
(
[name] => Снимок.JPG
[type] => image
[link] => https://название_портала/~Mz0wS
[size] => 26374
)

Как видно, Битрикс отдает ссылку. Но это не ссылка на файл, а ссылка на html страницу, с которой файл можно скачать. Естественно, внешняя система не может этого сделать. Поэтому нам нужна прямая ссылка на файл. Чтобы ее получить – нужно знать ид файла. Но этой информации у нас нет. Поэтому будем в очередной раз изобретать велосипед:

Имея короткую ссылку, можно найти информацию о полной ссылке на файл:

$uri_res = CBXShortUri::GetList(array(), array("SHORT_URI"=>#короткая ссылка#));

Полная ссылка на файл содержит в себе хэш, по которому можно найти файл:

$extLink = Bitrix\Disk\ExternalLink::load(array('=HASH' => #хэш_из_полной_ссылки#));
$dwnLink=\Bitrix\Disk\Driver::getInstance()->getUrlManager()->getUrlExternalLink(array('hash' => $extLink->getHash(),'action' => 'showFile','token' => $downloadToken),true);

То есть, помимо ид файла, нам еще нужен и токен загрузки. Формируется он следующим образом:

$downloadToken = Bitrix\Main\Security\Random::getString(12);
$_SESSION['DISK_PUBLIC_VERIFICATION'][$extLink->getObject()->getId()] = $downloadToken;

Так мы получим прямую ссылку на файл. Однако как видно – токен хранится в сессии

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

$file = $extLink->getFile();
$file_data = $file->getFile();
CFile::viewByUser($file_data, array('force_download' => false, 'attachment_name' => $file_data['ORIGINAL_NAME']));

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

[ID] => 2805421
[TIMESTAMP_X] => 09.09.2018 16:11:33
[MODULE_ID] => disk
[HEIGHT] => 0
[WIDTH] => 0
[FILE_SIZE] => 0
[CONTENT_TYPE] => application/octet-stream
[SUBDIR] => disk/a8a
[FILE_NAME] => a8a71448e3092e5af419e9fb853d5ea5
[ORIGINAL_NAME] => Снимок.JPG
[DESCRIPTION] =>
[HANDLER_ID] =>
[EXTERNAL_ID] => 398c0e074fc79967709addb43672966f
[~src] =>

Как видите размер у него равен нулю, content-type равен application/octet-stream. При попытке в обработчике найти файл /upload/disk/a8a/a8a71448e3092e5af419e9fb853d5ea5 с помощью Bitrix\Main\IO\File — сообщает что файл не существует. На самом деле — сразу после выполнения обработчика события OnSendMessageCustom — файл перемещается на Диск в другое хранилище (что-то вроде хранилища файлов открытых линий). И тогда становится нормальным, доступным. Но, это все происходит уже после окончания работы обработчика OnSendMessageCustom. Соответственно, внешняя система не сможет скачать файл пока мы находимся в обработчике.

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

Вот такие интересные особенности разработки кастомного коннектора мы открыли для себя, когда писали интеграцию коробочной версии Битрикс24 с WhatsApp.

Добавить комментарий

Ваш адрес email не будет опубликован.