Довідник API поки що доступний лише російською. Назви методів, шляхи й приклади однакові для всіх мов.
Realtime
Содержание
Обзор#
- Адрес:
wss://unixgram.com/unixproto. Кадры — двоичные сообщения WebSocket в MessagePack. - Порядок: получить токен через Client API → открыть сокет →
hello→auth→ дождатьсяauth_ok→ получатьrealtime_event. - Канал только для получения. Действия — отправка, прочтение, реакции — выполняются обычными методами Client API.
- Ботам сокет не нужен — у них getUpdates и вебхуки.
Подключение#
Токен#
GET /api/unixproto/token с cookie сессии → {"token", "expiresAt"}. Токен живёт 5 минут и нужен только для рукопожатия; на каждое переподключение берите новый.
HELLO и AUTH#
Первыми двумя кадрами клиент представляется и предъявляет токен:
{"version": 1, "id": "c1", "type": "hello", "timestamp": 1759500000000,
"payload": {"client": "web", "appVersion": "my-client/1.0", "protocolVersion": 16, "layer": 1,
"sessionId": "my-device-1",
"init": {"deviceModel": "Raspberry Pi", "systemVersion": "Linux 6.6", "langCode": "ru", "systemLangCode": "ru"}}}
{"version": 1, "id": "c2", "type": "auth", "timestamp": 1759500000001, "payload": {"token": "<токен>"}}| Поле | Тип | Описание |
|---|---|---|
client | String | web, android, ios или server. Сторонним программам — web или server |
appVersion | String | Название и версия программы, до 32 символов |
protocolVersion | Integer | 15–17; документ описывает 16 |
sessionId | String | Постоянный идентификатор установки, до 64 символов |
init | Object | Модель, ОС и языки — видны в списке устройств, каждое поле до 64 символов |
resume | Object | {sessionId, lastSeq} — продолжить прежнюю сессию и дослать пропущенное после обрыва |
Ответ — auth_ok. После него отправьте {"type": "presence_state", "payload": {"foreground": true}} — так вы будете «в сети», а не «был недавно».
Кадры#
| Поле | Тип | Описание |
|---|---|---|
version | Integer | Всегда 1 |
id | String | Уникальный идентификатор кадра |
type | String | realtime_event, ack, pong, error, … |
timestamp | Integer | Миллисекунды Unix |
seq | Integer | Номер в журнале сессии — для msgs_ack и resume |
important | Boolean | Кадр нужно подтвердить ack |
payload | Object | У realtime_event — {event, data, pts?, ptsCount?, boxKey?} |
{"version": 1, "id": "s91", "type": "realtime_event", "timestamp": 1759500004210, "seq": 42,
"payload": {"event": "message:new", "pts": 1759500004210123, "boxKey": "user:cmh1x…",
"data": {"conversationId": "cmh7q…", "message": {"id": "cmh7r…", "content": "Привет", …}}}}Подтверждения#
- Кадр с
important: trueподтверждайте сразу:{"type": "ack", "ack": "<id кадра>", "payload": {}}. Неподтверждённый кадр сервер пришлёт снова. - Номера
seqподтверждайте пачкой раз в секунду:{"type": "msgs_ack", "payload": {"seqs": [41, 42]}}.
Пинг#
Раз в 15 секунд отправляйте {"type": "ping_delay_disconnect", "payload": {"pingId": "…", "disconnectDelayMs": 45000}}: пинг и обещание молчать не дольше 45 секунд. Если от сервера ничего не приходило дольше этого времени, соединение мёртвое — переподключитесь.
Коды закрытия#
| Поле | Тип | Описание |
|---|---|---|
4401 | auth | Токен отвергнут — возьмите новый и переподключитесь |
4403 | forbidden | Доступ запрещён |
4413 | backpressure | Вы читали слишком медленно — переподключитесь и дочитайте пропущенное |
4426 | protocol too old | Версия протокола ниже 15 — переподключение не поможет |
4429 | rate limited | Слишком часто — подождите 30 секунд |
Переподключайтесь с паузой 1, 2, 4… секунды, не дольше 5 секунд при сетевых обрывах и 30 — при отказах сервера (коды 4xxx).
Пример#
Python: pip install websockets msgpack requests. Печатает новые сообщения.
import asyncio, time, uuid, msgpack, requests, websockets
API = "https://unixgram.com/api"
s = requests.Session()
s.get(f"{API}/auth/csrf")
s.post(f"{API}/auth/login", json={"email": "me@example.com", "password": "••••••••"},
headers={"x-csrf-token": s.cookies["csrf_token"]})
def frame(type_, payload=None, **extra):
body = {"version": 1, "id": uuid.uuid4().hex, "type": type_,
"timestamp": int(time.time() * 1000), **extra}
if payload is not None:
body["payload"] = payload
return msgpack.packb(body)
async def main():
token = s.get(f"{API}/unixproto/token").json()["data"]["token"]
async with websockets.connect("wss://unixgram.com/unixproto") as ws:
await ws.send(frame("hello", {"client": "web", "appVersion": "printer/1.0", "protocolVersion": 16}))
await ws.send(frame("auth", {"token": token}))
async for raw in ws:
f = msgpack.unpackb(raw)
if f.get("important"):
await ws.send(frame("ack", {}, ack=f["id"]))
if f.get("seq") is not None:
await ws.send(frame("msgs_ack", {"seqs": [f["seq"]]}))
if f["type"] == "auth_ok":
await ws.send(frame("presence_state", {"foreground": True}))
if f["type"] == "realtime_event" and f["payload"]["event"] == "message:new":
message = f["payload"]["data"]["message"]
print(message["sender"]["username"], ":", message["content"])
asyncio.run(main())События#
durable — у события есть строка в базе и номер в журнале (pts): пропущенное можно дочитать. ephemeral — доставляется и забывается (набор текста, присутствие, счётчики): пропустили — перечитайте состояние обычным методом.
| event | Класс | Когда |
|---|---|---|
message:new | durable | Новое сообщение в чате: conversationId, message, selfEcho (ваше же сообщение с другого устройства) |
message:deleted | durable | Сообщения удалены: conversationId, messageIds |
message:read | ephemeral | Собеседник прочитал ваши сообщения до момента at |
message:typing | ephemeral | Кто-то печатает: conversationId, userId |
conversation:update | ephemeral | Чат изменился (правка, реакция, закреп) — перечитайте его |
counter:unread | ephemeral | Изменилось число непрочитанных |
notification:new | durable | Новое уведомление |
notification:read | ephemeral | Уведомления прочитаны на другом устройстве |
channel:post:new | durable | Новый пост в канале, на который вы подписаны |
channel:comments | ephemeral | Новый или удалённый комментарий в открытой вами ветке: conversationId, postId |
comment:new | durable | Новый комментарий к вашему посту |
post:updated | ephemeral | Пост изменился (лайки, правка) |
feed:new | ephemeral | В ленте есть новые посты |
user:followed | ephemeral | На вас подписались |
user:updated | ephemeral | Профиль пользователя изменился |
gift:received | durable | Вам подарили подарок |
gift:offer | durable | Предложение цены за ваш подарок |
gift:updated | ephemeral | Подарок изменился (продажа, улучшение) |
stars:balance | ephemeral | Изменился баланс звёзд |
streak:update | ephemeral | Изменилась серия |
call:incoming | ephemeral | Входящий звонок |
call:accepted · call:declined · call:busy · call:ended | ephemeral | Состояние звонка |
call:signal | ephemeral | Сигнализация WebRTC (SDP, ICE) |
secret:request · secret:accept · secret:reject · secret:close | durable | Жизненный цикл секретного чата |
secret:message · secret:read · secret:deleted | durable | Сообщения секретного чата (шифротекст) |
secret:typing | ephemeral | Печатает в секретном чате |
bot:callbackAnswer | ephemeral | Ответ бота на нажатие кнопки: текст подсказки или окна |
Список событий может пополняться — пропускайте незнакомые имена.
Пропущенные события#
У durable-событий есть pts и boxKey — номер в журнале и имя журнала (ваш личный, канал и т. п.). Запоминайте последний pts каждого журнала; после обрыва дочитайте разницу:
GET /api/updates/state→{"boxes": [{"boxKey", "pts"}, …]}— с чего начинать при первом входе.GET /api/updates/difference?box=<boxKey>&pts=<последний>→ один из ответов:state— ничего не пропущено;difference/differenceSlice—entriesпропущенных событий; у slice повторите запрос с новымpts;differenceTooLong— пропущено больше, чем хранит журнал (512 записей или 48 часов): перечитайте чаты и уведомления обычными методами.