package warcraftlogs import ( "encoding/json" "fmt" "io/ioutil" "log" "math/rand" "net/http" "strings" "time" ) // WarcraftLogs Client type WarcraftLogs struct { client http.Client tokens []string endpoint string } // NewRetail - New WarcraftLogs Client for RETAIL/actual WoW, with required API Token. // Currently using v1 API endpoint as default func NewRetail(apiToken string) *WarcraftLogs { api := &WarcraftLogs{ endpoint: "https://www.warcraftlogs.com/v1/", client: http.Client{ Timeout: time.Second * 60, }, tokens: []string{apiToken}, } return api } // NewClassic - New WarcraftLogs Client for WoW CLASSIC, with required API Token. // 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, }, tokens: []string{apiToken}, } return api } // New - shortcut to NewRetail(apiToken string) func New(apiToken string) *WarcraftLogs { return NewRetail(apiToken) } // SetTokens - set list of tokens to api client func (w *WarcraftLogs) SetTokens(tokens []string) { uniq := map[string]bool{} for _, token := range tokens { if !uniq[token] { w.tokens = append(w.tokens, token) } } } // 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 + "/" } } // 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))] } // RemoveToken - remove token from pool func (w *WarcraftLogs) RemoveToken(remove string) { for idx, token := range w.tokens { if token == remove { w.tokens[idx] = w.tokens[len(w.tokens)-1] w.tokens = w.tokens[:len(w.tokens)-1] return } } } // Get - support function to make an authenticated GET call and parse response JSON to a responseHolder. func (w *WarcraftLogs) Get(path string, responseHolder interface{}) error { token := w.GetToken() req, err := http.NewRequest("GET", w.endpoint+path, nil) query := req.URL.Query() query.Add("api_key", token) req.URL.RawQuery = query.Encode() if err != nil { log.Println("Create request to WarcraftLogs error", err) return err } res, getErr := w.client.Do(req) body, readErr := ioutil.ReadAll(res.Body) if res.StatusCode != 200 { switch res.StatusCode { 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) case 403: log.Println("API token issue:", string(body)) log.Println("Removing token from pool and restarting request...") w.RemoveToken(token) return w.Get(path, responseHolder) case 400: return fmt.Errorf("Not found on Warcraft Logs") case 429: log.Println("Too many requests to WarcraftLogs API, sleep 2 minutes") time.Sleep(2 * time.Minute) return w.Get(path, responseHolder) } } if getErr != nil { log.Println(getErr) return getErr } if readErr != nil { log.Println("HTTP Response from WarcraftLogs read failed ", readErr) return readErr } jsonErr := json.Unmarshal(body, responseHolder) if jsonErr != nil { log.Println("Unmarshaling JSON from WarcraftLogs to Go response struct failed", jsonErr) return jsonErr } return nil }