惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threat Research - Cisco Blogs
A
Arctic Wolf
S
Securelist
O
OpenAI News
T
Threatpost
Forbes - Security
Forbes - Security
N
News and Events Feed by Topic
S
Secure Thoughts
H
Heimdal Security Blog
S
Security Affairs
P
Privacy International News Feed
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Cyberwarzone
Cyberwarzone
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
T
Tailwind CSS Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hacker News: Ask HN
Hacker News: Ask HN
T
Troy Hunt's Blog
S
SegmentFault 最新的问题
腾讯CDC
V
Visual Studio Blog
Last Week in AI
Last Week in AI
H
Hacker News: Front Page
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Project Zero
Project Zero
WordPress大学
WordPress大学
NISL@THU
NISL@THU
博客园 - 【当耐特】
博客园 - Franky
Webroot Blog
Webroot Blog
博客园_首页
T
Tenable Blog
雷峰网
雷峰网
Google Online Security Blog
Google Online Security Blog
阮一峰的网络日志
阮一峰的网络日志
V2EX - 技术
V2EX - 技术
Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
Lohrmann on Cybersecurity
The Hacker News
The Hacker News

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Создание чат-бота в портале Битрикс24 с помощью AI-агентов
rosliakov_b2 · 2026-05-06 · via Все публикации подряд на Хабре

Привет! Меня зовут Игорь Росляков, я технический писатель. По приглашению руководителя направления «Маркет и интеграции» Сергея Вострикова готовлю цикл статей на тему ИИ-ассистированной разработки решений для Битрикс24

Почти все проекты мы делаем на основе одного репозитория b24-ai-starter, который служит базой для разработки приложений. В нём уже есть все инструкции для ИИ, которые облегчают работу агентов.

Статьи из цикла можно использовать как туториалы при создании локальных приложений для своего бизнес-портала. Их можно показать своим разработчикам или использовать самому, если работаете один и хотите кастомизировать работу дополнительными удобными функциями.

Сегодня — 5-я статья. Мы создадим чат-бота, зарегистрируем его в портале и научим делать что-нибудь полезное. Всю работу будем делать с помощью AI-агентов. Во время работы разберём все возникающие сложности и устройство приложения.

Ссылка на итоговый репозиторий, который уже подключен к порталу:
github.com/igorrosliakov-bitrix24/Bitrix24-ChatBot

Игорь Росляков

Технический писатель

Что было в предыдущих туториалах:

  1. Пишем первое приложение с AI-стартером, чтобы видеть прибыли и убытки

  2. Добавляем в бизнес-портал Битрикс24 роботов для автоматизации

  3. Что даёт воспроизводимая среда разработки и как развернуть контейнеры на VPS.

  4. Анализ и модернизация коннектора баз данных с помощью AI-агентов

Что будет в этой статье:

Что нужно установить перед работой

Нам понадобятся:

  • Docker и Docker Compose для контейнеризации.

  • Git для контроля версий и пуша в удалённый репозиторий.

  • make для коротких команд.

  • Публичный HTTPS-туннель через CloudPub. Получить можно через десктопный клиент или инструмент командной строки clo.

Что за боты и где они находятся

В портале есть раздел Мессенджер. Там находятся все диалоги:

Если создать чат-бота, он тоже будет здесь. С ним можно начать диалог, попросить выполнить какие-то действия или запросить информацию.

Регистрируем приложение в портале

Сначала нужно прокинуть туннель через CloudPub, чтобы портал Битрикс24 мог обращаться к нашему приложению по HTTPS. Если не хотите через CloudPub, можно задеплоить на сервере — но это обычно дороже и дольше. Как копировать и настроить проект, прокинуть туннель с CloudPub и зарегистрировать приложение в своём портале, мы рассказали в первой статье в разделах:

При регистрации нужно указать путь обработчика и путь первоначальной установки. Для большинства проектов эти пути выглядит как URL от Cloudpub и тот же самый URL с эндпойнтом /install:

