Привет, Хабр! (И тебе, случайный читатель, который зашёл сюда просто просто потому, что заскучал в корпоративном чате.)
Сегодня я расскажу вам историю о том, какая задача посетила меня на этот раз и как я сделал «корпоративного бота с возможностью оценки сотрудников» — казалось бы, простая задача, но… Нас ждёт много удивительных вещей :-)
Изначально мой план был такой:
Сделать бота в Телеграме.
Дать боту ролевую модель и базовый функционал для дальнейшего простого расширения возможностей (по сути, я пришёл к тому, что сделал свой конструктор бота для 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. В следующей статье расскажу, как сделать боту ролевую модель, научить бота отвечать на любые произвольные команды пользователя, подружить его с СУБД , заставить этого бота анализировать сотрудников (и не сойти при этом с ума).
Комментарии (4)
Pluzh
12.08.2025 09:16В битриксе так устроено буквально всё, не только боты.
Их документация:
А) Разбросана как попало по множеству страниц.
Б) Неполная
В) Недостоверная, то есть часто там вообще описано что-то не то, что реально есть в битриксе.Можно было бы применять вместо документации глаза и логику. Но элементы внутри самой системы:
А) Разбросаны внутри с грубыми нарушениями логики
Б) Не содержат функций, которые ожидаешь увидеть в любом интерфейсе.
В) Делают не то, что на них написано.
Г) Имеют незадокументированные исключения в применимости.
Д) Однотипные элементы работают по разной логике.
Обычная фильтрация может в одном месте действительно фильтровать, как ожидается, а в другом месте фильтровать, но совсем не так.Я долго думал - действительно ли разработчики битрикс настолько плохи.
Но отгадку я услышал случайно на одной из их конференций с партнёрами-интеграторами. Они сказали, что и так дают тем заработать...
serg-vostrikov
12.08.2025 09:16Спасибо за статью! Разобраться с особенностями ботов - не самая простая история. Это достаточно старый функционал, который по мере развития, оброс рядом костылей в REST API, к сожалению. И пока мы еще не до конца актуализировали материалы по ботам в доке. Как раз в ближайших планах обновление по этому поводу от команды коммуникаций.
И да, текущая версия REST API - это зоопарк, получившийся в силу интенсивного наращивания пользовательского функционала, прежде всего. Битрикс24, признаем, не API first продукт. Но мы стараемся исправиться в новой версии REST API, которая уже жестко регламентирована для внутренней разработки. Думаю, скоро первые методы уже выкатим.
Было бы очень круто, если бы вы этот материал в виде туториала закинули в доку - https://github.com/bitrix-tools/b24-rest-docs. Уверен, что много людей были бы очень благодарны за подробный разбор особенностей bot api в Битрикс24 :).MrSotnik Автор
12.08.2025 09:16Отлично, появиться свободное время попробую туда изложить проблемные места и то как с этим можно и нужно бороться))
vacoo
Подтверждаю. Битрикс это лютая дичь. Код очень низкого качества