main
1package protect
2
3import (
4 "context"
5 "fmt"
6 "strings"
7)
8
9// Bootstrap fetches system information including all cameras.
10func (c *Client) Bootstrap(ctx context.Context) (*BootstrapResponse, error) {
11 req, err := c.newRequest(ctx, "GET", "/bootstrap", nil)
12 if err != nil {
13 return nil, fmt.Errorf("creating request: %w", err)
14 }
15
16 var resp BootstrapResponse
17 err = c.doJSON(req, &resp)
18 if err != nil {
19 return nil, fmt.Errorf("fetching bootstrap: %w", err)
20 }
21 return &resp, nil
22}
23
24// ResolveCamera finds a camera by ID or name.
25// If the input matches a camera ID exactly, that camera is returned.
26// Otherwise, it searches by name (case-insensitive partial match).
27// Returns ErrCameraNotFound if no match, ErrMultipleCameras if ambiguous.
28func (c *Client) ResolveCamera(ctx context.Context, idOrName string) (*Camera, error) {
29 bootstrap, err := c.Bootstrap(ctx)
30 if err != nil {
31 return nil, err
32 }
33
34 // First try exact ID match
35 for i := range bootstrap.Cameras {
36 if bootstrap.Cameras[i].ID == idOrName {
37 return &bootstrap.Cameras[i], nil
38 }
39 }
40
41 // Then try name match (case-insensitive)
42 var matches []*Camera
43 searchLower := strings.ToLower(idOrName)
44 for i := range bootstrap.Cameras {
45 nameLower := strings.ToLower(bootstrap.Cameras[i].Name)
46 if nameLower == searchLower || strings.Contains(nameLower, searchLower) {
47 matches = append(matches, &bootstrap.Cameras[i])
48 }
49 }
50
51 switch len(matches) {
52 case 0:
53 return nil, fmt.Errorf("%w: %s", ErrCameraNotFound, idOrName)
54 case 1:
55 return matches[0], nil
56 default:
57 names := make([]string, len(matches))
58 for i, cam := range matches {
59 names[i] = fmt.Sprintf("%s (%s)", cam.Name, cam.ID)
60 }
61 return nil, fmt.Errorf("%w: %s matches: %s", ErrMultipleCameras, idOrName, strings.Join(names, ", "))
62 }
63}