warcraftlogs.go 3.49 KB
Newer Older
rakshazi's avatar
Init  
rakshazi committed
1 2 3 4 5 6 7
package warcraftlogs

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
8
	"math/rand"
rakshazi's avatar
Init  
rakshazi committed
9 10 11 12 13 14 15
	"net/http"
	"strings"
	"time"
)

// WarcraftLogs Client
type WarcraftLogs struct {
16 17
	client http.Client
	tokens []string
rakshazi's avatar
Init  
rakshazi committed
18 19 20 21

	endpoint string
}

22
// NewRetail - New WarcraftLogs Client for RETAIL/actual WoW, with required API Token.
rakshazi's avatar
Init  
rakshazi committed
23
// Currently using v1 API endpoint as default
rakshazi's avatar
rakshazi committed
24
func NewRetail(apiToken string) *WarcraftLogs {
rakshazi's avatar
Init  
rakshazi committed
25 26 27 28 29
	api := &WarcraftLogs{
		endpoint: "https://www.warcraftlogs.com/v1/",
		client: http.Client{
			Timeout: time.Second * 60,
		},
30
		tokens: []string{apiToken},
rakshazi's avatar
Init  
rakshazi committed
31 32 33 34 35
	}

	return api
}

36
// NewClassic - New WarcraftLogs Client for WoW CLASSIC, with required API Token.
rakshazi's avatar
rakshazi committed
37 38 39 40 41 42 43
// Currently using v1 API endpoint as default
func NewClassic(apiToken string) *WarcraftLogs {
	api := &WarcraftLogs{
		endpoint: "https://classic.warcraftlogs.com/v1/",
		client: http.Client{
			Timeout: time.Second * 60,
		},
44
		tokens: []string{apiToken},
rakshazi's avatar
rakshazi committed
45 46 47 48 49 50 51 52 53 54
	}

	return api
}

// New - shortcut to NewRetail(apiToken string)
func New(apiToken string) *WarcraftLogs {
	return NewRetail(apiToken)
}

55 56
// SetTokens - set list of tokens to api client
func (w *WarcraftLogs) SetTokens(tokens []string) {
57 58 59 60 61 62
	uniq := map[string]bool{}
	for _, token := range tokens {
		if !uniq[token] {
			w.tokens = append(w.tokens, token)
		}
	}
63 64
}

rakshazi's avatar
Init  
rakshazi committed
65 66 67 68 69 70 71 72 73 74 75
// SetEndpoint allows customizing API Endpoint that client will use.
// If provided endoint does not end with a trailing slash, it will be added
func (w *WarcraftLogs) SetEndpoint(endpoint string) {

	w.endpoint = endpoint

	if !strings.HasSuffix(w.endpoint, "/") {
		w.endpoint = w.endpoint + "/"
	}
}

76 77 78 79 80 81
// GetToken - get random API token from pool
func (w *WarcraftLogs) GetToken() string {
	rand.Seed(time.Now().Unix())
	return w.tokens[rand.Intn(len(w.tokens))]
}

82 83 84 85
// RemoveToken - remove token from pool
func (w *WarcraftLogs) RemoveToken(remove string) {
	for idx, token := range w.tokens {
		if token == remove {
rakshazi's avatar
rakshazi committed
86 87
			w.tokens[idx] = w.tokens[len(w.tokens)-1]
			w.tokens = w.tokens[:len(w.tokens)-1]
88
			return
89 90 91 92
		}
	}
}

93
// Get - support function to make an authenticated GET call and parse response JSON to a responseHolder.
rakshazi's avatar
Init  
rakshazi committed
94
func (w *WarcraftLogs) Get(path string, responseHolder interface{}) error {
95
	token := w.GetToken()
rakshazi's avatar
rakshazi committed
96 97 98 99
	req, err := http.NewRequest("GET", w.endpoint+path, nil)
	query := req.URL.Query()
	query.Add("api_key", token)
	req.URL.RawQuery = query.Encode()
rakshazi's avatar
Init  
rakshazi committed
100 101

	if err != nil {
rakshazi's avatar
rakshazi committed
102 103
		log.Println("Create request to WarcraftLogs error", err)
		return err
rakshazi's avatar
Init  
rakshazi committed
104 105 106
	}

	res, getErr := w.client.Do(req)
rakshazi's avatar
rakshazi committed
107
	body, readErr := ioutil.ReadAll(res.Body)
108 109
	if res.StatusCode != 200 {
		switch res.StatusCode {
110 111 112 113 114
		case 401:
			log.Println("API token issue:", string(body))
			log.Println("Removing token from pool and restarting request...")
			w.RemoveToken(token)
			return w.Get(path, responseHolder)
rakshazi's avatar
rakshazi committed
115 116
		case 403:
			log.Println("API token issue:", string(body))
117 118
			log.Println("Removing token from pool and restarting request...")
			w.RemoveToken(token)
rakshazi's avatar
rakshazi committed
119
			return w.Get(path, responseHolder)
120
		case 400:
rakshazi's avatar
rakshazi committed
121
			return fmt.Errorf("Not found on Warcraft Logs")
122
		case 429:
123 124
			log.Println("Too many requests to WarcraftLogs API, sleep 2 minutes")
			time.Sleep(2 * time.Minute)
125 126
			return w.Get(path, responseHolder)
		}
rakshazi's avatar
Init  
rakshazi committed
127
	}
rakshazi's avatar
rakshazi committed
128 129 130 131
	if getErr != nil {
		log.Println(getErr)
		return getErr
	}
rakshazi's avatar
Init  
rakshazi committed
132
	if readErr != nil {
rakshazi's avatar
rakshazi committed
133
		log.Println("HTTP Response from WarcraftLogs read failed ", readErr)
rakshazi's avatar
Init  
rakshazi committed
134 135 136 137 138
		return readErr
	}

	jsonErr := json.Unmarshal(body, responseHolder)
	if jsonErr != nil {
rakshazi's avatar
rakshazi committed
139
		log.Println("Unmarshaling JSON from WarcraftLogs to Go response struct failed", jsonErr)
rakshazi's avatar
Init  
rakshazi committed
140 141 142 143 144
		return jsonErr
	}

	return nil
}