Разработка Битрикс-бота: история о том, как документация врала, а облака смеялись

от автора

Привет, Хабр! (И тебе, случайный читатель, который зашёл сюда просто просто потому, что заскучал в корпоративном чате.)

Сегодня я расскажу вам историю о том, какая задача посетила меня на этот раз и как я сделал «корпоративного бота с возможностью оценки сотрудников» — казалось бы, простая задача, но… Нас ждёт много удивительных вещей))

Изначально мой план был такой:

  1. Сделать бота в Телеграме.

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

  3. Научить его собирать фидбэк о коллегах.

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

Но, как это часто бывает в IT, реальность внесла коррективы.

Отдел информационной безопасности посмотрел на мой прототип и сказал:
— «Персональные данные в Телеграме? Ну уж нет, это не по-нашему, не по-православному!»

И вот я стою на распутье: куда же пристроить бота? Или месяц коту под хвост и сказать, что всё это плохая идея и пора завязывать со всем.

Глава 1. Выбор мессенджера: три пути в никуда

Мне предложили три варианта отечественных мессенджеров:

  1. VK – «Круто, есть API!» Но…

    • Половина сотрудников им не пользуется.

    • Безопасность? Ха! То аккаунты взламывают, то сам VK их теряет.

  2. MAX (тот самый «патриотичный» мессенджер) – «Звучит гордо!» Но…

    • Из 250 сотрудников им пользуется ровно один.

    • API выглядит кстати очень здорово (приятно удивило).

  3. Битрикс24 – «Он у нас уже есть, сервера свои, все сотрудники уже там!» Идеально, подумал я и начал изучать API? Как бы не так…

Глава 2. Документация Битрикса: игра в угадайку

API Битрикса – это как квест с сюрпризами, болью, слезами и страданием, а ещё в конце появляется скример.

Bitrix скримеры которых я боюсь

Bitrix скримеры которых я боюсь

Написано конечно всё классно, плюс/минус понятным языком, жаль конечно, что из примера реализации каждого метода то JS, то PHP, причём реально где-то откроешь метод и там сидит ПХП, а где-то полный набор с cURL, походу зависело от познаний разработчиков. Но вот вопрос, а где Python как можно было забыть этого красавчика.

В Bitrix забыли python

В Bitrix забыли python

Ладно, перейдём к примерам…

Открываем метод imbot.message.add – это отправка сообщений от лица бота, здесь у нас PHP

Пример из документации APi Bitrix24 - https://apidocs.bitrix24.ru/

Пример из документации APi Bitrix24 — https://apidocs.bitrix24.ru/

Открываем метод по созданию структуры компании (department.add) и здесь у нас уже полный набор.

Пример из документации APi Bitrix24 - https://apidocs.bitrix24.ru/

Пример из документации APi Bitrix24 — https://apidocs.bitrix24.ru/

И как я понял тот, кто делал API для бота владел одним стеком (PHP), а в остальных разделах API были разносторонние ребята, ну или те, кто умели пользоваться ГПТ, но это на самом деле не очень, важно…

Важно это:

Берём к примеру метод imbot.message.add – простой и понятный метод, отвечающий за отправку сообщений от лица бота. Смотрим на него детальнее…

Документация APi Bitrix24 - https://apidocs.bitrix24.ru/

Документация APi Bitrix24 — https://apidocs.bitrix24.ru/
Пример из документации APi Bitrix24 - https://apidocs.bitrix24.ru/

Пример из документации APi Bitrix24 — https://apidocs.bitrix24.ru/
Пример из документации APi Bitrix24 - https://apidocs.bitrix24.ru/

Пример из документации APi Bitrix24 — https://apidocs.bitrix24.ru/

Красиво? Понятно? – Мне показалось, что да. А теперь хотите узнать, как оно на самом деле?

Истинная структура

Истинная структура

Да, в KEYBOARD у нас вложился BUTTONS. В BUTTONS вообще массив с перечислением кнопок. CLIENT_ID у нас так же появился из неоткуда, без него вообще запрос возвращал ошибку запрета доступа (скрин со скримером). А MENU где? Его нет.

