176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
// Package api holds functions to interact with the artifacts API
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"slices"
|
|
)
|
|
|
|
// custom error codes
|
|
var (
|
|
ErrTokenNotProvided = errors.New("make sure to provide a token via the api.SetToken() function, or set it via env variable ARTIFACTS_TOKEN")
|
|
)
|
|
|
|
const (
|
|
baseURL = "https://api.artifactsmmo.com"
|
|
)
|
|
|
|
var token string // the token must be provided for the client to work
|
|
|
|
type apiResponse struct {
|
|
Data json.RawMessage `json:"data"`
|
|
Total int `json:"total"`
|
|
Page int `json:"page"`
|
|
Size int `json:"size"`
|
|
Pages int `json:"pages"`
|
|
}
|
|
|
|
// SetToken can be used to set the token, which must be provided before calling any other function.
|
|
// It can be provided using this function or via environment variable 'ARTIFACTS_TOKEN'.
|
|
func SetToken(t string) {
|
|
token = t
|
|
}
|
|
|
|
func meta[T any](endpoint string, method string, body any, responseCodes map[int]string) (*T, error) {
|
|
// make it possible to set a token via env variable
|
|
if token == "" && os.Getenv("ARTIFACTS_TOKEN") != "" {
|
|
SetToken(os.Getenv("ARTIFACTS_TOKEN"))
|
|
}
|
|
|
|
// check for token
|
|
if token == "" {
|
|
return nil, ErrTokenNotProvided
|
|
}
|
|
|
|
bodyBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create a new request to retrieve characters
|
|
request, err := http.NewRequest(method, baseURL+endpoint, bytes.NewReader(bodyBytes))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// add auth header to the request
|
|
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
request.Header.Set("Content-Type", "application/json")
|
|
|
|
// execute this request
|
|
response, err := http.DefaultClient.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check all additional status codes first
|
|
for code, message := range responseCodes {
|
|
if response.StatusCode != code {
|
|
continue
|
|
}
|
|
|
|
// exit the function if a statuscode was found and return it's message
|
|
return nil, fmt.Errorf("%d: %s", code, message)
|
|
}
|
|
|
|
// basic error handling
|
|
if response.StatusCode != 200 {
|
|
return nil, fmt.Errorf("%s", response.Status)
|
|
}
|
|
|
|
// read in the body of the response
|
|
b, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// close the body when we are finished
|
|
defer response.Body.Close()
|
|
|
|
// convert it to a native go type
|
|
var ar apiResponse
|
|
if err := json.Unmarshal(b, &ar); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ar.Page < ar.Pages {
|
|
// parse endpoint
|
|
u, err := url.Parse(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
urlValues := u.Query()
|
|
urlValues.Set("page", fmt.Sprintf("%d", ar.Page+1))
|
|
endpoint = u.Path
|
|
|
|
nextPage, err := meta[T](fmt.Sprintf("%s?%s", endpoint, urlValues.Encode()), method, body, responseCodes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b1, err := ar.Data.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b2, err := json.Marshal(nextPage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bCombined := slices.Concat(b1[0:len(b1)-1], []byte(","), b2[1:])
|
|
|
|
// copy combined dataset into ar.Data
|
|
ar.Data.UnmarshalJSON(bCombined)
|
|
|
|
// combine both things
|
|
}
|
|
|
|
d, err := ar.Data.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// no error occurred
|
|
var r T
|
|
if err := json.Unmarshal(d, &r); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &r, nil
|
|
}
|
|
|
|
// Post is used to call an endpoint using a POST request, responseCodes contains all possible
|
|
// error codes that the API can respond with other than 200 OK
|
|
func Post[T any](endpoint string, body any, responseCodes map[int]string) (output T, err error) {
|
|
// use meta function to get a response
|
|
response, err := meta[T](endpoint, http.MethodPost, body, responseCodes)
|
|
if err != nil {
|
|
return output, err
|
|
}
|
|
|
|
// return the data alongside any error that might have occurred
|
|
return *response, err
|
|
}
|
|
|
|
// Get is used to call an endpoint using a GET request, responseCodes contains all possible
|
|
// error codes that the API can respond with other than 200 OK
|
|
func Get[T any](endpoint string, responseCodes map[int]string) (output T, err error) {
|
|
// use meta function to get a response
|
|
response, err := meta[T](endpoint, http.MethodGet, nil, responseCodes)
|
|
if err != nil {
|
|
return output, err
|
|
}
|
|
|
|
// return the data alongside any error that might have occurred
|
|
return *response, err
|
|
}
|