Боты и APIv281
Бот в Vortex — это обычная учётная запись, которой управляет программа: скрипт подключается к серверу по тому же WebSocket-протоколу, что и браузерный клиент. Отдельной регистрации для ботов не нужно: при первом подключении сервер сам создаёт учётку и выдаёт токен, а дальше бот может читать команды в чатах, отвечать на сообщения и даже заходить в голосовые каналы со своим звуком.
На этой странице — пошаговое создание своего бота и краткий справочник Bot API.
1. Как создать своего бота
Всё, что нужно, — любой язык с WebSocket-библиотекой. Ниже примеры на Python, но протокол тривиален и на другом языке: подключились, обменялись JSON — бот работает.
- Придумайте боту постоянный id — строку вида
bot-xxxx, которую вы генерируете один раз и больше не меняете. По нему сервер узнаёт бота при каждом входе. - Подключитесь к
wss://<ваш-сервер>/wsи первым делом отправьте сообщениеhello(см. пример ниже). - Сохраните токен из ответа
welcome— это единственный ключ учётки бота. При следующих входах присылайте его вhello, иначе сервер отвергнет подключение. - Пригласите бота на сервер: создайте ссылку-приглашение (настройки сервера → «Пригласить») и отправьте боту команду
join_serverс кодом из ссылки. - Читайте и пишите сообщения в каналах сервера — бот получает их так же, как обычный клиент.
Минимальный рабочий бот
Эхо-бот, который повторяет всё написанное в канале. Зависимость одна: pip install websockets.
# echo_bot.py — запустите один раз, токен сохранится сам
import asyncio, json, pathlib, uuid, websockets
SERVER = "wss://vortex-voice.com/ws"
ID_FILE = pathlib.Path("bot_identity.json") # id и токен бота
NAME = "🤖 Помощник" # до 32 символов
data = json.loads(ID_FILE.read_text()) if ID_FILE.exists() else {}
USER_ID = data.get("user_id") or ("bot-" + str(uuid.uuid4()))
async def main():
async with websockets.connect(SERVER) as ws:
await ws.send(json.dumps({
"type": "hello",
"user_id": USER_ID, # постоянный id бота
"username": NAME,
"kind": "bot", # честно представляемся ботом
"avatar_color": "#7c5cff",
"auth_token": data.get("auth_token", ""), # пусто при первом входе
}))
welcome = json.loads(await ws.recv()) # первый ответ сервера
if welcome.get("type") != "welcome":
print("Не пустили:", welcome); return
ID_FILE.write_text(json.dumps({ # id + токен — на диск
"user_id": USER_ID,
"auth_token": welcome["auth_token"],
}))
print("Бот в сети:", welcome["user"]["username"])
async def heartbeat(): # держим соединение живым
while True:
await asyncio.sleep(30)
await ws.send('{"type":"heartbeat"}')
asyncio.create_task(heartbeat())
async for raw in ws: # простое эхо
msg = json.loads(raw)
if msg.get("type") == "chat_message" and msg.get("user_id") != USER_ID:
await ws.send(json.dumps({
"type": "chat_message",
"channel_id": msg["channel_id"],
"content": "Ты написал: " + msg["content"],
}))
asyncio.run(main())
Чтобы бот попал на сервер, отправьте ему приглашение — код возьмите из ссылки вида …?invite=a1b2c3&kind=server:
→ {"type": "join_server", "invite_code": "a1b2c3"}
← {"type": "server_joined", "server": {…}, "channels": […]} // готово
bot_identity.json надёжно: если файл с токеном потерян, восстановить учётку нельзя (сервер не выдаёт токен повторно — иначе его мог бы перехватить первый встречный). Просто сгенерируйте новый user_id — бот начнёт с чистого листа.
2. Bot API: справочник
Протокол — тот же, что у браузерного клиента: JSON-сообщения по WebSocket. Подключение: wss://<ваш-сервер>/ws.
Подключение и авторизация
Первым сообщением бот отправляет hello. При самом первом входе сервер вернёт welcome со свежим auth_token — сохраните его и присылайте во всех следующих подключениях. Токен привязан к user_id: неверный или отсутствующий токен для существующей учётки — соединение закрывается с причиной auth_required.
// → первое сообщение после подключения
{
"type": "hello",
"user_id": "bot-6b944944…", // стабильный id бота (генерируется один раз)
"username": "🤖 Мой бот", // до 32 символов, эмодзи можно
"kind": "bot", // представьтесь ботом — это обязательно
"avatar_color": "#7c5cff",
"auth_token": "" // пусто при первом входе, дальше — из сохранённого
}
// ← ответ сервера
{ "type": "welcome", "auth_token": "…", "user": {…},
"servers": […], "channels": […], "users": […] }
Другие причины закрытия: rate_limited (больше 40 hello в минуту с одного IP), banned, account_deleted. Пока соединение открыто, имя и цвет аватарки можно менять, просто переподключившись с новым username в hello. Метка kind:"bot" ставится один раз и персистится — сниматься переподключением без поля нельзя.
Сообщения
| Тип | Направление | Назначение |
|---|---|---|
chat_message | ↕ | Сообщение в текстовый канал сервера: {"type":"chat_message","channel_id":"…","content":"текст"}. До 4000 символов; опционально reply_to_id для ответа. Входящие приходят только из каналов серверов, где бот участник. |
conversation_message | ↕ | Личка и групповые чаты: {"type":"conversation_message","conversation_id":"…","content":"текст"}. |
conversation_created | ← | Кто-то открыл личку с ботом — удобно поздороваться и рассказать, что он умеет. |
presence | ← | Обновления списка пользователей: статусы, текущий голосовой канал. |
heartbeat | ↕ | {"type":"heartbeat"} раз в ~30 секунд → в ответ heartbeat_ack. Любое другое исходящее сообщение тоже продлевает жизнь соединения. |
join_server / join_private_channel | → | Вступить по приглашению: {"type":"join_server","invite_code":"…"}. Код — из ссылки ?invite=…&kind=server|channel. |
server_joined / channel_created | ← | Результат вступления (сервер + список каналов) и появление новых каналов. |
error | ← | Текст ошибки: неверный код приглашения, бан на сервере, «вы заглушены» и т.п. |
Голосовые каналы и звук
В голосе бот — WebRTC-пир. Библиотека aiortc (Python) покрывает весь тракт. Каналы бывают двух типов — флаг is_sfu у канала подскажет, как подключаться; он приходит в списках каналов в welcome и server_joined.
Обычные (mesh) каналы — каждый участник соединяется с каждым напрямую. Бот отвечает на офферы участников и офферит сам тем, кто зашёл позже:
→ {"type":"voice_join","channel_id":"…"} // войти в голосовой канал
← {"type":"voice_peer_join","peer_id":"…"} // кто-то зашёл — офферим ему сами
← {"type":"webrtc_offer","from":"…","sdp":{…},"kind":"audio"}
→ {"type":"webrtc_answer","target":"…","sdp":{…},"kind":"audio"}
↕ {"type":"webrtc_ice", …} // ICE-кандидаты в обе стороны
→ {"type":"voice_leave"} // выйти
Mesh прост, но у него есть цена: бот кодирует отдельный поток для каждого слушателя и зависит от качества каждого P2P-пути. Для бота, которого слушают несколько человек, это заметная нагрузка, а при строгом NAT часть участников может вовсе не услышать бота.
SFU-каналы — звук идёт через сервер: у бота один-единственный пир (сам сервер), бот отправляет один поток, сервер раздаёт его всем слушателям. Один поток вместо N, никаких P2P-путей — там, где в mesh кто-то «не слышит бота» или звук лагает, SFU решает обе проблемы. Это тот же транспорт, что используют браузерные клиенты в SFU-каналах:
→ {"type":"voice_join","channel_id":"…"} // войти в канал, как обычно
→ {"type":"sfu_offer","channel_id":"…", // публикуем свой звук серверу
"sdp":{…}} // секция бота — sendonly,
// + 8 пустых recvonly-секций (слоты)
← {"type":"sfu_answer","sdp":{…},"tracks":{…}} // сервер принял поток
← {"type":"sfu_tracks","tracks":{…}} // карта «слот → кто говорит»
← {"type":"sfu_server_offer","sdp":{…}} // редкие пере-переговоры
→ {"type":"sfu_client_answer","sdp":{…}}
→ {"type":"sfu_ice","candidate":{…}} // ICE к серверу
← {"type":"sfu_republish_request"} // сервер не видит поток —
// перепубликуйте его заново
Три правила SFU-публикации. Первое: свою секцию объявляйте sendonly, а под слушателей предложите 8 пустых recvonly-секций прямо в первом offer'е — сервер развернёт их и будет класть в них голоса участников без пере-переговоров (как в браузерном клиенте). Второе: чужие дорожки из слотов боту обычно не нужны, но вычитывать их надо (иначе растёт буфер приёмника). Третье: если сервер прислал sfu_republish_request — соберите сессию заново (не чаще раза в 15 секунд).
Свой аудиотрек бот крутит вместо микрофона: 48 кГц, стерео, кадры по 20 мс — так бот может воспроизводить любой звук: музыку, уведомления, синтез речи. Если канал недоступен, сервер ответит voice_join_error с причиной.
3. Ограничения и правила
- Лимит входов: до 40
helloв минуту с одного IP; при превышении соединение закрывается с причинойrate_limited. - Молчащее соединение сервер закрывает: раз в ~30 секунд отправляйте
heartbeat(или любое другое сообщение). - Текст сообщения — до 4000 символов; более длинные сервер обрежет.
- Бот видит только те серверы, куда его впустили приглашением, и свои личные чаты.
- Боты не модераторы. Помеченный
kind:"bot"бот не получает прав модерации на чужих серверах — даже если владелец по ошибке выдал ему роль с правом бана или кика. На своём собственном сервере бот-владелец волен делать что угодно. Человек в списках видит ботов по бейджу «БОТ». - Не используйте ботов для спама и флуда: аккаунты-нарушители блокируются, а удалённый администратором аккаунт не воскресает повторным подключением.
- Один аккаунт может держать несколько одновременных подключений (несколько процессов с одним
user_id) — старые соединения не вытесняются. Удобно, если бот одновременно и пишет в чат, и живёт в голосовом канале.