Как я это понял?

  • 5 часов гугления.

  • 10 часов перебора вариантов (просто тупо перебора вариантов как бы это могло работать…).

  • 1 нервный срыв.

Но самое весёлое – документация врёт нам во многом.

Документация APi Bitrix24 - https://apidocs.bitrix24.ru/

Документация APi Bitrix24 — https://apidocs.bitrix24.ru/

Например, параметр BG_COLOR якобы меняет цвет кнопки. На деле – нет. Есть только четыре предустановленных варианта и то они в BG_COLOR_TOKEN:

primary (синий)

primary (синий)
secondary (белый с синей обводкой)

secondary (белый с синей обводкой)
alert (красный)

alert (красный)
base (прозрачный + серая обводка)

base (прозрачный + серая обводка)

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

Документация APi Bitrix24 - https://apidocs.bitrix24.ru/ (с моим пояснением: Синяя стрелочка то что не влияет на цвет кнопки, а красная стрелочка то что влияет)

Документация APi Bitrix24 — https://apidocs.bitrix24.ru/ (с моим пояснением: Синяя стрелочка то что не влияет на цвет кнопки, а красная стрелочка то что влияет)

Кстати, перед тем как, к примеру создавать command в KEYBOARD её ещё надо зарегистрировать в системе Битрикса. Это делается через метод imbot.command.register. Смотрим на пример.

Пример из документации APi Bitrix24 - https://apidocs.bitrix24.ru/

Пример из документации APi Bitrix24 — https://apidocs.bitrix24.ru/

А делаем вот так:

Истинная структура

Истинная структура

Вывод: Документация Битрикса — как квантовая физика: если кажется, что вы её поняли, значит, вы точно что-то упустили.

Глава 3. Локальный Битрикс? Не смешите мои серверы

Для начала давайте посмотрим, как вообще создаётся бот.

Из раздела «Разработчикам» мы можем попасть в меню выбора разработки.

Переход в раздел Разработчики

Переход в раздел Разработчики

Для создания бота мы выбираем «Добавить чат-бот», но на самом деле если вы будете делать нечто большее чем просто чат бот с быстрыми командами, то придется попотеть. Но пока не об этом.

После того как мы перешли в «Добавить чат-бот» и перешли следующий пункт (кстати который может у всех отличаться и как я понимаю вы можете брать понравившийся вам) у меня «Информировать сотрудников в чате». Переходим и видим панель создания бота.

Переход в Создание бота

Переход в Создание бота

Вебхук для вызова rest api – это механизм уведомления одного сервиса другим о произошедших событиях. Но простыми словами и в данном случае это просто API ключ. Иногда когда вы будете менять права доступа своему боту, вам может понадобиться перегенерировать данный ключ.

Создание бота:

Название бота* — Здесь у нас будет название бота таким каким пользователи будут его видеть и находить через поиск по чатам битрикса.

Название бота в нашем случае

Название бота в нашем случае

Кстати, если вы хотите изменить картинку боту, то это «ТИПА» можно сделать через запрос imbot.update в формате base64 — но у меня сколько я не пытался не получилось, возможно здесь таже ситуация с неправильной или не актуально докой, я сделал это через «Панель администратора Битрикс», о ней будет в следующем разделе.

Пример из документации APi Bitrix24 - https://apidocs.bitrix24.ru/

Пример из документации APi Bitrix24 — https://apidocs.bitrix24.ru/

URL обработчика бота* — это, пожалуй, самая важная штука. Сюда мы указываем адрес нашего сервера, на котором мы будем слушать обращения. Это нужно, чтобы, к примеру, когда пользователь пишет боту сообщение, или запускает команду, бот нам слал Сообщение.

А теперь перейдём к самой теме…

Мы столкнулись с интересной проблемой: несмотря на то, что сервер с Битрикс и мой локальный компьютер разработки находились в одной сети, сервер не мог достучаться до URL обработчика, указанного на моей машине. Это вылилось в долгий процесс поиска причины и решения.

Игра «Кто хочет стать разработчиком Bitrix-бота»:

Игра «Кто хочет стать разработчиком Bitrix бота»

Игра «Кто хочет стать разработчиком Bitrix бота»

