Как мы выдавали ключи от демозала через Telegram, но переехали на eXpress: техническое руководство по миграции бота

Меня зовут Дмитрий, я инженер в Группе Rubytech — компания производит программно-аппаратные комплексы Скала^р и разрабатывает технологии для высоконагруженных ИТ-инфраструктур.
Вообще, наше подразделение занимается базами данных, но сегодня я расскажу про задачу, которая напрямую не была связана с рабочими обязанностями, зато ее решение сделало куда проще жизнь наших инженеров.
Ниже лирическое отступление про офис, скоро вы все поймете.
Помимо основного здания рядом (действительно рядом, метров 400), у нас есть свой демозал, производство и склад запчастей — здесь происходит своего рода магия, а именно — работа с железом. Инженеры время от времени выходят из офиса в соседние помещения и, как вы понимаете, происходить это может несколько раз за день. Демозал охраняется, доступ в него есть у ограниченного количества сотрудников. Также от демозала есть отдельный ключ, который до какого-то момента выдавался по старинке и иногда в отрыве от логики: записи о местонахождении ключа делались на ресепшене офисного здания, а сотрудникам каждый раз приходилось бежать спринт до демозала, чтобы с ненулевой вероятностью уже на месте обнаружить отсутствие ключа. В один прекрасный день нам просто надоело искать ключ и расстраиваться, и мы решили автоматизировать процесс выдачи.
Когда мы решали эту проблему год назад, то автоматизировали выдачу и учет ключа через бот в Telegram — довольно много сотрудников пользовались для неформального общения именно им (помните эти старые добрые времена, да?).
Этот мессенджер прекрасно подходил под нашу задачу с ключом, а логика работы бота легко программировалась. Мы выбрали python3 из-за его простоты. Принцип работы бота несложный: голосованием среди коллег мы выделили 4 главных состояния у ключа, которые запрограммировали в виде кнопок. Кнопка “help” — чтобы увидеть расшифровку числового статуса (цифры делают интерфейс читаемым), а кнопка “status” дает возможность увидеть номер телефона и ФИО сотрудника, взявшего ключ.

Бот прижился и всех всем устраивал… но начались проблемы с Telegram. Наша компания, как и многие, искала быструю замену привычному и полюбившемуся мессенджеру, и относительно недавно вся рабочая коммуникация в компании окончательно переехала на платформу eXpress.
Так как к хорошему быстро привыкаешь, отказываться от нашего «автоматизированного ключника», как в Tg, не хотелось, да и было не за чем. Через нашу внутреннюю техподдержку я запросил создать мне бота в eXpress и начал изучать поддерживаемый функционал.
Сегодня поделюсь решением насущной задачи по переносу бота из tg со всем функционалом — вдруг вы тоже собираетесь или в процессе переезда на eXpress.
Миграция бота с телеграм на eXpress
Ни одна миграция не проходит полностью гладко. Иногда мы даже добавляем себе технических вводных, чтобы жить стало интереснее. Когда я получил бот от коллег из техподдержки, то переписал его с нуля на язык Go — какой-то глубокой причины искать в этом не стоит. Просто захотелось попробовать язык в деле.
Ниже набор ссылок, которые помогли мне в разработке:
Давайте разберемся, какие же возможности мы получили (или потеряли) после перехода.
Оба бота работали на наших серверах, хранили и обрабатывали информацию в нашей сети, но для Telegram-бота все запросы отправлялись на сторонний сервер, а в eXpress — в наш. В случае с Telegram нужно было учитывать 152 ФЗ и информировать сотрудников об обработке и хранении их персональных данных для использования бота, а в eXpress этого повторно делать не надо, так как все согласия уже были подписаны при трудоустройстве.
Сложности в поиске готовых решений и документации
Во время разработки и внедрения мы столкнулись с некоторыми трудностями. Какие-то ответы на вопросы было сложно найти в сети или узнать у коллег, а некоторые были нам в новинку в принципе. Ниже поделюсь ходом мысли в решении встречающихся на пути сложностей и тем, как с ними удалось справиться.
1. Токен — динамический или статический?
Главная сложность при первичной настройке интеграции заключается в механизме авторизации REST API. Официальные SDK eXpress (например, библиотека pybotX для Python) работают по принципу динамической генерации токенов. Они берут учетные данные бота и «под капотом» вычисляют новую криптографическую подпись для каждого исходящего запроса. Однако большинство статических систем автоматизации и вебхуков (таких как Zabbix) не умеют генерировать сложные подписи на лету. Они требуют единого статического Bearer Token, который прописывается в настройках один раз и используется постоянно. Подробный гайд о том, как это сделать, изложен в статье.
Если коротко, то:
Необходимо вычислить криптографический хэш HMAC-SHA256:
```
echo -n <BOT_ID> | openssl dgst -sha256 -hmac <SECRET> | awk '{print toupper($0)}'
```
2)
```
curl 'https://localhost/api/v2/botx/bots/<ID Bot>/token?signature=<ХЭШ из 1 пункта>'
{"result": "TFMyNTY.g2gDbQA….Yh8g8-QPasNQ", "status": "ok}
```
3)
```
curl -X POST "https://<EXPRESS_DOMAIN>/api/v4/botx/notifications/direct" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <HASH> " \
-d '{
"group_chat_id": "12345678-abcd-1234-abcd-1234567890ab",
"notification": {
"status": "ok",
"body": "Тестовое сообщение для проверки интеграции BotX API"
}
}'
```
Мы решили работать со статическим ключом, хотя оба функционала заложены в бота (динамический мы не проверяли).
2. JSON-ответы
Пишем функционал бота — а как получить ответ? Например, нам нужно было сделать аналогичные кнопки в боте в eXpress — “help”, “status”, кнопки с числовыми значениями.
Примеры в официальной документации хорошо помогают нам в этом, но нужно было найти их тут.
Лучше всего для понимания сработали примеры доступного функционала на сайте разработчика.
Скажем, вот пример создания двух кнопок:
POST https://{express-server}/api/v4/botx/notifications/direct
{
"group_chat_id": "dec60c05-77b7-0d78-159e-b4fbee4d48f6",
"notification": {
"body": "The time has come to make a choice, Mr. Anderson:",
"metadata": {"foo": "bar"},
"bubble": [
[
{
"command": "/choose blue",
"label": "Blue pill",
"data": {"baz": "quux"}
},
{
"command": "/choose red",
"label": "Red pill"
}
]
]
}
}
Да, возможности пока не так обширны, но продукт развивается.
3. Общение с ботом только в групповом чате
Администраторы нашей инфраструктуры запретили личное общение с ботом, но дали ему права в групповом чате. Примем это как данность.
Вводные данные были простые: бот сидит в групповом чате, коллеги добавляются в группу и работают с ним коллективно. Но если все коллеги одновременно начнут нажимать кнопку «status», то чат превратится в филиал информационного агентства с той лишь разницей, что новости будут одни и те же каждые пять секунд. Для решения данного неудобства на помощь приходит API eXpress, который может отправлять личные сообщения. Благо, пример, как реализовать данный функционал, нам уже показали по ссылке ранее. Примерный код отправки сообщения такой:
```
POST https://{express-server}/api/v4/botx/notifications/direct
{
"group_chat_id": "dec60c05-77b7-0d78-159e-b4fbee4d48f6",
"recipients": ["83fbf1c7-f14b-5176-bd32-ca15cf00d4b7"],
"notification": {
"body": "Personal message"
}
}
```
В итоге получаем, что в групповом чате можно писать всем важную информацию и все ее видят, но работа с ботом и получение ответа от него носит индивидуальный характер.
4. «А у нас 80 порт запрещен»
Логическая схема обработки запросов ботом представлена на диаграмме Mermaid:

Из нее видно, что после нажатия пользователем на кнопки ответы боту возвращаются обратно в виде webhook по 80 (или иному порту) или по tls-порту.
Как их получать — решать вам, но мы руководствуемся политикой безопасности компании, поэтому пошли получать сертификат, подписанный нашим СА.
В большинстве примеров из сети этот функционал реализован без сертификатов, поэтому куда их подключать — вопрос со звездочкой.
Сервер eXpress расположен на выделенной ВМ. Вы обращаетесь к нему по API, а webhook отправляется в ответ по другому каналу, который должен шифроваться. Решение нашлось: вы выпускаете сертификаты и подписываете их своим СА (центром сертификации). Сами сертификаты подгружаются в код бота (кодом реализуется просто):
```
if err := server.ListenAndServeTLS(config.CertFile, config.KeyFile); err != nil {
log.Fatalf("Ошибка запуска HTTPS сервера: %v", err)
}
или наглядный пример по ссылке
```
, а CA куда загрузить? Со стороны сервера все обращения к ВМ боту выдают только следующее:
```
curl -v https://ваш-бот:443/health
# curl: (60) SSL certificate problem….
```
Вероятно, следующая операция поможет нам (путь может быть другим, в зависимости от вашего дистрибутива):
```
sudo cp ваш-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
```
Но нет. Ведь eXpress ставится не на систему, а в контейнеры, и подгрузить их туда тоже не получится, так как обновление сервера подразумевает пересоздание контейнеров.
Так как прав администратора у нас нет, то через заявку в нашу техническую поддержку мы получаем ответ: «Все сделано, а СА подключать надо, оказывается, через Web панель eXpress» (спасибо Антону из департамента информационных технологий):

Да и документация на сайте в открытом доступе, там тоже можно поискать:
После этого я добавил в бота функционал, чтобы он за 14 дней до прекращения работы сертификата предупреждал администраторов чата об этом.
Вместо заключения
Что же мы имеем в итоге?
Несколько сотен пользователей видят историю ключа. К себе в копилку положим новый язык, а сообществу дадим информацию, которую нам пришлось собирать по крупицам.

Если у вас возник вопрос, что же еще можно сделать с ботами в eXpress, — потенциал обширен. Вы можете сделать из него виртуального помощника, интегрированного в процессы и цифровую инфраструктуру компании, а можете реализовать Support Bot, который ищет инструкции в корпоративной базе знаний и в формате «вопрос–ответ» адресовывает заявки службе поддержки.
Здесь, кстати, собраны готовые к развертыванию боты, подготовленные коллегами из eXpress — для автоматизации ухода на больничный, настройки рабочего календаря и прочего. Рекомендуем изучить.
Надеюсь, что эта статья и наш пройденный путь помогут вам написать бот в eXpress под ваши собственные задачи намного быстрее.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.