Но у чат-бота сценарий немного другой, поэтому оба пути должны быть одинаковыми и заканчиваться на эндпойнт /api/install. Ещё одно отличие: нужно выбрать пункт «Использует только API», потому что у чат-бота нет фронтенда:

Обновление: во время работы над статьёй в стартер внесли правку — теперь для приложений, которые используют только API, достаточно указывать эндпойнт /install. Если вы заметили, что можно исправить в стартер-ките, то тоже можете внести предложения по улучшению, потому что это open-source проект.

Если интересно, в чём разница пути обработчика и установки — объяснение под спойлером:

Путь обработчика, путь установки и почему у чат-бота именно такие параметры

Главное — для API-приложения на этих URL должен быть бэкенд-код, который понимает формат запроса. Одинаковые значения в двух полях допустимы, если один эндпойнт умеет обработать оба запроса.

Путь для первоначальной установки

Для обычного приложения с интерфейсом для установки нужно открывать фронтенд-страницу вида:

https://...cloudpub.ru/install

Но на этот раз наше приложение использует только API. В этой схеме при установке должен отправить данные установки или переустановки на наш бэкенд. Эти данные приходят HTTP-запросом: access token, refresh token, domain, member_id, scope. Бэкенд должен принять эти данные и понять: «Приложение установили в портал. Сейчас нужно сохранить токены, домен портала, member_id и выполнить стартовую настройку».

Путь вашего обработчика

Это более общий URL обработчика приложения. Битрикс24 может использовать его как основной серверный обработчик событий и запросов приложения.

В документации для чат-бота указан один PHP-файл:

https://example.com/chatapi/bot.php

И этот файл умеет обрабатывать разные случаи:

  • Установка приложения.

  • Сообщение боту.

  • Удаление бота.

То есть «путь обработчика» — это адрес, где находится серверная логика приложения.

Деплой на VPS мы разбирали это в отдельной статье: «Что даёт воспроизводимая среда разработки и как развернуть контейнеры на VPS».

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

  • Веб-мессенджер (im) необходим для передачи сообщений пользователю.

  • Создание и управление Чат-ботами (imbot) для регистрации чат-бота.

Проверяем, что чат-бот появился в портале

После успешной регистрации чат-бота можно найти в мессенджере, если начать вводить его имя:

Но в моём случае от момента регистрации до появления чат-бота в портале прошло довольно много времени, потому что во время интеграции всплыло несколько небольших ошибок, которые агент последовательно устранял.

При интеграции редко бывает одна большая ошибка. Чаще это цепочка маленьких несовпадений: несколько маленьких мест, где ожидания документации, портала и нашего проекта расходятся.

Дальше я опишу основные проблемы интеграции, но сначала остановимся на том, как с ними работать.

Как работать с ошибками: смотреть логи и передавать их агенту

Лучшее, что вы можете сделать во время работы с агентом — сразу дать ему доступ к файлу логов Symfony/PHP бэкенда. Другой вариант — мониторить логи самостоятельно такой командой:

tail -f logs/php/symfony/dev.log

Мониторить логи самому полезно и интересно для понимания работы, но если важна скорость, достаточно дать агенту доступ. Он сам найдёт все ошибки и поправит их. 

Команда выше показывает логи PHP-приложения. Через неё мы видим действия нашего портала Битрикс24, которые дошли до нашего PHP-бэкенда. То есть если портал отправил запрос на наш endpoint, мы увидим его в Symfony-логе.

Что есть что в этой команде:

  • tail показывает конец файла.

  • -f означает «следить дальше в реальном времени», то есть команду можно запустить в одном терминале и просто копировать вывод терминала после каждой новой ошибки.

  • logs/php/symfony/dev.log — файл, куда PHP/Symfony пишет dev-логи.

А вот что можно увидеть в логах:

  • Входящие запросы от портала Битрикс24.

  • Какой Symfony route сработал.

  • payload, который прислал портал.

  • Наши debug-сообщения.

  • Ошибки PHP/Symfony.

  • Запросы нашего бэкенда к Битрикс24 REST API.

  • Ответы портала на эти API-запросы.

