Кастомные балансеры

Кастомные балансеры

Кастомные балансеры позволяют подключать собственные источники контента без изменения основного кода сервера. Каждый балансер — standalone Go-программа, которая запускается как subprocess.

Как это работает

Lampa (клиент)
    |
lampac-go (порт 888)
    |
    +-- /lite/source/rezka       --> встроенный балансер
    +-- /lite/source/collaps     --> встроенный балансер
    +-- /lite/source/mybalancer  --> reverse proxy --> 127.0.0.1:50001 (subprocess)

Сервер при старте:

  1. Сканирует custom_balancers/ на наличие поддиректорий с config.json
  2. Компилирует и запускает балансеры с auto_start: true
  3. Проксирует запросы /lite/source/{name} на порт subprocess-а
  4. Мониторит состояние (перезапуск при падении)

Создание балансера

Структура файлов

custom_balancers/
  mybalancer/
    main.go         <-- исходный код
    config.json     <-- конфигурация
    go.mod          <-- создаётся автоматически
    mybalancer      <-- бинарник (после компиляции)

config.json

{
  "name": "mybalancer",
  "display_name": "My Balancer",
  "port": 0,
  "quality_badge": "FHD",
  "content_type": "both",
  "host": "https://example-source.com",
  "auto_start": true
}
ПолеТипОписание
namestringУникальный ID (a-zA-Z0-9_-, макс. 64 символа)
display_namestringНазвание в интерфейсе
portintTCP-порт (0 = автоматически, начиная с 50000)
quality_badgestringБейдж: SD, HD, FHD, 4K
content_typestringmovie, serial или both
hoststringURL источника (передаётся флагом -host)
auto_startboolЗапускать при старте сервера

main.go — минимальный шаблон

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "log"
    "net/http"
)

func main() {
    port := flag.Int("port", 50100, "listen port")
    mainHost := flag.String("main-host", "", "main server address")
    host := flag.String("host", "", "upstream source URL")
    token := flag.String("token", "", "API token")
    flag.Parse()

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        q := r.URL.Query()

        // Проверка доступности (для кросс-поиска)
        if q.Get("checksearch") == "true" {
            json.NewEncoder(w).Encode(map[string]any{"rch": true})
            return
        }

        // Основной поиск
        handleSearch(w, r, *host, *mainHost)
    })

    addr := fmt.Sprintf("127.0.0.1:%d", *port)
    log.Printf("mybalancer: listening on %s", addr)
    log.Fatal(http.ListenAndServe(addr, mux))
}

Флаги запуска

Сервер автоматически передаёт subprocess-у:

ФлагОписание
-portTCP-порт для прослушивания
-main-hostАдрес основного сервера (для проксирования потоков)
-hostUpstream URL из config.json
-tokenAPI-токен (опционально)

HTTP API

Балансер реализует единственный endpoint GET / с query-параметрами.

Входные параметры

ПараметрОписание
checksearchtrue — быстрая проверка (кросс-поиск)
kinopoisk_idID Кинопоиска
imdb_idIMDB ID
titleРусское название
original_titleОригинальное название
yearГод выхода
serial1 — сериал
sНомер сезона (-1 = список сезонов)
eНомер серии
tОзвучка
hrefURL конкретного результата
rjsontrue — всегда JSON

checksearch

Быстрая проверка — есть ли контент в этом источнике.

GET /?checksearch=true&kinopoisk_id=123&title=Movie
{"rch": true}

Форматы ответов

Все ответы — JSON с полем type:

