package remnawave import ( "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) // Client обращается к Remnawave Panel API: // - Base: REMNAWAVE_PANEL_URL (например https://panel.example.com) // - Auth: Authorization: Bearer REMNAWAVE_API_TOKEN // - Опционально: X-Api-Key: CADDY_AUTH_API_TOKEN // Документация: https://docs.rw/docs/install/subscription-page/bundled type Client struct { panelURL string token string caddyToken string http *http.Client } func NewClient(panelURL, apiToken, caddyAuthToken string) *Client { return &Client{ panelURL: strings.TrimRight(panelURL, "/"), token: apiToken, caddyToken: caddyAuthToken, http: &http.Client{ Timeout: 15 * time.Second, }, } } func (c *Client) PanelURL() string { return c.panelURL } func (c *Client) countFromEndpoint(ctx context.Context, path, arrayKey string) (int, error) { resp, body, err := c.get(ctx, path) if err != nil { return 0, err } if resp.StatusCode != http.StatusOK { return 0, fmt.Errorf("HTTP %d", resp.StatusCode) } return parseCount(body, arrayKey), nil } func (c *Client) get(ctx context.Context, path string) (*http.Response, []byte, error) { return c.doRequest(ctx, c.panelURL+path, true) } func (c *Client) getPublic(ctx context.Context, path string) (*http.Response, []byte, error) { return c.doRequest(ctx, c.panelURL+path, false) } func (c *Client) doRequest(ctx context.Context, url string, withBearer bool) (*http.Response, []byte, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } req.Header.Set("Accept", "application/json, text/html, */*") if withBearer { req.Header.Set("Authorization", "Bearer "+c.token) } if c.caddyToken != "" { req.Header.Set("X-Api-Key", c.caddyToken) } resp, err := c.http.Do(req) if err != nil { return nil, nil, fmt.Errorf("нет связи с панелью: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { return resp, nil, err } return resp, body, nil } func parseCount(body []byte, arrayKey string) int { var raw map[string]json.RawMessage if err := json.Unmarshal(body, &raw); err != nil { return 0 } if n := countInRaw(raw["response"], arrayKey); n > 0 { return n } return countInRaw(json.RawMessage(body), arrayKey) } func countInRaw(data json.RawMessage, arrayKey string) int { if len(data) == 0 { return 0 } var obj map[string]json.RawMessage if err := json.Unmarshal(data, &obj); err != nil { var arr []json.RawMessage if err := json.Unmarshal(data, &arr); err == nil { return len(arr) } return 0 } if totalRaw, ok := obj["total"]; ok { var total int if err := json.Unmarshal(totalRaw, &total); err == nil && total > 0 { return total } } if items, ok := obj[arrayKey]; ok { var arr []json.RawMessage if err := json.Unmarshal(items, &arr); err == nil { return len(arr) } } for _, v := range obj { var arr []json.RawMessage if err := json.Unmarshal(v, &arr); err == nil && len(arr) > 0 { return len(arr) } } return 0 } func trimBody(b []byte, max int) string { s := strings.TrimSpace(string(b)) if len(s) > max { return s[:max] + "…" } return s }