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

Go SDK

Официального SDK XLADA на Go нет. API XLADA идентичен API Remnawave, поэтому клиент собирается на стандартной библиотеке net/http без единой внешней зависимости — так вы не зависите от чужого релизного цикла.

Совместимость

Для Remnawave сообщество поддерживает Go-SDK, сгенерированный из той же OpenAPI-спецификации. XLADA отвечает на те же пути, поэтому он применим и здесь, но официально с XLADA мы его не тестировали. Пример ниже ни от каких модулей не зависит.

Установка​

Нужен Go 1.18+ (используется any) и модуль проекта:

mkdir xlada-bot && cd xlada-bot
go mod init example.com/xlada-bot

Внешние пакеты не требуются — только net/http, encoding/json и context.

Клиент​

Токен — это либо JWT от POST /api/auth/login, либо долгоживущий API-токен от POST /api/tokens. Оба передаются в заголовке Authorization: Bearer. При прямом обращении к панели добавьте заголовки X-Forwarded-For и X-Forwarded-Proto: https, иначе middleware панели разорвёт соединение.

package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)

type Client struct {
baseURL string
token string
http *http.Client
}

func NewClient(baseURL, token string) *Client {
return &Client{
baseURL: baseURL,
token: token,
http: &http.Client{Timeout: 30 * time.Second},
}
}

func (c *Client) do(ctx context.Context, method, path string, body, out any) error {
var payload []byte
if body != nil {
var err error
if payload, err = json.Marshal(body); err != nil {
return err
}
}

req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("X-Forwarded-For", "127.0.0.1")
req.Header.Set("X-Forwarded-Proto", "https")

res, err := c.http.Do(req)
if err != nil {
return err
}
defer res.Body.Close()

if res.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("xlada: %s %s: %s", method, path, res.Status)
}

if out != nil {
return json.NewDecoder(res.Body).Decode(out)
}
return nil
}

Список пользователей​

Постраничный вывод задаётся query-параметрами start (смещение) и size (размер страницы, 1–1000).

type user struct {
ID int `json:"id"`
Username string `json:"username"`
Status string `json:"status"`
ShortUUID string `json:"shortUuid"`
ExpireAt string `json:"expireAt"`
}

type usersResponse struct {
Response struct {
Users []user `json:"users"`
Total int `json:"total"`
} `json:"response"`
}

func listUsers(ctx context.Context, c *Client) error {
var out usersResponse
if err := c.do(ctx, http.MethodGet, "/api/users?start=0&size=25", nil, &out); err != nil {
return err
}

for _, u := range out.Response.Users {
fmt.Printf("%s\t%s\t%s\n", u.Username, u.Status, u.ExpireAt)
}
fmt.Printf("всего: %d\n", out.Response.Total)
return nil
}

Создание пользователя​

POST /api/users принимает username (3–36 символов, только a-z, A-Z, 0-9, _, -) и обязательный expireAt в формате ISO 8601.

type createUserRequest struct {
Username string `json:"username"`
Status string `json:"status,omitempty"`
ExpireAt string `json:"expireAt"`
TrafficLimitBytes int64 `json:"trafficLimitBytes,omitempty"`
TrafficLimitStrategy string `json:"trafficLimitStrategy,omitempty"`
}

func createUser(ctx context.Context, c *Client) error {
var out struct {
Response struct {
ID int `json:"id"`
ShortUUID string `json:"shortUuid"`
} `json:"response"`
}

err := c.do(ctx, http.MethodPost, "/api/users", createUserRequest{
Username: "john_doe",
Status: "ACTIVE",
ExpireAt: "2026-01-17T15:38:45.065Z",
TrafficLimitStrategy: "NO_RESET",
}, &out)
if err != nil {
return err
}

fmt.Printf("создан id=%d, shortUuid=%s\n", out.Response.ID, out.Response.ShortUUID)
return nil
}

Получение ноды​

Конкретная нода запрашивается по uuid, список нод — по GET /api/nodes.

func getNode(ctx context.Context, c *Client, uuid string) error {
var out struct {
Response struct {
Name string `json:"name"`
Address string `json:"address"`
Port *int `json:"port"`
IsConnected bool `json:"isConnected"`
} `json:"response"`
}

if err := c.do(ctx, http.MethodGet, "/api/nodes/"+uuid, nil, &out); err != nil {
return err
}

fmt.Printf("%s (%s) connected=%v\n", out.Response.Name, out.Response.Address, out.Response.IsConnected)
return nil
}
Совет

Все структуры ответов описаны в спецификации на странице API Specification. Теги json берите прямо оттуда — поля панели называются в camelCase.

Что дальше​