И тут «A» понятно, что шутка. «С» – порт мы пробросили через NAT, что бы он пинговал мою машину. «D». Тут как бы не столь критично, но самогенерированный (псевдо) сертификат hpps мы сделали. А верный ответ «B».

У нас on-premise версия Битрикса (чтобы всё было на наших серверах).

Но вот загадка:

Данный скриншот после команды "tcpdu"

Данный скриншот после команды «tcpdu»

Почему запросы от бота идут через облако сторонней компании?

Разгадка:

  • Бот отправляет сообщение → запрос улетает на Corp Soft, Seleznevskaya street, 32 (это облачный провайдер Битрикса) и он такой не один, мы так же поймали как минимум пять ip VK (походу он так сильно хотел нашего бота).

  • Вопрос: Зачем гонять трафик через третьи лица, если у нас локальная версия?

Теории:

  • Кто-то накосячил в архитектуре.

  • Это фича, а не баг (чтобы мы купили облако).

Это весьма интересный вопрос, от которого вообще теряется весь смысл on-prem поставки продукта. Ведь нельзя было использовать мощности сервера для отправки запроса, зачем гонять трафик через 3-х лиц. Короче мне кажется, что здесь кто-то просто сильно накосячил.

Глава 4. Бот, который боится файлов

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

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

К сожалению, стандартного решения нет. В интернете я нашёл только информацию о платных подписках на «супер-пупер бота», который решает эту проблему. Но мы же разработчики-экспериментаторы!

Решение?

  • Получаем права администратора (каждый делает это по-своему). И заходим в администрирование Bitrix.

  • Находим бота среди пользователей (извините, скриншотов не будет — это конфиденциально).

  • Выдаём ему права обычного сотрудника (чтобы он не выделялся).

  • Авторизуемся под ботом через кнопку «Авторизоваться под сотрудником» в Панеле администрования Bitrix.

  • Настраиваем персональный вебхук для бота (как показано на скриншотах).

Инструкция создания Вебхука

Инструкция создания Вебхука
Инструкция создания Вебхука

Инструкция создания Вебхука
Инструкция создания Вебхук

Инструкция создания Вебхук

Даём права в «Настройка прав». А лучше сразу дать всё чтобы больше не лазить сюда. Копируем сформированный Вебхук. Закрываем и забываем как страшный сон.

И получается у нас смешная картина. Мы сделали бота, наделили его правами создателя, а затем воскресили его (превратив в сотрудника) и выдали ему ещё токен под его правами, и теперь у нас есть 2 токена (1 супер крутой – принадлежит боту, другой простенький и принадлежит нам), и пишем код, который будет маршрутизировать использование прав в зависимости от ситуаций. Не хотите писать, я уже написал, правда не судите строго))

