Компоненты сообщений и модальные окна
Кнопки, выпадающие списки, чекбоксы на сообщении и модальные окна с полями ввода — всё это называется компонентами. На уровне протокола компоненты не описаны отдельными Protobuf-сообщениями на каждый визуальный тип (кнопка, список и т.д.) — вместо этого используется один опаковый JSON, который сервер не разбирает и не проверяет по содержимому, а только хранит и передаёт как есть. Такое решение сознательно: оно позволяет добавлять новые виды компонентов и клиентские возможности рендеринга, не меняя Protobuf-контракт и не требуя синхронного обновления сервера и всех клиентов.
Это единственная часть Bot API, где схема — не Protobuf, а плоский JSON-контракт, описанный прямо в этой документации. Если вы пишете свою библиотеку, стоит реализовать типизированные билдеры/парсеры поверх этого JSON, а не заставлять разработчика бота писать JSON руками (так сделано, например, в официальной библиотеке Voice.Bot.Client.Net).
Где используется
Поле с этим JSON называется components_json (для компонентов на сообщении) или modal_json
(для модального окна) и встречается в нескольких местах API:
BotsApi.SendMessage/UpdateMessage/SendInteractionFollowup— полеcomponents_json: бот может прикрепить компоненты к обычному сообщению, отправленному проактивно, не обязательно в ответ на interaction;BotsApi.RespondToInteraction— поляcomponents_jsonиmodal_json: ответ на interaction может нести компоненты (приkind: CHANNEL_MESSAGE/UPDATE_MESSAGE) либо открыть модальное окно (приkind: MODAL, см. «Interactions API»);- на прочитанном сообщении (
MessageInfo.components_json) — если у сообщения есть компоненты, они возвращаются вместе с сообщением при чтении, в том числе после перезагрузки истории чата.
Схема components_json: массив строк (ActionRow[])
Компоненты сообщения — это упорядоченный список строк (ActionRow), в каждой из которых один
или несколько элементов:
[
{
"type": "actionRow",
"components": [
{ "type": "button", "style": "primary", "customId": "confirm", "label": "Подтвердить" },
{ "type": "button", "style": "danger", "customId": "cancel", "label": "Отмена" },
{ "type": "button", "style": "link", "url": "https://example.com/docs", "label": "Документация" }
]
},
{
"type": "actionRow",
"components": [
{
"type": "select",
"customId": "pick-fruit",
"placeholder": "Выберите вариант",
"options": [
{ "label": "Яблоко", "value": "apple" },
{ "label": "Банан", "value": "banana" }
],
"minValues": 1,
"maxValues": 1
}
]
},
{
"type": "actionRow",
"components": [
{
"type": "combobox",
"customId": "search-user",
"placeholder": "Поиск...",
"searchable": true,
"options": [
{ "label": "Alpha", "value": "alpha-id" },
{ "label": "Beta", "value": "beta-id" }
],
"minValues": 1,
"maxValues": 1
}
]
},
{
"type": "actionRow",
"components": [
{ "type": "checkbox", "customId": "agree", "label": "Я согласен с правилами", "checked": false }
]
}
]
Виды элементов
type | Поля | Описание |
|---|---|---|
button | style, customId (обязателен, кроме style: "link"), url (обязателен для style: "link"), label, emoji, disabled | Кнопка. style — одно из primary/secondary/success/danger/link. Клик репортится через customId (см. InvokeMessageComponent.custom_id в «Interactions API»). |
select | customId, placeholder, options[] (label, value), minValues, maxValues | Обычный выпадающий список с фиксированными вариантами. Выбранные значения приходят в InvokeMessageComponent.values. |
combobox | То же, что select, плюс searchable: true | Такой же список вариантов, что и select, но клиенту показывается поле фильтрации/поиска над списком — удобно, когда вариантов много. Репортит выбор точно так же, как select. |
checkbox | customId, label, checked (состояние по умолчанию) | Отдельный переключатель; несколько чекбоксов в одной строке образуют чек-лист. Отмеченные customId репортятся так же, как выбор в select. |
Схема modal_json
{
"title": "Обратная связь",
"customId": "feedback-modal",
"components": [
{
"type": "actionRow",
"components": [
{
"type": "textInput",
"customId": "comment",
"label": "Комментарий",
"style": "paragraph",
"required": true,
"placeholder": "Расскажите, что вы думаете",
"maxLength": 1000
}
]
},
{
"type": "actionRow",
"components": [
{ "type": "checkbox", "customId": "subscribe", "label": "Уведомлять об ответах", "checked": true }
]
}
]
}
textInput.style — short (однострочное поле) или paragraph (многострочное) — прямой аналог
текстовых полей модальных окон Discord. После отправки формы значение каждого поля приходит в
SubmitModal.fields, где ключ — customId этого поля; чекбоксы в модальном окне возвращают
значение "true"/"false" тем же способом.
Куда это не проверяется
Ни в одном месте на сервере это содержимое не парсится и не валидируется по структуре — это
осознанно оставлено на усмотрение клиентов (и сервера рендеринга интерфейса Voice). Единственное
серверное ограничение — общая длина строки components_json/modal_json (несколько тысяч
символов) — как у любого текстового поля API. Ответственность за то, чтобы JSON соответствовал
описанной здесь схеме, лежит на клиентской библиотеке бота и на клиенте Voice, который эти
компоненты отображает.