package client import ( "bytes" "context" "encoding/json" "fmt" "git.aiterp.net/lucifer/new-server/models" "io" "net" "net/http" "strings" "time" ) type Client struct { APIRoot string } func (client *Client) GetDevices(ctx context.Context, fetchStr string) ([]models.Device, error) { devices := make([]models.Device, 0, 16) err := client.Fetch(ctx, "GET", "/api/devices/"+fetchStr, &devices, nil) if err != nil { return nil, err } return devices, nil } func (client *Client) PutDevice(ctx context.Context, fetchStr string, update models.DeviceUpdate) ([]models.Device, error) { devices := make([]models.Device, 0, 16) err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr, &devices, update) if err != nil { return nil, err } return devices, nil } func (client *Client) PutDeviceState(ctx context.Context, fetchStr string, update models.NewDeviceState) ([]models.Device, error) { devices := make([]models.Device, 0, 16) err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr+"/state", &devices, update) if err != nil { return nil, err } return devices, nil } func (client *Client) PutDeviceTags(ctx context.Context, fetchStr string, addTags []string, removeTags []string) ([]models.Device, error) { devices := make([]models.Device, 0, 16) err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr+"/tags", &devices, map[string][]string{ "add": addTags, "remove": removeTags, }) if err != nil { return nil, err } return devices, nil } func (client *Client) AssignDevice(ctx context.Context, fetchStr string, push bool, assignment models.DeviceSceneAssignment) ([]models.Device, error) { query := "" if push { query = "?push=true" } devices := make([]models.Device, 0, 16) err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr+"/scene"+query, &devices, assignment) if err != nil { return nil, err } return devices, nil } func (client *Client) ClearDevice(ctx context.Context, fetchStr string) ([]models.Device, error) { devices := make([]models.Device, 0, 16) err := client.Fetch(ctx, "DELETE", "/api/devices/"+fetchStr+"/scene", &devices, nil) if err != nil { return nil, err } return devices, nil } func (client *Client) FireEvent(ctx context.Context, event models.Event) error { err := client.Fetch(ctx, "POST", "/api/events", nil, event) if err != nil { return err } return nil } func (client *Client) Fetch(ctx context.Context, method string, path string, dst interface{}, body interface{}) error { var reqBody io.ReadWriter if body != nil && method != "GET" { reqBody = bytes.NewBuffer(make([]byte, 0, 512)) err := json.NewEncoder(reqBody).Encode(body) if err != nil { return err } } req, err := http.NewRequest(method, client.APIRoot+path, reqBody) if err != nil { return err } res, err := httpClient.Do(req.WithContext(ctx)) if err != nil { return err } defer res.Body.Close() if !strings.HasPrefix(res.Header.Get("Content-Type"), "application/json") { return fmt.Errorf("%s: %s", path, res.Status) } var resJson struct { Code int `json:"code"` Message *string `json:"message"` Data json.RawMessage `json:"data"` } err = json.NewDecoder(res.Body).Decode(&resJson) if err != nil { return err } if resJson.Code != 200 { msg := "" if resJson.Message != nil { msg = *resJson.Message } return fmt.Errorf("%d: %s", resJson.Code, msg) } if dst == nil { return nil } return json.Unmarshal(resJson.Data, dst) } var httpClient = &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, MaxIdleConns: 16, MaxIdleConnsPerHost: 16, IdleConnTimeout: time.Minute, }, Timeout: time.Minute, }