package creds import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strconv" "time" ) const httpTimeout = 8 * time.Second type apiEntry struct { Manufacturer string `json:"manufacturer"` Name string `json:"name"` Icon string `json:"icon"` Tags []string `json:"tags"` Version string `json:"version"` User string `json:"user"` Pass string `json:"pass"` Comment string `json:"comment"` } type apiSearchResponse struct { Results []apiEntry `json:"results"` Pagination struct { TotalResults int `json:"totalResults"` } `json:"pagination"` } type apiStatsResponse struct { TotalApps int `json:"totalApps"` TotalPasswords int `json:"totalPasswords"` } func FetchOnline(apiURL, query string, size int) (results []Entry, total int, err error) { u := apiURL + "/api/search?" + url.Values{ "q": {query}, "size": {strconv.Itoa(size)}, }.Encode() var resp apiSearchResponse if err := getJSON(u, &resp); err != nil { return nil, 0, err } entries := make([]Entry, len(resp.Results)) for i, r := range resp.Results { entries[i] = Entry{ Manufacturer: r.Manufacturer, Name: r.Name, Icon: r.Icon, Tags: r.Tags, Version: r.Version, User: r.User, Pass: r.Pass, Comment: r.Comment, } } return entries, resp.Pagination.TotalResults, nil } func FetchStats(apiURL string) (manufacturers, credentials int, err error) { var resp apiStatsResponse if err := getJSON(apiURL+"/api/stats", &resp); err != nil { return 0, 0, err } return resp.TotalApps, resp.TotalPasswords, nil } func getJSON(u string, out any) error { ctx, cancel := context.WithTimeout(context.Background(), httpTimeout) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return err } resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("%s: unexpected status %s", u, resp.Status) } return json.NewDecoder(resp.Body).Decode(out) }