Например, запись Matched route "b24_install" означает, что Битрикс24 достучался до нашего /api/install. call.start {"apiMethod":"imbot.register"} показывает, что бэкенд вызывает REST API Битрикс24, а chatbotRegistered — что бот зарегистрирован.

Но по этой команде нельзя увидеть всё. Логи фронтенда, базы данных, Cloudpub и системные Docker-логи контейнеров вызываются отдельно. Но в моём конкретном случае нужны были именно логи PHP-приложения.

Ошибка 1: неверный формат install payload

Битрикс24 в режиме «Использует только API» присылал данные установки не в том формате, который ожидал стартер.

В процессе установки участвовал PHP-бэкенд. Но в коде репозитория AI-стартер был класс с названием FrontendPayload, и бэкенд использовал его для разбора данных установки. И вот этот класс ожидал такие поля:

DOMAIN
AUTH_ID
REFRESH_ID

А портал присылал вложенный объект:

auth.domain
auth.access_token
auth.refresh_token
auth.member_id

То есть проблема была в том, что класс ожидал payload из frontend-сценария установки, а портал в API-only режиме присылает другой формат. 

Обратите внимание, что стартер-кит периодически дорабатывается. В случае изменений можно отправить issue в репозиторий и сделать его лучше.

Как решилось: агент расширил FrontendPayload, чтобы он понимал оба варианта: frontend-style payload и direct API-only payload.

Ошибка 2: не хватало прав приложения

Права нужно отслеживать в 2 местах.

При регистрации приложения в портале:

Те же права должны быть прописаны в .env в параметре SCOPE:

SCOPE='im,imbot,task,tasks_extended,imconnector'

Если забыть выдать права, установка может упасть на REST-вызове с такой ошибкой:

insufficient_scope

Ошибка 3: записи в локальной базе данных

После нескольких попыток установки в PostgreSQL остались старые записи. При новой установке база сказала:

duplicate key value violates unique constraint

То есть в базе уже существовала запись для пары b24_user_id и domain_url.

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

Ошибка 4: новый метод регистрации бота не работал на портале

Для регистрации предполагается использовать метод imbot.v2.Bot.register.

Но почему-то в моём портале это не сработало — видимо, агент неправильно прочитал документацию. В результате портал ответил:

ERROR_METHOD_NOT_FOUND
Method not found

Как решилось: агент решил испробовать метод, который использовался раньше, и он сработал.

imbot.register

В итоге агент добавил fallback: сначала для установки используется новый метод, а если он не срабатывает, пробуем старый.

Ошибка 5: разные состояния локальной базы данных и портала

В стартере есть обработчик lifecycle-событий AppLifecycleEventController. У него есть route:

#[Route('/api/app-events/', name: 'b24_events', methods: ['POST'])]:

То есть Symfony говорит: «Если придет POST-запрос на /api/app-events/, передай его в AppLifecycleEventController». 

Lifecycle-события — события жизненного цикла приложения: установили, обновили, удалили. И портал может сообщать бэкенду о таких событиях, например ONAPPINSTALL означает «приложение установлено в портале», а ONAPPUNINSTALL — наоборот, «приложение удалено из портала».

Во время установки наш бэкенд сообщил адрес обработчика lifecycle-событий Битрикс24, и после этого портал начал отправлять события вроде ONAPPINSTALL на отдельный endpoint /api/app-events/.

В чём была проблема: после нескольких неудачных попыток установки агент почистил все записи из локальной базы данных — это было на этапе Ошибки №3. Получилось так, что локальная база и состояние портала разошлись: в портале обработчик уже привязан, а в базе записи установки нет. 

В итоге Битрикс24 прислал событие установки, но наш бэкенд не нашел соответствующую запись установки в локальной базе. Появилась новая ошибка:

Application installation not found