{
  "type": "movie",
  "data": [{
    "method": "play",
    "url": "https://cdn.example.com/stream.m3u8",
    "stream": "https://cdn.example.com/stream.m3u8",
    "name": "Название",
    "title": "Название / Original Title"
  }]
}
{
  "type": "similar",
  "data": [{
    "method": "link",
    "url": "/lite/source/mybalancer?kinopoisk_id=123&href=...",
    "name": "Вариант 1",
    "year": 2024,
    "img": "https://poster.jpg"
  }]
}
{
  "type": "season",
  "data": [{
    "method": "link",
    "url": "/lite/source/mybalancer?kinopoisk_id=123&serial=1&s=1",
    "name": "1 сезон"
  }]
}
{
  "type": "episode",
  "data": [{
    "method": "play",
    "url": "https://cdn.example.com/s01e01.m3u8",
    "s": 1, "e": 1,
    "name": "Серия 1",
    "title": "Название / Original"
  }]
}
{"type": "empty", "data": []}

Методы в data

methodОписание
playПрямая ссылка на поток — запуск плеера
callURL для повторного запроса (lazy-load)
linkНавигационная ссылка (выбор сезона/варианта)

Проксирование потоков

Потоковые URL нужно оборачивать через proxystream основного сервера — для CORS и шифрования ссылок:

func buildStreamUrl(link, mainHost string, r *http.Request) string {
    if mainHost == "" { return link }

    body, _ := json.Marshal(map[string]string{
        "url":    link,
        "plugin": "mybalancer",
    })
    req, _ := http.NewRequest("POST", mainHost+"/api/proxystream",
        bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")

    // Пробросить заголовки для корректного proxy URL
    for _, h := range []string{"X-Forwarded-For", "X-Forwarded-Host",
        "X-Forwarded-Proto"} {
        if v := r.Header.Get(h); v != "" {
            req.Header.Set(h, v)
        }
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil || resp.StatusCode != 200 { return link }
    defer resp.Body.Close()

    var result struct{ ProxyURL string `json:"proxy_url"` }
    json.NewDecoder(resp.Body).Decode(&result)
    if result.ProxyURL != "" { return result.ProxyURL }
    return link
}

Деплой

Через админ-панель

На вкладке «Конструктор» можно:

  • Загрузить Go-код балансера
  • Указать config (имя, хост, качество)
  • Нажать «Deploy» — код скомпилируется и запустится автоматически

Через API

# Deploy (создать + скомпилировать + запустить)
curl -X POST "https://server/{admin}/api/custbal" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "deploy",
    "name": "mybalancer",
    "code": "package main...",
    "config": {"display_name": "My Balancer", "quality_badge": "FHD", "auto_start": true}
  }'

# Перекомпилировать
curl -X POST ... -d '{"action": "compile", "name": "mybalancer"}'

# Запуск / Стоп / Рестарт
curl -X POST ... -d '{"action": "start", "name": "mybalancer"}'
curl -X POST ... -d '{"action": "stop", "name": "mybalancer"}'
curl -X POST ... -d '{"action": "restart", "name": "mybalancer"}'

# Удалить
curl -X POST ... -d '{"action": "remove", "name": "mybalancer"}'

Вручную

mkdir -p custom_balancers/mybalancer
# Положить main.go и config.json
# Перезапустить сервер — или через API: compile + start

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

stopped  --start-->  starting  --TCP ready-->  running
   ^                                             |
   |                                         crash/stop
   +---------------------------------------------+
                                              error
  • starting — subprocess запущен, ожидание TCP (15 сек)
  • running — порт отвечает, трафик проксируется
  • error — subprocess упал или не ответил
  • stopped — остановлен (SIGINT, 5 сек, затем SIGKILL)

Конвертер C# -> Go

Через вкладку «Конструктор» можно конвертировать C# балансеры из старого Lampac:

  1. Вставить C# код
  2. Нажать «Портировать»
  3. LLM конвертирует в Go и автоматически деплоит
Для работы конвертера нужно настроить секцию [llm] в config.toml — endpoint и API-ключ.

Рекомендации

  • Кэшируйте ответы upstream-а в памяти (TTL 10-30 мин)
  • checksearch должен работать быстро (< 5 сек)
  • Используйте proxystream для потоковых URL
  • Балансер должен быть standalone Go-программой без CGO
  • Рабочий пример: custom_balancers/uaflix/main.go