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

Plugin API v1

Справочник для разработчиков плагина ExTrack. Плагин живёт на игровом сервере и общается с панелью по HTTPS. Для Java есть готовый клиент: sdk/java. Он закрывает всё, что описано ниже: пачки логов, gzip, повторы при обрывах связи, очередь команд и защиту входа персонала.

  • Базовый адрес: https://<ваш домен>/api/plugin/v1
  • Формат: JSON, UTF-8
  • Авторизация: заголовок Authorization: Bearer et_.... Токен выдаётся при создании сервера и пересоздаётся в «Настройки → Подключение». Старый токен перестаёт работать сразу.
  • Время: Unix-время в миллисекундах (time), ответы сервера в ISO 8601 UTC.
  • Сжатие: тело можно отправлять с Content-Encoding: gzip, лимит после распаковки 8 МБ.

Ошибки

{
  "error": {
    "code": "server_locked",
    "message": "Сервер заморожен: он не входит в лимит тарифа владельца"
  }
}
HTTP code Что делать
400 validation_failed, bad_request Ошибка в данных, повтор не поможет
401 bad_token Токен неверный или пересоздан. Остановить отправку, написать в консоль
403 ip_not_allowed IP сервера не в белом списке (настраивается в панели)
423 server_locked Сервер заморожен по тарифу. Повторять раз в 5 минут
429 rate_limited Слишком часто. Подождать и повторить
5xx internal Повторить с экспоненциальной задержкой

Лимиты на сервер: события 600 запросов в минуту, heartbeat 20, остальное от 60 до 300.

Жизненный цикл

  1. При запуске POST /handshake. Ответ содержит настройки, список персонала и состояние защиты входа.
  2. Каждые settings.heartbeatSeconds секунд POST /heartbeat.
  3. События копятся в памяти и уходят пачками раз в settings.flushSeconds секунд или при наборе settings.maxBatch.
  4. Отдельный поток держит long-poll GET /actions?wait=25 и выполняет команды из панели, затем POST /actions/ack.
  5. При выключении сервера отправить остаток буфера.

Если сервер не присылал heartbeat 3 минуты, панель считает его выключенным: закрывает сессии игроков и шлёт уведомление владельцу.


POST /handshake

{
  "pluginVersion": "1.0.0",
  "platform": "paper",
  "mcVersion": "1.21.4",
  "onlineMode": false,
  "maxPlayers": 200,
  "plugins": [
    { "name": "Essentials", "version": "2.21.0", "main": "com.earth2me.essentials.Essentials" },
    {
      "name": "LuckPerms",
      "version": "5.4.140",
      "main": "me.lucko.luckperms.bukkit.loader.BukkitLoaderPlugin"
    }
  ]
}

platform: paper, spigot, folia, purpur, velocity, bungeecord, fabric, forge.

plugins необязателен: по пакету главного класса панель определяет, какой плагин вызвал ошибку в консоли.

Ответ:

{
  "apiVersion": 1,
  "server": { "id": "uuid", "name": "Demo SMP", "timezone": "Europe/Moscow" },
  "locked": false,
  "protection": { "enabled": true, "maxAttempts": 5, "timeoutSeconds": 300 },
  "staff": [
    {
      "uuid": "...",
      "name": "LenaMoon",
      "nickname": "Lena_mod",
      "role": "Модератор",
      "isOwner": false
    }
  ],
  "settings": {
    "heartbeatSeconds": 30,
    "flushSeconds": 5,
    "maxBatch": 500,
    "actionsWaitSeconds": 25
  }
}

Повторять при получении действия sync.

POST /heartbeat

{
  "online": 57,
  "maxPlayers": 200,
  "tps": 19.94,
  "mspt": 23.1,
  "ramUsed": 6144,
  "ramMax": 12288,
  "chunks": 4200,
  "entities": 9100,
  "players": ["069a79f4-44e9-4726-a5be-fca90e38aaf5"]
}

ramUsed, ramMax в мегабайтах. players необязателен, но лучше передавать: по нему панель закрывает сессии игроков, чей выход потерялся (краш, kill -9).

POST /events