import json import requests import itertools from typing import Dict, Any, List, Optional  class Bitrix24API:     """Класс для работы с REST API Bitrix24 через вебхук(и)"""     def __init__(self, webhook_url: str, array_webhooks: Optional[List[str]] = None, logger=None, fetch_all_pages: bool = True, page_size: int = 50):         """         Инициализация класса с URL вебхука(ов) Bitrix24         ---         :param webhook_url: URL вебхука Bitrix24         :param array_webhooks: Список альтернативных вебхуков для перебора         :param logger: Объект логирования от библиотеки logging         :param fetch_all_pages: Автоматически загружать все страницы при пагинации         :param page_size: Количество элементов на странице (по умолчанию 50)         """         self.webhook_url = webhook_url         self.array_webhooks = array_webhooks or []         self.logger = logger         self.specifically = None         self.fetch_all_pages = fetch_all_pages         self.page_size = page_size      # ---Внутрение методы---     def _try_call_method(self, webhook_url: str, method: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:         """Внутренний метод для попытки вызова метода через конкретный вебхук"""         url = f"{webhook_url}{method}"         try:             response = requests.post(url, json=params)             response.raise_for_status()             result = response.json()             if 'error' in result:                 if self.logger: self.logger.error(f"Bitrix API ошибка в '{webhook_url}': {result['error']}")                 return None             if self.logger:  self.logger.info(f"Запрос к Bitrix API выполнен успешно - method: {method} - 200")             return result         except requests.exceptions.RequestException as e:             if self.logger:  self.logger.error(f"Ошибка запроса в '{webhook_url}': {e}")             return None         except json.JSONDecodeError as e:             if self.logger: self.logger.error(f"Ошибка декодирования JSON в '{webhook_url}': {e}")             return None      def _call_with_pagination(self, webhook_url: str, method: str, params: Dict[str, Any], start: int = 0) -> Optional[Dict[str, Any]]:         """         Внутренний метод для обработки пагинации         ---         :param webhook_url: URL вебхука         :param method: Метод API         :param params: Параметры запроса         :param start: Смещение для пагинации         :return: Объединенный результат или None при ошибке         """         params = params.copy()         params['start'] = start         result = self._try_call_method(webhook_url, method, params)         if not result: return None         if not self.fetch_all_pages or 'next' not in result or start > 0: return result.get('result')         # Если нужно получить все страницы и это первый запрос         total = result.get('total', 0)         if total <= self.page_size:             return result.get('result')         # Вычисляем количество дополнительных запросов         count_pages = (total // self.page_size) - (1 if total % self.page_size == 0 else 0)         # Собираем результаты со всех страниц         all_results = [result.get('result', [])]         for page in range(1, count_pages + 1):             next_result = self._call_with_pagination( webhook_url, method, params, page * self.page_size)             if next_result is not None: all_results.append(next_result)         # Объединяем результаты         if isinstance(all_results[0], list):             return list(itertools.chain.from_iterable(all_results))         elif isinstance(all_results[0], dict):             merged = {}             for res in all_results: merged.update(res)             return merged         return all_results[0]      # ---Внешние методы---     def callMethod(self, method: str, params: Optional[Dict[str, Any]] = None,                    specifically: Optional[str] = None) -> Any:         """         Отправляет запрос к Bitrix24 REST API с поддержкой пагинации         ---         :param method: Метод API (например, "crm.deal.list")         :param params: Параметры запроса в виде словаря         :param specifically: Конкретный вебхук для использования         :return: Ответ от сервера Bitrix24 или None в случае ошибки         """         if params is None: params = {}         webhooks_to_try = []         if specifically is not None: webhooks_to_try = [specifically]         else:             if self.webhook_url: webhooks_to_try.append(self.webhook_url)             webhooks_to_try.extend(self.array_webhooks)         for webhook in webhooks_to_try:             result = self._call_with_pagination(webhook, method, params)             if result is not None: return result         if self.logger:             self.logger.error(f"Запрос к Bitrix API никак не отработал {method}, {params}")         return None   # # ==================================== # # ----------Пример использования------ # if __name__ == "__main__": #     # Инициализация клиента с несколькими вебхуками #     main_webhook = "https://...Токен_Бота" #     backup_webhooks = ["https://...Токен_Разработчика", "https://...Токен_Васи_Пупкина", "https://...Токен_ФигЗнаетКого"]      #     bitrix = Bitrix24API(webhook_url=main_webhook, array_webhooks=backup_webhooks, logger=C.LOGGER_BITRIX_API) #     result = bitrix.callMethod("calendar.section.get", {"type": "user", "ownerId": "0"}, specifically="https://...Токен_Разработчика") #     print(result) # # ====================================

Итоговая схема:

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

Схема того как работает наш бот на on-premise Bitrix24

Схема того как работает наш бот на on-premise Bitrix24

Вывод: кто виноват и что делать?

  • Документация Битрикса – это квест. Гугл и метод тыка спасают.

  • «Локальный» Битрикс иногда ведёт себя как облачный.

  • Боты боятся файлов, пока не дашь самостоятельность боту (а это может быть страшно).

Мораль:
Если ваш бот в Битриксе работает с первого раза – проверьте, не снится ли вам это.

P.S. В следующей статье расскажу, как сделать боту ролевую модель, научить бота отвечать на любые произвольные команды пользователя, подружить его с СУБД , заставить этого бота анализировать сотрудников (и не сойти при этом с ума).


ссылка на оригинал статьи https://habr.com/ru/articles/936252/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *