Перейти к основному содержимому

Пример на SDK

Полный рабочий бот целиком на slash-командах: при старте декларирует свой набор команд через register_commands, а на каждый вызов отвечает эфемерным сообщением (видно только инвокеру) через bot.respond.

Slash-команды — единственный inbound-канал для бота. Обычные сообщения пользователей не доставляются боту как webhook (privacy hard-rule): content зашифрован E2E и боту недоступен. Если бот должен реагировать на ввод человека — делайте это через команды.

Как устроен бот на командах

Декларируйте набор команд при старте

bot.client.register_commands([...]) — idempotent bulk-overwrite: один вызов переписывает весь набор команд бота. Платформа отдаёт его пользователям в каталоге команд.

Регистрируйте хендлеры

@bot.command("ping") — имя команды без /. Декоратор НЕ принимает scope=: доступ к командам бота запрашивается scope'ом commands при приглашении (invite), а не в коде хендлера.

Отвечайте эфемерно

Внутри хендлера — await bot.respond(event, "..."). Ответ видит только тот, кто вызвал команду.

Полный пример

from reasonspace_bot import Bot, CommandInvokedEvent

bot = Bot() # читает BOT_TOKEN + WEBHOOK_SECRET из env


async def register() -> None:
await bot.client.register_commands(
[
{
"name": "ping",
"description": "Проверить, что бот жив",
},
{
"name": "play",
"description": "Найти и поставить трек",
"options": [
{
"name": "query",
"type": "string",
"description": "Название трека или артист",
"required": True,
}
],
},
]
)


@bot.command("ping")
async def cmd_ping(event: CommandInvokedEvent) -> None:
await bot.respond(event, "pong")


@bot.command("play")
async def cmd_play(event: CommandInvokedEvent) -> None:
query = event.options.get("query", "").strip()
if not query:
await bot.respond(event, "Укажите трек: /play <название>")
return

await bot.respond(
event,
f"Запрос «{query}» принят в канале {event.channel_id} "
f"от пользователя {event.actor_user_id}",
)


if __name__ == "__main__":
import asyncio

asyncio.run(register()) # один раз при старте — декларируем набор команд
bot.run(port=8765)

register_commands лучше вызывать один раз при деплое/старте (например, из bootstrap-хука или отдельным CLI-скриптом), а не на каждый webhook. Это полная перезапись набора — повторные вызовы безопасны (idempotent), но лишние.

Регистрация: что отправляется

bot.client.register_commands(commands) делает PUT /api/bot/commands с телом {"commands": [...]} (заголовок Authorization: Bot <token>).

namestringrequired

Имя команды без ведущего /. Это ключ, по которому платформа маршрутизирует вызов и по которому вы регистрируете хендлер: @bot.command("ping").

descriptionstring

Человекочитаемое описание для каталога команд.

optionsarray

Опции команды. Для play — одна string-опция query.

options[].namestringrequired

Имя опции — ключ в event.options при вызове.

options[].typestringrequired

Тип значения (например, string).

options[].requiredboolean

Обязательна ли опция.

Управление набором команд — три метода клиента:

Метод SDKHTTPНазначение
client.register_commands(commands)PUT /api/bot/commandsПерезаписать весь набор
client.list_commands()GET /api/bot/commandsПолучить текущий набор
client.delete_command(name)DELETE /api/bot/commands/{name}Удалить команду (204 No Content)

Событие command.invoked

Когда пользователь вызывает команду, платформа доставляет боту webhook-событие типа command.invoked. SDK разбирает его в CommandInvokedEvent и направляет в хендлер, зарегистрированный под этим именем через @bot.command(...).

commandstring

Имя вызванной команды (без /). По нему SDK выбирает хендлер.

optionsdict

Значения опций: event.options["query"]. Пустой словарь, если опций нет.

channel_idstring

Канал, из которого вызвана команда.

actor_user_idstring

Пользователь, вызвавший команду.

interaction_tokenstring

Токен взаимодействия для эфемерного ответа. bot.respond(event, ...) берёт его из события автоматически.

interaction_idstring

Идентификатор взаимодействия.

command_versionint

Версия набора команд, в рамках которой произошёл вызов (может быть null).

space_idstring

Space, в котором вызвана команда (поле конверта события).

bot_user_idstring

user_id самого бота (поле конверта события).

Эфемерный ответ

await bot.respond(event, content) достаёт interaction_token из события и вызывает client.respond(...)POST /api/bot/interactions/respond с телом {"interaction_token": ..., "content": ...}. Платформа отвечает 204 No Content. Сообщение видно только инвокеру.

interaction_token подписан HMAC-SHA256 и живёт 15 минут (TTL 900 с). Если обработка команды дольше — отвечайте быстро (например, «принято в работу»), а итог отправляйте отдельным сообщением через bot.send_message(...).

Если хотите вызывать команды от лица пользователя

Помимо вызова из клиента Reason Space, есть пользовательский API (заголовок Authorization: Bearer <user-token>):

GET /api/chat/{space_id}/{channel_id}/commands
POST /api/chat/{space_id}/{channel_id}/commands/invoke
Content-Type: application/json

{
"bot_user_id": "<uuid>",
"command": "play",
"options": { "query": "numb" }
}

Этот путь — для клиента/интеграции, действующей от имени пользователя; бот же получает результат как событие command.invoked и отвечает через bot.respond(...), как в примере выше.