┌─ GO ───────────────────────────────────────────────────────────────────────┐
│ package blizzard │
│ │
│ import ( │
│ "encoding/json" │
│ "errors" │
│ "fmt" │
│ "io" │
│ "net/http" │
│ "time" │
│ ) │
│ │
│ // FetchSeasonIndex fetches the list of available seasons for a region │
│ func (c *Client) FetchSeasonIndex(region string) (*SeasonIndexResponse, error) { │
│ namespace := fmt.Sprintf("dynamic-classic-%s", region) │
│ url := fmt.Sprintf( │
│ "https://%s.api.blizzard.com/data/wow/mythic-keystone/season/index?namespace=%s& │
│ locale=en_US", │
│ region, namespace, │
│ ) │
│ │
│ const maxRetries = 3 │
│ const baseDelay = 1 * time.Second │
│ │
│ var lastErr error │
│ attempt := 0 │
│ for { │
│ if attempt > 0 { │
│ delay := time.Duration(1<<uint(attempt-1)) * baseDelay │
│ time.Sleep(delay) │
│ } │
│ │
│ result, err := c.fetchSeasonIndexOnce(url) │
│ if err == nil { │
│ return result, nil │
│ } │
│ │
│ lastErr = err │
│ │
│ var apiErr *APIError │
│ if errors.As(err, &apiErr) { │
│ switch apiErr.Status { │
│ case http.StatusTooManyRequests: │
│ time.Sleep(apiErr.retryDelay()) │
│ continue │
│ case http.StatusNotFound: │
│ return nil, err │
│ } │
│ } │
│ │
│ attempt++ │
│ if attempt >= maxRetries { │
│ break │
│ } │
│ } │
│ │
│ return nil, fmt.Errorf("failed to fetch season index after %d attempts: %w", maxR │
│ etries, lastErr) │
│ } │
│ │
│ func (c *Client) fetchSeasonIndexOnce(url string) (*SeasonIndexResponse, error) { │
│ c.waitForRateSlot() │
│ │
│ req, err := http.NewRequest("GET", url, nil) │
│ if err != nil { │
│ return nil, fmt.Errorf("failed to create request: %w", err) │
│ } │
│ │
│ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token)) │
│ req.Header.Set("User-Agent", "WoWStatsDB/1.0") │
│ │
│ resp, err := c.HTTPClient.Do(req) │
│ if err != nil { │
│ return nil, fmt.Errorf("HTTP request failed: %w", err) │
│ } │
│ defer resp.Body.Close() │
│ │
│ if resp.StatusCode != http.StatusOK { │
│ bodyBytes, _ := io.ReadAll(resp.Body) │
│ return nil, newAPIError(resp.StatusCode, bodyBytes, resp.Header.Get("Retry-After │
│ ")) │
│ } │
│ │
│ var result SeasonIndexResponse │
│ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { │
│ return nil, fmt.Errorf("failed to decode response: %w", err) │
│ } │
│ │
│ return &result, nil │
│ } │
│ │
│ // FetchSeasonDetail fetches details for a specific season │
│ func (c *Client) FetchSeasonDetail(region string, seasonID int) (*SeasonDetailResp │
│ onse, error) { │
│ namespace := fmt.Sprintf("dynamic-classic-%s", region) │
│ url := fmt.Sprintf( │
│ "https://%s.api.blizzard.com/data/wow/mythic-keystone/season/%d?namespace=%s&loc │
│ ale=en_US", │
│ region, seasonID, namespace, │
│ ) │
│ │
│ const maxRetries = 3 │
│ const baseDelay = 1 * time.Second │
│ │
│ var lastErr error │
│ attempt := 0 │
│ for { │
│ if attempt > 0 { │
│ delay := time.Duration(1<<uint(attempt-1)) * baseDelay │
│ time.Sleep(delay) │
│ } │
│ │
│ result, err := c.fetchSeasonDetailOnce(url) │
│ if err == nil { │
│ return result, nil │
│ } │
│ │
│ lastErr = err │
│ │
│ var apiErr *APIError │
│ if errors.As(err, &apiErr) { │
│ switch apiErr.Status { │
│ case http.StatusTooManyRequests: │
│ time.Sleep(apiErr.retryDelay()) │
│ continue │
│ case http.StatusNotFound: │
│ return nil, err │
│ } │
│ } │
│ │
│ attempt++ │
│ if attempt >= maxRetries { │
│ break │
│ } │
│ } │
│ │
│ return nil, fmt.Errorf("failed to fetch season %d after %d attempts: %w", seasonI │
│ D, maxRetries, lastErr) │
│ } │
│ │
│ func (c *Client) fetchSeasonDetailOnce(url string) (*SeasonDetailResponse, error) │
│ { │
│ c.waitForRateSlot() │
│ │
│ req, err := http.NewRequest("GET", url, nil) │
│ if err != nil { │
│ return nil, fmt.Errorf("failed to create request: %w", err) │
│ } │
│ │
│ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token)) │
│ req.Header.Set("User-Agent", "WoWStatsDB/1.0") │
│ │
│ resp, err := c.HTTPClient.Do(req) │
│ if err != nil { │
│ return nil, fmt.Errorf("HTTP request failed: %w", err) │
│ } │
│ defer resp.Body.Close() │
│ │
│ if resp.StatusCode != http.StatusOK { │
│ bodyBytes, _ := io.ReadAll(resp.Body) │
│ return nil, newAPIError(resp.StatusCode, bodyBytes, resp.Header.Get("Retry-After │
│ ")) │
│ } │
│ │
│ var result SeasonDetailResponse │
│ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { │
│ return nil, fmt.Errorf("failed to decode response: %w", err) │
│ } │
│ │
│ return &result, nil │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ GO ─────────────────────────────────┐
│ package blizzard │
│ │
│ import ( │
│ "encoding/json" │
│ "errors" │
│ "fmt" │
│ "io" │
│ "net/http" │
│ "time" │
│ ) │
│ │
│ // FetchSeasonIndex fetches the list of avai │
│ lable seasons for a region │
│ func (c *Client) FetchSeasonIndex(region str │
│ ing) (*SeasonIndexResponse, error) { │
│ namespace := fmt.Sprintf("dynamic-classic-% │
│ s", region) │
│ url := fmt.Sprintf( │
│ "https://%s.api.blizzard.com/data/wow/myth │
│ ic-keystone/season/index?namespace=%s&locale │
│ =en_US", │
│ region, namespace, │
│ ) │
│ │
│ const maxRetries = 3 │
│ const baseDelay = 1 * time.Second │
│ │
│ var lastErr error │
│ attempt := 0 │
│ for { │
│ if attempt > 0 { │
│ delay := time.Duration(1<<uint(attempt-1) │
│ ) * baseDelay │
│ time.Sleep(delay) │
│ } │
│ │
│ result, err := c.fetchSeasonIndexOnce(url) │
│ if err == nil { │
│ return result, nil │
│ } │
│ │
│ lastErr = err │
│ │
│ var apiErr *APIError │
│ if errors.As(err, &apiErr) { │
│ switch apiErr.Status { │
│ case http.StatusTooManyRequests: │
│ time.Sleep(apiErr.retryDelay()) │
│ continue │
│ case http.StatusNotFound: │
│ return nil, err │
│ } │
│ } │
│ │
│ attempt++ │
│ if attempt >= maxRetries { │
│ break │
│ } │
│ } │
│ │
│ return nil, fmt.Errorf("failed to fetch sea │
│ son index after %d attempts: %w", maxRetries │
│ , lastErr) │
│ } │
│ │
│ func (c *Client) fetchSeasonIndexOnce(url st │
│ ring) (*SeasonIndexResponse, error) { │
│ c.waitForRateSlot() │
│ │
│ req, err := http.NewRequest("GET", url, nil │
│ ) │
│ if err != nil { │
│ return nil, fmt.Errorf("failed to create r │
│ equest: %w", err) │
│ } │
│ │
│ req.Header.Set("Authorization", fmt.Sprintf │
│ ("Bearer %s", c.Token)) │
│ req.Header.Set("User-Agent", "WoWStatsDB/1. │
│ 0") │
│ │
│ resp, err := c.HTTPClient.Do(req) │
│ if err != nil { │
│ return nil, fmt.Errorf("HTTP request faile │
│ d: %w", err) │
│ } │
│ defer resp.Body.Close() │
│ │
│ if resp.StatusCode != http.StatusOK { │
│ bodyBytes, _ := io.ReadAll(resp.Body) │
│ return nil, newAPIError(resp.StatusCode, b │
│ odyBytes, resp.Header.Get("Retry-After")) │
│ } │
│ │
│ var result SeasonIndexResponse │
│ if err := json.NewDecoder(resp.Body).Decode │
│ (&result); err != nil { │
│ return nil, fmt.Errorf("failed to decode r │
│ esponse: %w", err) │
│ } │
│ │
│ return &result, nil │
│ } │
│ │
│ // FetchSeasonDetail fetches details for a s │
│ pecific season │
│ func (c *Client) FetchSeasonDetail(region st │
│ ring, seasonID int) (*SeasonDetailResponse, │
│ error) { │
│ namespace := fmt.Sprintf("dynamic-classic-% │
│ s", region) │
│ url := fmt.Sprintf( │
│ "https://%s.api.blizzard.com/data/wow/myth │
│ ic-keystone/season/%d?namespace=%s&locale=en │
│ _US", │
│ region, seasonID, namespace, │
│ ) │
│ │
│ const maxRetries = 3 │
│ const baseDelay = 1 * time.Second │
│ │
│ var lastErr error │
│ attempt := 0 │
│ for { │
│ if attempt > 0 { │
│ delay := time.Duration(1<<uint(attempt-1) │
│ ) * baseDelay │
│ time.Sleep(delay) │
│ } │
│ │
│ result, err := c.fetchSeasonDetailOnce(url │
│ ) │
│ if err == nil { │
│ return result, nil │
│ } │
│ │
│ lastErr = err │
│ │
│ var apiErr *APIError │
│ if errors.As(err, &apiErr) { │
│ switch apiErr.Status { │
│ case http.StatusTooManyRequests: │
│ time.Sleep(apiErr.retryDelay()) │
│ continue │
│ case http.StatusNotFound: │
│ return nil, err │
│ } │
│ } │
│ │
│ attempt++ │
│ if attempt >= maxRetries { │
│ break │
│ } │
│ } │
│ │
│ return nil, fmt.Errorf("failed to fetch sea │
│ son %d after %d attempts: %w", seasonID, max │
│ Retries, lastErr) │
│ } │
│ │
│ func (c *Client) fetchSeasonDetailOnce(url s │
│ tring) (*SeasonDetailResponse, error) { │
│ c.waitForRateSlot() │
│ │
│ req, err := http.NewRequest("GET", url, nil │
│ ) │
│ if err != nil { │
│ return nil, fmt.Errorf("failed to create r │
│ equest: %w", err) │
│ } │
│ │
│ req.Header.Set("Authorization", fmt.Sprintf │
│ ("Bearer %s", c.Token)) │
│ req.Header.Set("User-Agent", "WoWStatsDB/1. │
│ 0") │
│ │
│ resp, err := c.HTTPClient.Do(req) │
│ if err != nil { │
│ return nil, fmt.Errorf("HTTP request faile │
│ d: %w", err) │
│ } │
│ defer resp.Body.Close() │
│ │
│ if resp.StatusCode != http.StatusOK { │
│ bodyBytes, _ := io.ReadAll(resp.Body) │
│ return nil, newAPIError(resp.StatusCode, b │
│ odyBytes, resp.Header.Get("Retry-After")) │
│ } │
│ │
│ var result SeasonDetailResponse │
│ if err := json.NewDecoder(resp.Body).Decode │
│ (&result); err != nil { │
│ return nil, fmt.Errorf("failed to decode r │
│ esponse: %w", err) │
│ } │
│ │
│ return &result, nil │
│ } │
└──────────────────────────────────────────────┘