{
  "events": [
    {
      "type": "join",
      "time": 1759340000000,
      "player": { "uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "name": "Notch" },
      "ip": "203.0.113.5",
      "client": { "brand": "vanilla", "version": "1.21.4" },
      "groups": ["default", "vip"]
    },
    {
      "type": "block_break",
      "player": { "uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "name": "Notch" },
      "world": "world",
      "x": 10,
      "y": 64,
      "z": -5,
      "data": { "block": "minecraft:diamond_ore" }
    }
  ]
}

До 2000 событий за запрос. Ответ: { "accepted": 2, "rejected": 0 }. Невалидные события отбрасываются по одному, пачка целиком не отклоняется.

Поле Тип
type string обязательно, см. таблицу ниже
time number мс, по умолчанию время приёма. Допустимо до 7 дней в прошлое
player {uuid, name} обязательно для join и quit
target {uuid?, name} второй участник: жертва, получатель ЛС, партнёр по обмену
world, x, y, z string, int координаты, по ним работает поиск «кто ломал здесь»
message string до 2000 символов
data object произвольные поля, до 4 КБ
ip, client только для join: IP для поиска твинков, бренд и версия клиента
groups string[] только для join: группы прав игрока, например из LuckPerms

Ники проверяются по шаблону [A-Za-z0-9_.*-]{1,32}, так что игроки Bedrock через Floodgate проходят.

Типы событий

Категория Типы
Входы join, quit, world_change, teleport, gamemode
Чат chat, command, private_message, sign_edit, book_edit, anvil_rename
Мир block_break, block_place, explosion
Предметы container_open, container_take, container_put, item_drop, item_pickup, craft
Бой death, kill, mob_kill
Экономика trade, economy, shop, donation
Модерация punishment, anticheat, report, staff_auth
Прочее advancement, custom

donation - покупка за реальные деньги, из неё считаются выручка, ARPPU и популярные товары на вкладке «Аналитика → Донатеры». Поле data строгое: { "amount": 349, "currency": "RUB", "product": "Premium на 30 дней" }, amount в целых единицах валюты, currency по умолчанию RUB. Событие без суммы или без игрока отклоняется. Удобнее всего отправлять его из обработчика оплаты вашего донат-магазина.

IP из join используется для определения страны на вкладке «География», сам адрес видят только сотрудники с доступом к IP.

private_message видят только сотрудники с правом «Личные сообщения и скрытые команды».

Аргументы команд авторизации (/login, /register, /changepassword, /l, /reg, /cp, /2fa, /email, /extrack code и другие) маскируются сервером при приёме. Плагину тоже стоит маскировать их до отправки.

POST /tickets

Обращение из игры, например по /report <ник> <текст> или /bug <текст>.

{
  "type": "complaint",
  "message": "Грифер сломал дом",
  "player": { "uuid": "...", "name": "Notch" },
  "target": { "name": "Griefer" },
  "location": { "world": "world", "x": 1240, "y": 70, "z": -320 }
}

Ответ { "number": 42 }. Не больше 3 обращений от одного игрока за 10 минут (429).

POST /punishments

Синхронизация наказаний, выданных в игре (LiteBans, AdvancedBan, Essentials).

{
  "id": "litebans-ban-1234",
  "type": "ban",
  "player": { "uuid": "...", "name": "Griefer" },
  "reason": "Гриферство",
  "durationMinutes": 10080,
  "issuer": { "uuid": "...", "name": "LenaMoon" },
  "time": 1759340000000
}

type: warn, mute, kick, ban, ipban. durationMinutes: null значит навсегда. id из плагина наказаний защищает от дублей. Если issuer.uuid привязан к сотруднику, наказание попадёт в его статистику.

POST /punishments/revoke

{ "id": "litebans-ban-1234", "by": "LenaMoon", "reason": "Апелляция одобрена" }

или по игроку: { "player": {...}, "type": "mute" }. Ответ { "revoked": 1 }.

POST /groups

Группы, изменившиеся между входами: купленный или истёкший донат, повышение. Группы при входе передаются в событии join, этот запрос нужен только для изменений во время игры. С LuckPerms его удобно вызывать из UserDataRecalculateEvent.

{
  "players": [
    {
      "player": { "uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "name": "Notch" },
      "groups": ["default", "premium"]
    }
  ]
}

До 500 игроков за запрос. Передавайте полный список текущих групп: всё, чего в нём нет, считается снятым. Имена приводятся к нижнему регистру. Какие группы считать донатерскими, владелец выбирает в панели.

В Java SDK:

client.log(LogEvent.join(player, ip, brand, version, groups));
client.playerGroups(player, groups);
client.log(LogEvent.donation(player, 349, "RUB", "Premium на 30 дней"));

POST /console

Предупреждения и ошибки из консоли сервера для вкладки «Ошибки». Отправляйте строки уровня WARN и выше, до 500 за запрос, не чаще раза в 10 секунд. Повторы одной и той же ошибки панель сама объединяет, поэтому дедупликация на стороне плагина не нужна.

{
  "records": [
    {
      "time": 1790000000000,
      "level": "ERROR",
      "logger": "Minecraft",
      "thread": "Server thread",
      "message": "Could not pass event PlayerInteractEvent to ShopGUIPlus v1.98.2",
      "throwable": "java.lang.NullPointerException: ...\n\tat net.brcdev.shopgui.listener.ShopListener.onClick(ShopListener.java:88)"
    }
  ]
}
Поле Описание
level WARN, WARNING, ERROR, SEVERE или FATAL, остальные отбрасываются
logger имя логгера, у плагинов совпадает с названием плагина
message текст строки, цветовые коды удаляются на сервере
throwable трассировка стека целиком, если есть

Ответ: { "accepted": 1, "rejected": 0 }.

На Paper консоль пишется через Log4j2: достаточно добавить аппендер с фильтром WARN к корневому логгеру. Готовый пример есть в sdk/java/examples/PaperExample.java. Строки собственного логгера плагина ExTrack отправлять не нужно, иначе ошибка соединения будет порождать новые ошибки.

Защита входа персонала

Если владелец включил защиту, сотрудник после входа на сервер должен ввести код, который приходит ему на сайт (и в Telegram, если привязан). До ввода кода плагин должен держать игрока «замороженным»: запрет движения, чата, команд (кроме /extrack code), инвентаря и урона.

POST /staff/login

Вызывать на каждый вход игрока.

{ "player": { "uuid": "...", "name": "LenaMoon" }, "ip": "203.0.113.5" }
Ответ Действие
{ "staff": false } обычный игрок
{ "staff": true, "required": false } сотрудник, защита выключена или IP доверенный
{ "staff": true, "required": true, "challengeId": "uuid", "expiresIn": 300, "maxAttempts": 5 } заморозить, ждать код

Если API недоступен, а игрок есть в staff из последнего handshake и защита включена, безопаснее кикнуть с просьбой зайти позже.

POST /staff/verify

{ "challengeId": "uuid", "code": "483920" }
Ответ Действие
{ "ok": true } разморозить
{ "ok": false, "attemptsLeft": 3 } неверный код
{ "ok": false, "locked": true } попытки кончились, кикнуть. Владелец получит уведомление
{ "ok": false, "reason": "expired" } время вышло или вход отклонён на сайте, кикнуть

POST /staff/link

Привязка игрового аккаунта к сотруднику. Сотрудник получает код в карточке сервера на сайте и вводит в игре /extrack link <код>.

{ "player": { "uuid": "...", "name": "LenaMoon" }, "code": "K7M2PX" }

Ответ { "ok": true, "nickname": "Lena_mod" } или { "ok": false, "reason": "invalid" | "taken" }. После успешной привязки повторите handshake, чтобы обновить список персонала.

Очередь команд

GET /actions?wait=25

Long-poll: если команд нет, сервер держит запрос до wait секунд (максимум 30) и отвечает сразу, как только команда появится. Таймаут клиента ставьте на 10-15 секунд больше wait.

{
  "actions": [
    {
      "id": 17,
      "type": "command",
      "payload": { "command": "tempban Griefer 7d Гриферство", "punishmentId": "uuid" }
    },
    {
      "id": 18,
      "type": "message",
      "payload": { "uuid": "...", "text": "[Обращение #42] Lena_mod: проверяем" }
    },
    {
      "id": 19,
      "type": "kick",
      "payload": { "uuid": "...", "reason": "Вход отклонён владельцем аккаунта" }
    },
    { "id": 20, "type": "sync", "payload": { "what": "config" } }
  ]
}
type Что сделать
command выполнить от консоли, если команда есть в белом списке плагина (allowed-commands)
message отправить сообщение игроку, если он онлайн
kick кикнуть игрока
sync повторить handshake

Команды собираются из шаблонов наказаний, которые редактирует владелец. Белый список на стороне плагина обязателен: он защищает сервер, даже если аккаунт в панели скомпрометирован.

Неподтверждённая команда выдаётся повторно через 60 секунд, максимум 5 раз, затем помечается как просроченная.

POST /actions/ack

{
  "results": [
    { "id": 17, "ok": true },
    { "id": 18, "ok": false, "message": "Игрок не в сети" }
  ]
}

Для команд наказаний результат виден в карточке игрока: «применено» или текст ошибки.

GET /players/{uuid}

Сводка для игровых команд вроде /extrack info <ник>:

{
  "player": {
    "id": 531,
    "name": "Notch",
    "firstSeenAt": "2026-09-01T10:00:00Z",
    "playtimeSeconds": 86400,
    "watched": false,
    "activePunishments": [
      { "type": "mute", "reason": "Флуд", "expiresAt": "2026-10-02T10:00:00Z", "createdAt": "..." }
    ],
    "notes": [
      { "body": "Подозрение на X-Ray", "pinned": true, "createdAt": "...", "author": "Lena_mod" }
    ],
    "alts": 2
  }
}

Проверка вручную

TOKEN=et_...
curl -s https://extrack.ru/api/plugin/v1/handshake \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"pluginVersion":"dev","platform":"paper"}'