Files
sub2api-cn-relay-manager/internal/host/sub2api/accounts.go
2026-05-13 00:41:12 +08:00

164 lines
4.2 KiB
Go

package sub2api
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
)
func (c *Client) CreateAccount(ctx context.Context, req CreateAccountRequest) (AccountRef, error) {
var ref AccountRef
if err := c.postJSON(ctx, "/api/v1/admin/accounts", req, &ref); err != nil {
return AccountRef{}, err
}
return ref, nil
}
func (c *Client) BatchCreateAccounts(ctx context.Context, req BatchCreateAccountsRequest) ([]AccountRef, error) {
statusCode, _, body, err := c.perform(ctx, http.MethodPost, "/api/v1/admin/accounts/batch", req)
if err != nil {
return nil, err
}
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
return nil, newHTTPError(http.MethodPost, "/api/v1/admin/accounts/batch", statusCode, body)
}
models, err := decodeAccountRefs(body)
if err != nil {
return nil, fmt.Errorf("decode /api/v1/admin/accounts/batch response: %w", err)
}
return models, nil
}
func (c *Client) TestAccount(ctx context.Context, accountID string) (ProbeResult, error) {
path := "/api/v1/admin/accounts/" + accountID + "/test"
statusCode, _, body, err := c.perform(ctx, http.MethodPost, path, map[string]any{})
if err != nil {
return ProbeResult{}, err
}
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
return ProbeResult{}, newHTTPError(http.MethodPost, path, statusCode, body)
}
result, err := parseProbeResult(body)
if err != nil {
return ProbeResult{}, fmt.Errorf("parse %s sse: %w", path, err)
}
return result, nil
}
func (c *Client) GetAccountModels(ctx context.Context, accountID string) ([]AccountModel, error) {
path := "/api/v1/admin/accounts/" + accountID + "/models"
statusCode, _, body, err := c.perform(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, err
}
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
return nil, newHTTPError(http.MethodGet, path, statusCode, body)
}
models, err := decodeAccountModels(body)
if err != nil {
return nil, fmt.Errorf("decode %s response: %w", path, err)
}
return models, nil
}
func decodeAccountRefs(body []byte) ([]AccountRef, error) {
var refs []AccountRef
if err := decodeEnvelopeObject(body, &refs); err == nil {
return refs, nil
}
var wrapper struct {
Data struct {
Items []AccountRef `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(body, &wrapper); err != nil {
return nil, err
}
return wrapper.Data.Items, nil
}
func decodeAccountModels(body []byte) ([]AccountModel, error) {
var models []AccountModel
if err := decodeEnvelopeObject(body, &models); err == nil {
return models, nil
}
var wrapper struct {
Data struct {
Items []AccountModel `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(body, &wrapper); err != nil {
return nil, err
}
return wrapper.Data.Items, nil
}
func parseProbeResult(body []byte) (ProbeResult, error) {
scanner := bufio.NewScanner(bytes.NewReader(body))
var payloads []string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "data:") {
payloads = append(payloads, strings.TrimSpace(strings.TrimPrefix(line, "data:")))
}
}
if err := scanner.Err(); err != nil {
return ProbeResult{}, err
}
if len(payloads) == 0 {
return ProbeResult{}, fmt.Errorf("missing data event")
}
var event struct {
Status string `json:"status"`
Message string `json:"message"`
OK *bool `json:"ok"`
Success *bool `json:"success"`
}
if err := json.Unmarshal([]byte(payloads[len(payloads)-1]), &event); err != nil {
return ProbeResult{}, err
}
ok := false
switch {
case event.OK != nil:
ok = *event.OK
case event.Success != nil:
ok = *event.Success
default:
switch strings.ToLower(strings.TrimSpace(event.Status)) {
case "ok", "pass", "passed", "success", "succeeded":
ok = true
}
}
status := normalizeProbeStatus(event.Status, ok)
return ProbeResult{
OK: ok,
Status: status,
Message: strings.TrimSpace(event.Message),
}, nil
}
func normalizeProbeStatus(status string, ok bool) string {
switch strings.ToLower(strings.TrimSpace(status)) {
case "pass", "passed", "ok", "success", "succeeded":
return "passed"
case "fail", "failed", "error":
return "failed"
}
if ok {
return "passed"
}
return "failed"
}