Slash-команды
Reason Space шифрует чат end-to-end, и бот не получает обычные сообщения пользователей как webhook — единственный inbound-канал «юзер → бот» это slash-команды.
Когда пользователь вызывает команду бота, её значения едут боту в plain-text (в обход E2E-шифрования чата) — но только по явному opt-in пользователя: вызвать команду = осознанно отправить эти данные конкретному боту. Объём строго ограничен схемой команды и суммарным размером — это структурная граница утечки.
Обычные чат-сообщения не доставляются боту никаким способом. Нет события вроде «бот получил DM» или «юзер написал боту». Inbound-текст есть только через slash-команды.
Жизненный цикл
Сервер бота объявляет весь свой набор команд одним
bulk-overwrite запросом PUT /api/bot/commands (заголовок
Authorization: Bot <token>). Каждая команда — имя, описание и
schema опций. См. Регистрация и
Опции.
В канале клиент подтягивает каталог доступных команд
(GET /api/chat/{space_id}/{channel_id}/commands) для автокомплита.
Пользователь выбирает команду, заполняет опции и вызывает её
(POST /api/chat/{space_id}/{channel_id}/commands/invoke). Платформа
валидирует доступ актора, доступность бота и значения опций против
схемы. См. Инвокация.
После валидации платформа адресно доставляет боту webhook-событие
command.invoked с типизированными options, actor_user_id,
channel_id и подписанным interaction_token. См.
Событие и ответ.
Бот отвечает инвокеру по interaction_token через
POST /api/bot/interactions/respond. Ответ виден только
вызвавшему пользователю (точечный WebSocket) и не персистится
в чат.
flowchart LR
Bot["Сервер бота"] -->|"PUT /api/bot/commands"| API[Reason Space API]
User["Пользователь"] -->|"GET .../commands<br/>POST .../commands/invoke"| API
API -->|"webhook command.invoked<br/>(+interaction_token)"| Bot
Bot -->|"POST /api/bot/interactions/respond"| API
API -.->|"эфемерный ответ (WS)"| User
Требуемый scope
Регистрация, доставка и видимость команд гейтятся одним scope:
commandsscopeПраво регистрировать slash-команды и принимать событие command.invoked.
Выдаётся владельцем при invite бота в Space — это не параметр кода
бота и не указывается в декораторе @bot.command(...).
Команду пользователь увидит в канале и сможет вызвать, только если у
membership бота есть scope commands и канал входит в
allowed_channel_ids. Сама регистрация (PUT /api/bot/commands)
глобальна для бота; scope commands гейтит доставку и видимость в
конкретном Space. См. Scopes.
Приватность
Значения опций ≤ 1024 байт plaintext. Суммарный размер
типизированных значений инвокации ограничен 1024 байтами (UTF-8) —
анти-«/note <дамп чата>». Превышение → ошибка валидации, событие
боту не уходит.
- Schema-bound. Юзер не может прислать боту произвольный текст —
только значения, объявленные в схеме команды. Неизвестные опции
отбрасываются, типы приводятся (
string/integer/number/boolean/user/channel),required/choices/min/maxпроверяются на стороне платформы. - НЕ попадают в чат, ленту и модерацию. Значения инвокации
передаются боту только через адресный webhook
command.invoked. Они не создают сообщение в канале, не видны другим участникам и не проходят через модерацию. Эфемерный ответ бота тоже не персистится. - Actor из подписанного interaction-token.
actor_user_idберётся изinteraction_token— это HMAC-SHA256 подпись над(interaction_id, actor, channel, space, bot)с TTL 900 секунд (15 минут). Бот не может подменить инвокера: при ответе платформа проверяет подпись и что токен принадлежит именно этому боту.
В payload_summary audit log сохраняется только имя команды и число
опций ({"command": ..., "n_options": ...}) — без значений. Полный
plaintext опций нигде на стороне платформы не логируется.
Пример (Python SDK)
from reasonspace_bot import Bot, CommandInvokedEvent
bot = Bot() # BOT_TOKEN + WEBHOOK_SECRET из env
# Объявить набор команд при старте (bulk-overwrite).
async def setup() -> None:
await bot.client.register_commands([
{
"name": "weather",
"description": "Погода в городе",
"options": [
{"name": "city", "type": "string",
"description": "Название города", "required": True},
],
},
])
# Хендлер на конкретную команду (имя — БЕЗ слэша, БЕЗ scope= в декораторе).
@bot.command("weather")
async def on_weather(event: CommandInvokedEvent) -> None:
city = event.options.get("city", "")
# Эфемерный ответ — виден только вызвавшему пользователю.
await bot.respond(event, f"В городе {city}: ясно, +18°C")
if __name__ == "__main__":
bot.run(port=8765)
@bot.command("weather") принимает только имя команды — без слэша и
без параметра scope=. Scope commands запрашивается при invite
бота в Space, а не в коде.
Дальше
PUT /api/bot/commands — bulk-overwrite, лимиты, список и удаление.
Типы опций, choices, min/max, required-порядок, лимит 1024 байт.
Каталог для юзера и invoke — проверки доступа и валидация значений.
Payload command.invoked и эфемерный respond по interaction-token.
Полный командный бот на reasonspace-bot.