Как решилось: обработчик lifecycle-событий должен быть устойчивым: отсутствие локальной записи не должно ронять весь процесс. 

Раньше пайплайн установки выглядел примерно так:

Если пришел ONAPPINSTALL:
  обязательно найди установку в базе
  если не нашел -- ошибка 500

А стал выглядеть так:

Если пришел ONAPPINSTALL:
  попробуй финализировать установку в базе
  если записи нет -- запиши warning, но не падай
  продолжай обработку

Ещё агент сделал так, что событие ONAPPINSTALL использовалось как второй шанс для регистрации чат-бота, потому что оно содержит полезные данные:

  • auth.access_token

  • auth.domain

  • auth.member_id

  • data.VERSION

То есть пайплайн ещё немного изменился:

Если регистрация бота не завершилась в /api/install:
  попробуй зарегистрировать его при обработке ONAPPINSTALL в /api/app-events/.

В итоге помогло именно это. В логе появились такие записи:

AppLifecycleEventController.process.chatbotRegistered
method: imbot.register
result: ["14"]

А сам чат-бот появился в мессенджере.

PS Откуда в проекте фронтенд

В логах и при работе с агентами можно заметить, что периодически встречаются записи и ошибки со словом frontend. Это может сбивать с толку, поэтому на всякий случай дадим краткое объяснение.

В AI-стартере есть отдельная frontend-часть на Nuxt, и для нее есть отдельный Docker-контейнер. Это потому, что стартер рассчитан не только на чат-ботов. Он может использоваться для обычных Битрикс24-приложений, где пользователь открывает интерфейс внутри портала — таких, как наши приложения с дашбордами и роботами.

То есть фронтенд нужен, если приложение имеет экран, кнопки, формы, настройки, виджеты, placement и так далее. Но если приложение использует только API, как чат-бот, фронтенд-контейнер может быть запущен, но всё будет работать и без него.

Учим чат-бота полезным функциям

Сейчас боту можно написать, но он ничего не ответит. Чтобы он мог делать что-то нужное, для него нужно создать возможные команды.

Агента можно попросить добавить функции в свободной форме, например: «Научи бота собирать просроченные задачи и присылать мне отчёт по команде, который будет включать дни просрочки и имена ответственных». Понимать код для добавления этих возможностей необязательно, но мы всё равно разберём, как это работает.

Главный файл для работы — ChatbotEventsController.php.

