Привет, Хабр! (И тебе, случайный читатель, который зашёл сюда просто просто потому, что заскучал в корпоративном чате.)
Сегодня я расскажу вам историю о том, какая задача посетила меня на этот раз и как я сделал «корпоративного бота с возможностью оценки сотрудников» — казалось бы, простая задача, но… Нас ждёт много удивительных вещей))
Изначально мой план был такой:
-
Сделать бота в Телеграме.
-
Дать боту ролевую модель и базовый функционал для дальнейшего простого расширения возможностей (по сути, я пришёл к тому, что сделал свой конструктор бота для Bitrix24, но об этом не в этой статье).
-
Научить его собирать фидбэк о коллегах.
-
Добавить машинное обучение, чтобы он сам выявлял, кто у нас в офисе трудяга, а кто мастерски имитирует бурную деятельность, ну и вообще эмоциональный фон в коллективе).
Но, как это часто бывает в IT, реальность внесла коррективы.
Отдел информационной безопасности посмотрел на мой прототип и сказал:
— «Персональные данные в Телеграме? Ну уж нет, это не по-нашему, не по-православному!»
И вот я стою на распутье: куда же пристроить бота? Или месяц коту под хвост и сказать, что всё это плохая идея и пора завязывать со всем.
Глава 1. Выбор мессенджера: три пути в никуда
Мне предложили три варианта отечественных мессенджеров:
-
VK – «Круто, есть API!» Но…
-
Половина сотрудников им не пользуется.
-
Безопасность? Ха! То аккаунты взламывают, то сам VK их теряет.

-
-
MAX (тот самый «патриотичный» мессенджер) – «Звучит гордо!» Но…
-
Из 250 сотрудников им пользуется ровно один.
-
API выглядит кстати очень здорово (приятно удивило).
-
-
Битрикс24 – «Он у нас уже есть, сервера свои, все сотрудники уже там!» Идеально, подумал я и начал изучать API? Как бы не так…
Глава 2. Документация Битрикса: игра в угадайку
API Битрикса – это как квест с сюрпризами, болью, слезами и страданием, а ещё в конце появляется скример.
Написано конечно всё классно, плюс/минус понятным языком, жаль конечно, что из примера реализации каждого метода то JS, то PHP, причём реально где-то откроешь метод и там сидит ПХП, а где-то полный набор с cURL, походу зависело от познаний разработчиков. Но вот вопрос, а где Python как можно было забыть этого красавчика.
Ладно, перейдём к примерам…
Открываем метод imbot.message.add – это отправка сообщений от лица бота, здесь у нас PHP
Открываем метод по созданию структуры компании (department.add) и здесь у нас уже полный набор.
И как я понял тот, кто делал API для бота владел одним стеком (PHP), а в остальных разделах API были разносторонние ребята, ну или те, кто умели пользоваться ГПТ, но это на самом деле не очень, важно…
Важно это:
Берём к примеру метод imbot.message.add – простой и понятный метод, отвечающий за отправку сообщений от лица бота. Смотрим на него детальнее…
Красиво? Понятно? – Мне показалось, что да. А теперь хотите узнать, как оно на самом деле?
Да, в KEYBOARD у нас вложился BUTTONS. В BUTTONS вообще массив с перечислением кнопок. CLIENT_ID у нас так же появился из неоткуда, без него вообще запрос возвращал ошибку запрета доступа (скрин со скримером). А MENU где? Его нет.
Как я это понял?
-
5 часов гугления.
-
10 часов перебора вариантов (просто тупо перебора вариантов как бы это могло работать…).
-
1 нервный срыв.
Но самое весёлое – документация врёт нам во многом.
Например, параметр BG_COLOR якобы меняет цвет кнопки. На деле – нет. Есть только четыре предустановленных варианта и то они в BG_COLOR_TOKEN:
И самое забавно, что в примере они типа управляют цветом, но на самом деле используют перезаписанный стиль кнопки.
Кстати, перед тем как, к примеру создавать command в KEYBOARD её ещё надо зарегистрировать в системе Битрикса. Это делается через метод imbot.command.register. Смотрим на пример.
А делаем вот так:
Вывод: Документация Битрикса — как квантовая физика: если кажется, что вы её поняли, значит, вы точно что-то упустили.
Глава 3. Локальный Битрикс? Не смешите мои серверы
Для начала давайте посмотрим, как вообще создаётся бот.
Из раздела «Разработчикам» мы можем попасть в меню выбора разработки.
Для создания бота мы выбираем «Добавить чат-бот», но на самом деле если вы будете делать нечто большее чем просто чат бот с быстрыми командами, то придется попотеть. Но пока не об этом.
После того как мы перешли в «Добавить чат-бот» и перешли следующий пункт (кстати который может у всех отличаться и как я понимаю вы можете брать понравившийся вам) у меня «Информировать сотрудников в чате». Переходим и видим панель создания бота.
Вебхук для вызова rest api – это механизм уведомления одного сервиса другим о произошедших событиях. Но простыми словами и в данном случае это просто API ключ. Иногда когда вы будете менять права доступа своему боту, вам может понадобиться перегенерировать данный ключ.
Создание бота:
Название бота* — Здесь у нас будет название бота таким каким пользователи будут его видеть и находить через поиск по чатам битрикса.
Кстати, если вы хотите изменить картинку боту, то это «ТИПА» можно сделать через запрос imbot.update в формате base64 — но у меня сколько я не пытался не получилось, возможно здесь таже ситуация с неправильной или не актуально докой, я сделал это через «Панель администратора Битрикс», о ней будет в следующем разделе.
URL обработчика бота* — это, пожалуй, самая важная штука. Сюда мы указываем адрес нашего сервера, на котором мы будем слушать обращения. Это нужно, чтобы, к примеру, когда пользователь пишет боту сообщение, или запускает команду, бот нам слал Сообщение.
А теперь перейдём к самой теме…
Мы столкнулись с интересной проблемой: несмотря на то, что сервер с Битрикс и мой локальный компьютер разработки находились в одной сети, сервер не мог достучаться до URL обработчика, указанного на моей машине. Это вылилось в долгий процесс поиска причины и решения.
Игра «Кто хочет стать разработчиком Bitrix-бота»:
И тут «A» понятно, что шутка. «С» – порт мы пробросили через NAT, что бы он пинговал мою машину. «D». Тут как бы не столь критично, но самогенерированный (псевдо) сертификат hpps мы сделали. А верный ответ «B».
У нас on-premise версия Битрикса (чтобы всё было на наших серверах).
Но вот загадка:
Почему запросы от бота идут через облако сторонней компании?
Разгадка:
-
Бот отправляет сообщение → запрос улетает на 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) # # ====================================
Итоговая схема:
Вот такую я нарисовал схему взаимодействия, на первый взгляд возможно не очевидную.
Вывод: кто виноват и что делать?
-
Документация Битрикса – это квест. Гугл и метод тыка спасают.
-
«Локальный» Битрикс иногда ведёт себя как облачный.
-
Боты боятся файлов, пока не дашь самостоятельность боту (а это может быть страшно).
Мораль:
Если ваш бот в Битриксе работает с первого раза – проверьте, не снится ли вам это.
P.S. В следующей статье расскажу, как сделать боту ролевую модель, научить бота отвечать на любые произвольные команды пользователя, подружить его с СУБД , заставить этого бота анализировать сотрудников (и не сойти при этом с ума).
ссылка на оригинал статьи https://habr.com/ru/articles/936252/
Добавить комментарий