Слэш-команды и кнопки
Бот регистрирует команды через REST, получает INTERACTION_CREATE в шлюзе и отвечает по токену взаимодействия. Для быстрого старта есть примеры на discord.js и discord.py в руководстве для ботов.
Пути ниже указаны относительно https://kli.chat/api/v10. Формы объектов описаны в OpenAPI. Архитектура описана в ADR 0010.
Бот
Команды
| Метод | Путь | Что делает |
|---|---|---|
| GET | /applications/{app}/commands | глобальные команды |
| POST | /applications/{app}/commands | создать (201) или обновить команду с тем же типом и именем (200, прежний id) |
| PUT | /applications/{app}/commands | заменить весь список: те же (тип, имя) или тот же id сохраняют id, остальные создаются, лишние удаляются |
| GET / PATCH / DELETE | /applications/{app}/commands/{command} | одна команда; PATCH меняет только присланные поля |
| … | /applications/{app}/guilds/{guild}/commands… | то же для команд одного сервера — только если бот на нём состоит |
Вызывает бот своего приложения (Authorization: Bot klb_…) или владелец приложения (обычный вход). Чужое приложение: бот — 403 50001, человек — 404 10002.
Чтобы проверить регистрацию на одном сервере, укажите ID приложения и сервера в KLICHAT_APP_ID и KLICHAT_GUILD_ID. Бот уже должен быть на сервере.
curl --fail-with-body "https://kli.chat/api/v10/applications/${KLICHAT_APP_ID}/guilds/${KLICHAT_GUILD_ID}/commands" \
-H "Authorization: Bot ${KLICHAT_BOT_TOKEN}" \
-H 'Content-Type: application/json' \
--data '{"name":"эхо","description":"Повторить текст","options":[{"type":3,"name":"текст","description":"Что повторить","required":true}]}'
Ответ — объект команды Discord: id, type, application_id, guild_id?, name, name_localizations, description, description_localizations, options, default_member_permissions (строка или null), dm_permission, contexts (null или список), integration_types, nsfw, version. version — snowflake; меняется, только если команда правда поменялась.
Проверки как у Discord, ошибка — 400 50035 с errors по полям (путь через точку: options.0.name, у PUT — с номером команды: 1.name):
- имя
^[-_\p{L}\p{N}]{1,32}$, у слэш-команд и опций — строчными (кириллица можно); у команд меню (type 2, 3) — 1–32 любых символа, описание пустое, опций нет; - описание 1–100; локализации — только известные языки Discord (
ru,en-US…); - до 25 опций; подкоманды не смешиваются с обычными опциями; группа (2) — только на первом уровне и только из подкоманд (1); обязательные опции раньше необязательных;
choicesдо 25 (name 1–100, value — строка до 100 или число до 2^53, у целых — целое), только у строк и чисел, нельзя вместе сautocomplete;min_value/max_value— у чисел;min_length0–6000 иmax_length1–6000 — у строк;channel_types— у каналов;- пределы: 100 слэш-команд глобально и 100 на сервер, по 5 команд меню каждого типа. PUT сверх предела — 400
50035, POST — 40030032.
Команды меню (USER, MESSAGE) хранятся, чтобы регистрация у ботов не падала, но клиент их пока не показывает.
INTERACTION_CREATE
Приходит в шлюз без всяких intents (бот с intents: 0 тоже получает). Если у бота шарды, событие получает шард сервера (guild_id >> 22) % num_shards; личка — шард 0.
{"op":0,"t":"INTERACTION_CREATE","s":42,"d":{
"id":"1553048426941149184","application_id":"1553047201491349504","type":2,"version":1,
"token":"sBOUfWEYoWCR…86 символов base64url","locale":"ru","guild_locale":"ru",
"guild_id":"1553047201298411520","channel_id":"1553047201323577344",
"channel":{"id":"1553047201323577344","type":0,"name":"общий","guild_id":"1553047201298411520","permissions":"665754290618367", …},
"member":{"user":{"id":"1553047201248079872","username":"member_6345x", …},"roles":[],"joined_at":"…","flags":0,"permissions":"638238686961217", …},
"app_permissions":"665754290618367","entitlements":[],"context":0,
"authorizing_integration_owners":{"0":"1553047201298411520"},"attachment_size_limit":26214400,
"data":{"id":"1553047377396264964","name":"эхо","type":1,
"options":[{"name":"текст","type":3,"value":"привет"},{"name":"кто","type":6,"value":"1553047200983838720"}],
"resolved":{"users":{"1553047200983838720":{…}},"members":{"1553047200983838720":{"roles":[],"permissions":"…", …}}}}
}}
type: 2 — команда, 3 — кнопка или список, 4 — автодополнение (у сфокусированной опцииfocused: true, значение — введённый текст строкой).- В личке с ботом вместо
member—user,context: 1,authorizing_integration_owners: {"1": "<id человека>"},channel.recipients: [человек]. - У кнопок и списков —
message(сообщение, где нажали) иdata: {custom_id, component_type, values?, resolved?}. resolvedсобирает сервер:users,members(безuser, сpermissionsв канале),roles,channels(сpermissionsвызвавшего),attachments(у опции-вложения значение — id файла).- Токен живёт 15 минут; в Redis — только его SHA-256.
Ответ: POST /interactions/{id}/{token}/callback
Передайте id и token из события INTERACTION_CREATE. Заголовок Authorization здесь не нужен. Принимаются JSON или multipart с payload_json и files[n].
| type | Команда | Кнопка | Автодополнение | Что делает |
|---|---|---|---|---|
| 4 | ✓ | ✓ | сообщение-ответ; flags: 64 — только вызвавшему | |
| 5 | ✓ | ✓ | «бот думает…» (флаг 128); data.flags: 64 — «думает» только у вызвавшего | |
| 6 | ✓ | ничего видимого; @original — сообщение с кнопкой | ||
| 7 | ✓ | сразу правит сообщение с кнопкой | ||
| 8 | ✓ | data.choices (до 25) уходят клиенту |
- Первый ответ — не позже 3 секунд после создания, иначе 404
10062 Unknown interaction. Повторный — 40040060. Неподходящий тип, модалка (9), опрос, Components V2 — 40050035. - Тело сообщения — как у ботов в REST:
contentдо 2000,embedsдо 10,components,allowed_mentions,flags(4, 64, 4096), вложения (files[n], кроме сообщений «только вам»). ?with_response=true— 200 и{"interaction": {"id", "type", "activity_instance_id": null, "response_message_id", "response_message_loading", "response_message_ephemeral"}, "resource": {"type", "message"?}}; без него — 204.
Например, это тело ответа отправит сообщение, которое увидит только вызвавший команду.
{"type":4,"data":{"content":"Понг!","flags":64}}
Дополнительные сообщения и правка ответа
На путях вебхуков, как у Discord: сервер сначала ищет вебхук с таким id, а не найдя — взаимодействие приложения с этим токеном.
| Метод | Путь | |
|---|---|---|
| POST | /webhooks/{app}/{token} | дополнительное сообщение; ответ — всегда сообщение (как wait=true) |
| GET / PATCH / DELETE | /webhooks/{app}/{token}/messages/@original | ответ (или, после 6 и 7, сообщение с кнопкой) |
| GET / PATCH / DELETE | /webhooks/{app}/{token}/messages/{id} | сообщение этого взаимодействия |
- Работает 15 минут и только после первого ответа (иначе 404
10062); неверный токен — 40410015. - Первая правка «думает…» снимает флаг 128 и отметкой правки не считается. Как у Discord, followup сразу после ответа 5 не создаёт новое сообщение, а заполняет «думает…».
@originalможно слать и как%40original(так кодирует discord.js).
Сообщения ответа
Автор — бот приложения, webhook_id — null.
{"id":"…","type":20,"flags":0,"content":"Понг!","application_id":"1553047201491349504",
"author":{"id":"1553047201491349505","bot":true, …},
"interaction_metadata":{"id":"…","type":2,"name":"настройки звук громкость","command_type":1,
"user":{"id":"…", …},"authorizing_integration_owners":{"0":"…"}},
"interaction":{"id":"…","type":2,"name":"настройки звук громкость","user":{…}},
"components":[]}
type: 20 — ответ на слэш-команду, 23 — на команду меню, 0 — дополнительные сообщения и ответы на кнопки.interaction_metadata.original_response_message_id— у дополнительных,interacted_message_id— у ответов на кнопку. Старое полеinteraction— только у ответов на команды.componentsесть у каждого сообщения (пустой список, если кнопок нет).- Флаг 64 («только вам»): сообщение не пишется в базу, не ищется и не считается непрочитанным.
MESSAGE_CREATE/UPDATE/DELETEуходят одному человеку (сchannel_idиguild_id), копия для правок по токену хранится в Redis 15 минут. После перезагрузки клиента его нет, как у Discord.
Кнопки и списки
Отправлять может только бот — в REST (создание и правка сообщения), в ответе на взаимодействие и в дополнительном сообщении. От людей и обычных вебхуков — 400 50035 «Кнопки могут отправлять только приложения».
"components": [
{"type": 1, "components": [
{"type": 2, "style": 1, "label": "Да", "custom_id": "yes"},
{"type": 2, "style": 5, "label": "Сайт", "url": "https://kli.chat"}
]},
{"type": 1, "components": [
{"type": 3, "custom_id": "цвет", "placeholder": "Цвет", "min_values": 1, "max_values": 2,
"options": [{"label": "Красный", "value": "red"}, {"label": "Синий", "value": "blue"}]}
]}
]
- До 5 строк (type 1); в строке до 5 кнопок или один список.
- Кнопка (2): стили 1–4 с
custom_id(до 100, уникален в сообщении), 5 — ссылкаurl(http/https) безcustom_id;labelдо 80,emoji {id?, name, animated?},disabled. Стиль 6 — 400. - Списки: 3 — текстовый (
options1–25:labelиvalueдо 100,descriptionдо 100,emoji,default), 5 — участники, 6 — роли, 7 — участники и роли, 8 — каналы (channel_types);placeholderдо 150,min_values0–25,max_values1–25 (по умолчанию 1 и 1),default_values [{id, type}],disabled. - Нажатие проверяет сервер: сообщение существует, автор — бот этого приложения,
custom_idесть и не выключен, тип совпадает; у списков — значения из разрешённых и в пределах min/max.
Клиент Кличата
Список команд
GET /api/v1/guilds/{guild}/application-command-index — что показать по «/» на сервере. GET /api/v1/channels/{channel}/application-command-index — в личке с ботом (в канале сервера — то же, что у сервера, в остальных личках — пусто).
{
"applications": [
{"id": "1553047201491349504", "name": "Проверочный", "icon": null, "bot_id": "1553047201491349505",
"bot": {"id": "1553047201491349505", "username": "proverochnyy", "global_name": "Проверочный", "avatar": null, "bot": true, …}},
{"id": "1550657676906590210", "name": "Кличат Музыка", "icon": null, "bot_id": "1550657676906590209", "bot": {…}}
],
"application_commands": [ApplicationCommand, …]
}
- На сервере: глобальные команды приложений, чей бот на сервере, серверные команды этого сервера и встроенная музыка (есть на каждом сервере). Только слэш-команды, только с контекстом «сервер» (
contextsсодержит 0 или null) и только те, что человек может вызвать поdefault_member_permissions(null — все, «0» — администраторы, маска — у кого все её права; владелец и администратор видят всё). - В личке с ботом: его глобальные команды, где
contextsсодержит 1 (илиcontextsnull иdm_permissionне false). - Приложения без подходящих команд в
applicationsне попадают.bot— расширение: имя и аватар бота. - Не участник сервера — 403.
Вызов: POST /api/v1/interactions
{"type": 2, "application_id": "1553047201491349504", "guild_id": "…", "channel_id": "…", "nonce": "1727270000000",
"data": {"id": "1553047377396264964", "name": "эхо", "type": 1, "options": [
{"type": 3, "name": "текст", "value": "привет"},
{"type": 6, "name": "кто", "value": "1553047200983838720"}
]}}
- Подкоманды — как у Discord:
options: [{"type": 2, "name": "звук", "options": [{"type": 1, "name": "громкость", "options": [{"type": 4, "name": "проценты", "value": 50}]}]}]. - Подсказки (type 4): та же команда, у опции, где сейчас курсор, —
"focused": trueи введённый текст строкой (даже у чисел). Остальные опции — только верные; обязательные можно не слать. - Опция-вложение: файл грузится как вложение сообщения (
POST /channels/{id}/attachments, затем PUT поupload_url), значение опции — id изdata.attachments:"data": {…, "options": [{"type": 11, "name": "файл", "value": "0"}], "attachments": [{"id": "0", "filename": "отчёт.pdf", "uploaded_filename": "<upload_filename из ответа>"}]}. - Кнопка или список (type 3):
{"type": 3, "channel_id", "guild_id"?, "message_id", "application_id"?, "data": {"custom_id", "component_type", "values"?}}.application_idможно не слать — приложение берётся по автору сообщения; если прислан, должен совпасть. У списков участников, ролей и каналовvalues— id. nonce— строка до 25 символов; сервер её принимает и не использует.
Сервер проверяет, создаёт взаимодействие, отдаёт его боту и ждёт первого ответа до 3 секунд:
| Ответ | Когда |
|---|---|
| 204 | бот ответил на команду или кнопку; само сообщение придёт событием шлюза |
200 {"choices": [{"name", "value"}]} | ответ на подсказки |
504 {"code": 10062, "message": "Приложение не ответило"} | бот не ответил за 3 секунды |
400 / 403 {"code", "message", "errors"?} | проверка не прошла; message — для человека |
429 {"retry_after"} | больше 5 вызовов за 5 секунд (подсказки — 20 за 5 секунд) |
Тексты отказов: «Нет права использовать команды в этом канале», «Не хватает прав для этой команды», «Команда больше не существует», «Приложения больше нет на этом сервере», «Кнопка больше не работает», «Канал недоступен», «Канал не найден», «Приложение не найдено», «Эта команда работает только на сервере», «Эта команда работает только в личке с ботом», «Команда 18+ работает только в каналах 18+», «Канал 18+: доступен только с 18 лет», «В категории команд нет: выберите канал», «Команды из меню пока не поддерживаются», «Неизвестный тип взаимодействия», «Проверьте значения в команде» (с errors по опциям: options.0.value), «Не удалось разобрать запрос».
Права: USE_APPLICATION_COMMANDS в канале, бот на сервере (кроме встроенной музыки), default_member_permissions. Нажать кнопку можно, если канал виден. Боту вызывать нельзя — 403 20001.
События
Обычные MESSAGE_CREATE/MESSAGE_UPDATE/MESSAGE_DELETE с полями выше. Сообщения «только вам» (флаг 64) приходят только вызвавшему; «бот думает…» — флаг 128 и пустой текст, затем MESSAGE_UPDATE с текстом.
Встроенная музыка
Приложение 1550657676906590210, бот 1550657676906590209. Worker при запуске задаёт команды /играй запрос, /пропустить, /стоп, /пауза, /продолжить, /очередь, /сейчас, /громкость число, /музыка (только на серверах), получает INTERACTION_CREATE подпиской на субъект бота и отвечает через interaction.Builtin. Подсказки и отказы видит только вызвавший; /играй сначала «думает», потом правит ответ. Команды с «!» работают по-прежнему.
Полное описание REST — OpenAPI 3.1. Разделы: вся документация.