Общая логика работы с командами
#[Route('/api/chatbot/events', name: 'chatbot_events', methods: ['POST'])]
public function process(Request $request): JsonResponse
{
    // 1. Получаем событие от Битрикс24: сообщение, вход в чат и т.д.
    $payload = $this->getPayload($request);

    // 2. Нас интересуют только события чат-бота.
    if (!in_array($payload['event'] ?? '', ['ONIMBOTJOINCHAT', 'ONIMBOTMESSAGEADD'], true)) {
        return new JsonResponse(['status' => 'ignored'], 200);
    }

    // 3. Достаем текст сообщения пользователя.
    // 4. Определяем команду.
    // 5. Строим ответ.
    $reply = 'ONIMBOTJOINCHAT' === ($payload['event'] ?? '')
        ? $this->buildHelpReply()
        : $this->buildReply($this->extractUserMessage($payload), $payload);

    // 6. Отправляем ответ обратно в Битрикс24.
    $this->sendReply($payload, $reply);

    return new JsonResponse(['status' => 'ok'], 200);p

Команды добавляются в методе buildReply.

Общая логика работы бота в зависимости от разных команд
$tokens = $this->extractTokens($message);
$command = mb_strtolower($tokens[0] ?? '');
$arguments = array_slice($tokens, 1);

// Если команда /fx -- идем в валютный функционал.
if (in_array($command, ['/fx', 'fx', 'валюта', 'курс'], true)) {
    return $this->buildFxReply($arguments);
}

// Если команда /stock -- идем в функционал акций.
if (in_array($command, ['/stock', 'stock', 'акция', 'тикер'], true)) {
    return $this->buildStockReply($arguments);
}

// Если команда /market -- собираем акции + валюты.
if (in_array($command, ['/market', 'market', 'рынок', 'инвестиции'], true)) {
    return $this->buildMarketReply($arguments);
}

// Если команда /утро -- идем в Битрикс24 за задачами.
if (in_array($command, ['/morning', 'morning', '/утро', 'утро', 'дайджест'], true)) {
    return $this->buildMorningReply($payload);
}

То есть общие правила добавления команд такие:

1. Добавить условие в buildReply().

2. Создать метод buildSomethingReply().

3. Если нужна сложная логика — вынести ее в отдельный Service.

4. Вернуть строку, которую бот отправит в чат.

Запрос статуса задач

Сначала мы добавили отчёт по всем существующим задачам. Пользователь может написать слова: /morning, morning, /утро, утро, дайджест, и бот запускает функцию buildMorningReply:

if (in_array($command, ['/morning', 'morning', '/утро', 'утро', 'дайджест'], true)) {
    return $this->buildMorningReply($payload);
}

Что делает buildMorningReply:

private function buildMorningReply(array $payload): string
{
    // Достаем из события Битрикс24 домен, access token и ID пользователя.
    $context = $this->extractBitrixContext($payload);

    // Передаем эти данные в сервис, который умеет читать задачи.
    return $this->taskDigestService->buildMorningDigest(
        $context['domain'],
        $context['accessToken'],
        $context['userId'],
    );
}

Контекст по задачам получает функция extractBitrixContext:

   private function extractBitrixContext(array $payload): array
   {
       $bots = (array) ($payload['data']['BOT'] ?? []);
       $botAuth = [] !== $bots ? reset($bots) : [];
       $botAuth = is_array($botAuth) ? $botAuth : [];

       $domain = (string) ($payload['auth']['domain'] ?? $botAuth['domain'] ?? '');
       $accessToken = (string) ($payload['auth']['access_token'] ?? $botAuth['access_token'] ?? '');
       $userId = (int) ($payload['data']['USER']['ID'] ?? $payload['auth']['user_id'] ?? 0);

       if ('' === $domain  '' === $accessToken  0 === $userId) {
           throw new \RuntimeException('Не хватает данных Битрикс24 для чтения задач.');
       }

       return [
           'domain' => $domain,
           'accessToken' => $accessToken,
           'userId' => $userId,
       ];
   }

Общая логика задач находится уже в другом файле — Bitrix24TaskDigestService.php:

Общая логика задач
public function buildMorningDigest(string $domain, string $accessToken, int $userId): string
{
    // Загружаем активные задачи пользователя из Битрикс24.
    $tasks = $this->loadActiveTasks($domain, $accessToken, $userId);

    // Готовим группы задач.
    $overdue = [];
    $todayTasks = [];
    $important = [];
    $withoutDeadline = [];
    $future = [];

    // Раскладываем задачи по категориям.
    foreach ($tasks as $task) {
        if ($this->isCompleted($task)) {
            continue;
        }

        if (($task['priority'] ?? '') === '2') {
            $important[] = $task;
        }

        $deadline = $this->parseDeadline($task['deadline'] ?? null);

        if (null === $deadline) {
            $withoutDeadline[] = $task;
            continue;
        }

        // Сравниваем дедлайн с сегодняшним днем.
    }

    // Собираем текст ответа.
    return implode("\n", $lines);
}

Последняя часть — запрос к порталу в функции loadActiveTasks:

$this->httpClient->request('POST', sprintf('https://%s/rest/tasks.task.list', $domain), [
    'json' => [
        // Берем задачи, где текущий пользователь ответственный.
        'filter' => [
            'RESPONSIBLE_ID' => $userId,
            '!REAL_STATUS' => 5,
        ],

        // Запрашиваем только нужные поля.
        'select' => [
            'ID',
            'TITLE',
            'STATUS',
            'REAL_STATUS',
            'DEADLINE',
            'PRIORITY',
        ],

        // Токен Битрикс24 из входящего события бота.
        'auth' => $accessToken,
    ],
]);

Что получается при запуске:

Запрос во внешние API

Бот может работать не только с инструментами портала, но и внешними источниками. Для примера я попросил чат-бота ходить за курсами акций и валют в 2 разных API.

Команды для бота находятся там же, где и команда для сводки задач из портала — в файле ChatbotEventsController.php:

// Валюты: /fx USD EUR
if (in_array($command, ['/fx', 'fx', 'валюта', 'курс'], true)) {
    return $this->buildFxReply($arguments);
}

// Акции: /stock AAPL MSFT
if (in_array($command, ['/stock', 'stock', 'акция', 'тикер'], true)) {
    return $this->buildStockReply($arguments);
}

// Общая сводка: /market AAPL MSFT USD EUR
if (in_array($command, ['/market', 'market', 'рынок', 'инвестиции'], true)) {
    return $this->buildMarketReply($arguments);
}Ъ

Формирование ответа по акциям происходит в функции buildStockReply:

foreach (array_slice($arguments, 0, 5) as $symbol) {
    // Пропускаем валюты, оставляем только тикеры акций.
    if ($this->marketDataService->isCurrency($symbol)) {
        continue;
    }

    // Получаем котировку акции.
    $quotes[] = $this->marketDataService->getStockQuote($symbol);
}

Общая сводка создаётся в buildMarketReply:

foreach ($arguments as $argument) {
    // Если это валюта -- кладем в currencySymbols.
    if ($this->marketDataService->isCurrency($argument)) {
        $currencySymbols[] = $argument;

    // Если похоже на тикер -- кладем в stockSymbols.
    } elseif (preg_match('/^[A-Z]{1,6}(?:\.[A-Z]{1,4})?$/', $argument)) {
        $stockSymbols[] = $argument;
    }
}

Внешние API вынесены в отдельный файл MarketDataService.php. Бот получает валюты через сервис Frankfurter в getExchangeRate:

$payload = $this->httpClient
    ->request('GET', 'https://api.frankfurter.app/latest', [
        'query' => [
            'from' => $base,
            'to' => $target,
        ],
    ])
    ->toArray();

Курсы акций бот пытается получить через 2 разных API: Alpha Vantage или Stooq. Alpha Vantage даёт более надёжную информацию, но для него нужен ключ. Если мы захотим использовать его в будущем, этот ключ нужно будет положить в .env

ALPHA_VANTAGE_API_KEY='твой_ключ'

Если ключа нет, используем более простой сервис Stooq:

$apiKey = (string) ($_ENV['ALPHA_VANTAGE_API_KEY'] ?? $_SERVER['ALPHA_VANTAGE_API_KEY'] ?? '');

if ('' !== $apiKey) {
    return $this->getAlphaVantageQuote($symbol, $apiKey);
}

return $this->getStooqQuote($symbol);

Чтобы протестировать работу, нужно вызывать команду и аргументы. Командой может быть одно из слов в списке:

'/market', 'market', 'рынок', 'инвестиции'

И сразу после этого передаём аргументы — названия акций и валют в том виде, как они приняты на рынке. Например, /market AAPL MSFT USD EUR:

Или так — /market TSLA USD JPY:

Что будем делать дальше

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

Если вам интересна какая-то тема про кастомизацию портала — напишите в комментариях, а мы постараемся раскрыть её в следующих статьях.

Содержание цикла статей про создание приложений с AI-агентами

  1. Пишем первое приложение с AI-стартером, чтобы видеть прибыли и убытки

  2. Добавляем в бизнес-портал Битрикс24 роботов для автоматизации

  3. Что даёт воспроизводимая среда разработки и как развернуть контейнеры на VPS.

  4. Анализ и модернизация коннектора баз данных с помощью AI-агентов

  5. Создание чат-бота в портал Битрикс24 с помощью AI-агентов