2021-09-13 03:25:47 +00:00
|
|
|
package heliumapi
|
|
|
|
|
|
|
|
import (
|
2021-10-27 02:07:14 +00:00
|
|
|
"context"
|
2021-09-25 01:30:22 +00:00
|
|
|
"encoding/json"
|
2021-09-13 03:25:47 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2021-10-27 02:07:14 +00:00
|
|
|
"time"
|
2021-11-14 15:44:39 +00:00
|
|
|
|
|
|
|
"golang.org/x/time/rate"
|
2021-09-13 03:25:47 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2021-11-14 15:44:39 +00:00
|
|
|
// those are set via flags on init
|
|
|
|
ApiUrl string
|
|
|
|
ApiTimeout time.Duration
|
|
|
|
Limiter *rate.Limiter
|
|
|
|
|
|
|
|
client = &http.Client{}
|
2021-09-13 03:25:47 +00:00
|
|
|
)
|
|
|
|
|
2021-09-26 04:42:34 +00:00
|
|
|
// createGetRequest create a GET request to the helium api
|
2021-10-27 02:07:14 +00:00
|
|
|
func createGetRequest(path string, params map[string]string) (*http.Request, *context.CancelFunc, error) {
|
|
|
|
ctx, ctxCancel := context.WithTimeout(context.Background(), ApiTimeout)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s", ApiUrl, path), nil)
|
2021-09-13 03:25:47 +00:00
|
|
|
|
|
|
|
// setup headers
|
|
|
|
req.Header.Add("Accept", "application/json")
|
2021-11-14 15:44:39 +00:00
|
|
|
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36")
|
2021-09-13 03:25:47 +00:00
|
|
|
|
|
|
|
// setup query param if there are any
|
|
|
|
if params != nil {
|
|
|
|
query := req.URL.Query()
|
2021-09-25 01:30:22 +00:00
|
|
|
for k, v := range params {
|
2021-09-13 03:25:47 +00:00
|
|
|
query.Add(k, v)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2021-10-27 02:07:14 +00:00
|
|
|
defer ctxCancel()
|
|
|
|
return nil, nil, err
|
2021-09-13 03:25:47 +00:00
|
|
|
}
|
|
|
|
req.URL.RawQuery = query.Encode()
|
|
|
|
}
|
|
|
|
|
2021-10-27 02:07:14 +00:00
|
|
|
return req, &ctxCancel, err
|
2021-09-25 01:30:22 +00:00
|
|
|
}
|
|
|
|
|
2021-09-26 04:42:34 +00:00
|
|
|
// getHeliumApi query a regular helium api endpoint
|
2021-09-25 01:30:22 +00:00
|
|
|
func getHeliumApi(path string, params *map[string]string) ([]byte, error) {
|
|
|
|
// if params is nil, set it to an empty map
|
|
|
|
if params == nil {
|
|
|
|
params = &map[string]string{}
|
|
|
|
}
|
|
|
|
|
2021-11-14 15:44:39 +00:00
|
|
|
// ensure we respect the rate limit
|
2021-12-24 21:03:32 +00:00
|
|
|
if err := Limiter.Wait(context.Background()); err != nil {
|
2021-11-14 15:44:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the request
|
2021-10-27 02:07:14 +00:00
|
|
|
req, ctxCancel, err := createGetRequest(path, *params)
|
2021-09-25 01:30:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create query request %v: %v", path, err)
|
|
|
|
}
|
2021-10-27 02:07:14 +00:00
|
|
|
defer (*ctxCancel)()
|
2021-09-25 01:30:22 +00:00
|
|
|
|
2021-09-13 03:25:47 +00:00
|
|
|
// query the api
|
2021-10-12 14:37:03 +00:00
|
|
|
// log.Printf("querying %v", req.URL.String())
|
2021-09-13 03:25:47 +00:00
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to query %v: %v", req.URL.String(), err)
|
2021-09-13 03:25:47 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2021-10-03 22:02:25 +00:00
|
|
|
// validate the response status
|
|
|
|
if resp.StatusCode != 200 {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to query %v: http status %v", req.URL.String(), resp.StatusCode)
|
2021-10-03 22:02:25 +00:00
|
|
|
}
|
|
|
|
|
2021-09-13 03:25:47 +00:00
|
|
|
// read the response body
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to read response body of %v: %v", req.URL.String(), err)
|
2021-09-13 03:25:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return body, nil
|
|
|
|
}
|
|
|
|
|
2021-09-26 04:42:34 +00:00
|
|
|
// getHeliumApiWithCursor query a helium api with a cursor
|
|
|
|
// It will continue to query the api until all the cursors are exhausted. Combine all the
|
|
|
|
// responses in a single object.
|
2021-09-25 01:30:22 +00:00
|
|
|
func getHeliumApiWithCursor(path string, params *map[string]string) ([][]byte, error) {
|
|
|
|
// if params is nil, set it to an empty map
|
|
|
|
if params == nil {
|
|
|
|
params = &map[string]string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ResponseWithCursor struct {
|
|
|
|
Cursor string `json:"cursor"`
|
|
|
|
}
|
|
|
|
|
2021-11-14 15:44:39 +00:00
|
|
|
// create the initial request
|
2021-09-25 01:30:22 +00:00
|
|
|
res := [][]byte{}
|
|
|
|
respCursor := ResponseWithCursor{}
|
2021-10-27 02:07:14 +00:00
|
|
|
req, ctxCancel, err := createGetRequest(path, *params)
|
2021-09-25 01:30:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create query request %v: %v", path, err)
|
|
|
|
}
|
2021-10-27 02:07:14 +00:00
|
|
|
defer (*ctxCancel)()
|
2021-09-25 01:30:22 +00:00
|
|
|
|
|
|
|
for {
|
2021-11-14 15:44:39 +00:00
|
|
|
// ensure we respect the rate limit
|
2021-12-24 21:03:32 +00:00
|
|
|
if err := Limiter.Wait(context.Background()); err != nil {
|
2021-11-14 15:44:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-09-25 01:30:22 +00:00
|
|
|
// query the api
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to query %v: %v", req.URL.String(), err)
|
2021-09-25 01:30:22 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
// read the response body and add it to the result array
|
2021-12-24 17:53:46 +00:00
|
|
|
// log.Printf("querying %v", req.URL.String())
|
2021-09-25 01:30:22 +00:00
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to read response body of %v: %v", req.URL.String(), err)
|
2021-09-25 01:30:22 +00:00
|
|
|
}
|
|
|
|
res = append(res, body)
|
|
|
|
|
2021-10-03 22:02:25 +00:00
|
|
|
// validate the response status
|
|
|
|
if resp.StatusCode != 200 {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to query %v: http status %v", req.URL.String(), resp.StatusCode)
|
2021-10-03 22:02:25 +00:00
|
|
|
}
|
|
|
|
|
2021-09-25 01:30:22 +00:00
|
|
|
// parse the response body for a cursor
|
|
|
|
respCursor.Cursor = ""
|
2021-12-24 21:03:32 +00:00
|
|
|
if err := json.Unmarshal(body, &respCursor); err != nil {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to unmarshal response from %v: %v", req.URL.String(), err)
|
2021-09-25 01:30:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// continue querying until there is no longer a cursor
|
|
|
|
if respCursor.Cursor != "" {
|
|
|
|
params = &map[string]string{"cursor": respCursor.Cursor}
|
2021-10-27 02:07:14 +00:00
|
|
|
req, ctxCancel, err = createGetRequest(path, *params)
|
2021-09-25 01:30:22 +00:00
|
|
|
if err != nil {
|
2021-10-12 13:19:58 +00:00
|
|
|
return nil, fmt.Errorf("failed to create query request %v: %v", req.URL.String(), err)
|
2021-09-25 01:30:22 +00:00
|
|
|
}
|
2021-10-27 02:07:14 +00:00
|
|
|
defer (*ctxCancel)()
|
2021-09-25 01:30:22 +00:00
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
2021-09-13 03:25:47 +00:00
|
|
|
}
|