Боты со Slack

Приложение на Slack Bolt или slack_sdk переезжает в Kontext без переписывания обработчиков: меняются адрес API, токен и секрет подписи. События приходят по HTTP — режима Socket Mode нет.

Что работает

В SlackВ Kontext
Бот-пользователь приложенияБот пространства: «Интеграции → Боты → Новый бот», шаблон «Приложение Slack (Bolt)»
Bot token xoxb-…Токен бота ktx_b_… — в карточке бота, раздел «Токены»
Signing SecretСекрет подписи — показывается при создании бота, сменить его можно в разделе «Секрет подписи»
Web API: chat.*, reactions.*, conversations.*, users.*, auth.testТе же методы по адресу https://app.kontext.su/api/slack/
Events API: message, app_mention, message_changed, message_deleted, reaction_added, reaction_removed, team_join, channel_createdПодписка бота на события в формате Slack — раздел карточки «События»
Слеш-команды, ack(), respond(), response_urlСвои команды бота — раздел карточки «Команды»
Block Kit и вложения в chat.*Карточка с кнопками, списками, датами, «⋯» — как у Slack
Interactivity: block_actions, respond(replace_original=True)Адрес действий бота — раздел карточки «Интерактивность»
views.open, views.update, views.push, view_submission, view_closedМодальные окна по trigger_id — тем же кодом
Shortcuts: app.shortcut(...) — глобальные и у сообщенияЯрлыки бота — «Интерактивность» → «Ярлыки»: глобальный ярлык виден в подсказке после «/», ярлык сообщения — значком молнии в панели действий сообщения
external_select, app.options(...)Список, варианты которого приложение подбирает по набранному тексту, — тем же кодом, через адрес действий
User groups: usergroups.*, <!subteam^S…>Группы людей пространства: @devs уведомляет участников группы в канале; бот дежурств по очереди меняет, кто входит в @oncall
Входящий вебхукВходящий вебхук Kontext принимает формат Slack без переделок и может писать от имени бота

Переезд за шесть шагов

  1. Заведите бота: «Пространство → Интеграции → Боты → Новый бот», шаблон «Приложение Slack (Bolt)». Скопируйте секрет подписи — он показывается один раз.
  2. Добавьте бота в каналы, где он работает (карточка бота → «Каналы»). Бот видит и получает события только там, где состоит, — как в Slack.
  3. Выпустите токен бота (карточка бота → «Токены») с нужными областями доступа (scopes).
  4. Поменяйте три строки в приложении:
    import os
    
    from slack_bolt import App
    from slack_sdk import WebClient
    
    app = App(
        client=WebClient(
            token=os.environ["KONTEXT_BOT_TOKEN"],
            base_url="https://app.kontext.su/api/slack/",
        ),
        signing_secret=os.environ["KONTEXT_SIGNING_SECRET"],
    )
    Для Node (@slack/bolt): new App({ token, signingSecret, clientOptions: { slackApiUrl: "https://app.kontext.su/api/slack/" } }).
  5. Направьте события и команды на адрес приложения (обычно https://ваш-сервер/slack/events) в карточке бота: «События» — формат «Slack», «Команды» — /имя и тот же адрес. Kontext подписывает запросы так же, как Slack (X-Slack-Signature, X-Slack-Request-Timestamp), поэтому Bolt проверяет подпись сам. При сохранении подписки придёт запрос url_verification — на него Bolt тоже ответит сам.

    Отметьте только те события, которые приложение обрабатывает, — как «Subscribe to bot events» в настройках Slack: «Упомянули бота» — это app_mention, «Сообщение или ответ в треде» — message. На событие без обработчика Bolt отвечает ошибкой 404. Если приложение обрабатывает только упоминания, а подписка шлёт ему все сообщения, неудачные доставки будут копиться, пока подписка не выключится.

  6. Кнопки и окна: карточка бота → «Интерактивность» → тот же адрес https://ваш-сервер/slack/events (как Interactivity Request URL у Slack). Без него кнопки в карточках видны, но не нажимаются.

Отличия

  • Идентификаторы другие: C<число>, U<число>, B<число>, T<число>. Если идентификаторы Slack прописаны в коде или настройках, замените их на наши — их возвращают conversations.list, users.list, auth.test.
  • ts сообщения — номер, а не время ("12345.000000"). Треды, правка и реакции по ts работают, но вычислять по ts время отправки нельзя.
  • Блоки: нет rich_text_input, file_input, video, file. Неподдерживаемый блок в сообщении заменяется пометкой, а в окне вызывает ошибку invalid_arguments. Нет вкладки App Home (views.publish), устаревших интерактивных вложений и dialog.*.
  • Ярлыки задаются не в манифесте, а в карточке бота («Интерактивность» → «Ярлыки»): вид, название, callback_id. Ярлык сообщения люди видят только в каналах, которые бот может читать сам, — ведь приложению уходит всё сообщение целиком. У глобального ярлыка, как и в Slack, нет ни канала, ни response_url: приложение открывает окно по trigger_id.
  • external_select: варианты запрашиваются на тот же адрес действий (block_suggestion; отдельного «Options Load URL» нет), когда набрано min_query_length знаков (по умолчанию 3). Приложение должно ответить {"options"} или {"option_groups"} за 3 секунды. Выбрать можно только то, что приложение предложило этому человеку.
  • Группы: для usergroups.list и usergroups.users.list нужна область users:read; для usergroups.create, update, disable, enable и users.update — область usergroups:write и роль бота «как участник» (у гостевой роли групп нет). <!subteam^S5> с нашим номером в сообщении бота уведомляет группу. Номер берите из usergroups.list — номера Slack у нас другие.
  • Нажатие не повторяется, а ответа приложения Kontext ждёт 3 секунды, как и Slack. Кнопка со ссылкой (url) запроса приложению не отправляет. Значение value кнопки доступно каждому, кто видит сообщение, — секреты туда не кладите.
  • Нет: files.* (файлы загружаются через POST /api/v1/files), users.lookupByEmail (адресов почты в API нет), Socket Mode (события, команды и нажатия приходят только по HTTP: приложению на Socket Mode понадобятся адрес, доступный из интернета, и запуск в режиме HTTP), установки приложения через OAuth («Add to Slack») — токен выдаёт администратор пространства.
  • username, icon_emoji, icon_url не действуют: сообщение пишется от имени бота с его картинкой.
  • Событий задач в формате Slack нет (у Slack таких событий нет вовсе) — для них нужна подписка в формате Kontext.
  • Команды: ack("…") — ответ с пометкой «видно только вам». Если приложение не ответило за 3 секунды, вызвавший видит «ждём ответ…». Дольше 30 секунд команда не ждёт и не повторяется (Slack тоже не повторяет). respond() работает через response_url: 30 минут, до пяти ответов. say() пишет через chat.postMessage, поэтому бот должен состоять в канале.

Если что-то не так

  • invalid_auth — токен скопирован не целиком или это не токен бота Kontext.
  • account_inactive — бот выключен или удалён.
  • not_in_channel — добавьте бота в канал (карточка бота → «Каналы»).
  • missing_scope — у токена нет нужной области, её название — в поле needed ответа. Выпустите токен с этой областью.
  • unknown_method — такого метода в Kontext нет; список — на странице API.

Полное описание API — Документация → API.