-
Notifications
You must be signed in to change notification settings - Fork 2
/
api.go
166 lines (147 loc) · 4.35 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
const baseURI string = "https://%s.api.blizzard.com/%s%s"
const oauthURI string = "https://us.battle.net/oauth/token"
const requiredParams string = "?locale=en_US&namespace=%s"
const rateLimitRetryWaitSeconds int = 2
const maxRetryAttempts = 2
var clienID string = getEnvVar("BATTLE_NET_CLIENT_ID")
var secret string = getEnvVar("BATTLE_NET_SECRET")
var token string = createToken()
func getStatic(region, path string) *[]byte {
var namespace = "static-" + region
var staticPath = "data/wow/" + path
return get(region, namespace, staticPath)
}
func getDynamic(region, path string) *[]byte {
var namespace = "dynamic-" + region
var dynamicPath = "data/wow/" + path
return get(region, namespace, dynamicPath)
}
func getProfile(region, path string) *[]byte {
var namespace = "profile-" + region
var profilePath = "profile/wow/character/" + path
return get(region, namespace, profilePath)
}
func getMedia(region, path string) *[]byte {
var namespace = "static-" + region
var mediaPath = "data/wow/media/" + path
return get(region, namespace, mediaPath)
}
func getIcon(region, path string) string {
type AssetJSON struct {
Key string
Value string
}
type IconJSON struct {
Assets []AssetJSON
}
var data *[]byte = getMedia(region, path)
var iconJSON IconJSON
err := safeUnmarshal(data, &iconJSON)
if err != nil {
logger.Printf("%s parsing icon failed, using empty string: %s", warnPrefix, err)
return ""
}
for _, asset := range iconJSON.Assets {
if asset.Key == "icon" {
href := asset.Value
start := strings.LastIndex(href, "/") + 1
end := strings.LastIndex(href, ".")
return href[start:end]
}
}
return ""
}
func get(region, namespace, path string) *[]byte {
return getWithRetry(region, namespace, path, 1)
}
func getWithRetry(region, namespace, path string, attempt int) *[]byte {
var params string = fmt.Sprintf(requiredParams, strings.ToLower(namespace))
var url string = fmt.Sprintf(baseURI, strings.ToLower(region), path, params)
var req, err = http.NewRequest("GET", url, nil)
if err != nil {
logger.Printf("%s Failed to create request for '%s': %s", errPrefix, path, err)
return nil
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err := http.DefaultClient.Do(req)
if err != nil {
logger.Printf("%s GET '%s' failed: %s", errPrefix, path, err)
return nil
}
defer resp.Body.Close()
if resp.StatusCode == 429 {
time.Sleep(time.Duration(rateLimitRetryWaitSeconds) * time.Second)
return get(region, namespace, path)
}
if resp.StatusCode != 200 {
if attempt > maxRetryAttempts {
return nil
}
time.Sleep(time.Duration(rateLimitRetryWaitSeconds) * time.Second)
return getWithRetry(region, namespace, path, attempt+1)
}
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
logger.Printf("%s reading body of '%s' failed: %s", errPrefix, path, err)
return nil
}
return &body
}
func createToken() string {
d := url.Values{"grant_type": {"client_credentials"}}
req, err := http.NewRequest("POST", oauthURI, strings.NewReader(d.Encode()))
if err != nil {
logger.Fatalf("%s creating token failed: %s", errPrefix, err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(clienID, secret)
cli := &http.Client{}
resp, err := cli.Do(req)
if err != nil {
logger.Fatalf("%s creating token failed: %s", errPrefix, err)
}
if resp.StatusCode != 200 {
logger.Fatalf("%s received %d creating token: %s", errPrefix, resp.StatusCode, resp.Body)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
logger.Fatalf("%s reading token body failed: %s", errPrefix, err)
}
var accessTokenResponse = new(accessTokenResponse)
err = safeUnmarshal(&body, &accessTokenResponse)
if err != nil {
logger.Fatalf("%s unmarshalling token response failed: %s", errPrefix, err)
}
return accessTokenResponse.Token
}
// accessTokenResponse : response from an OAuth token request
type accessTokenResponse struct {
Token string `json:"access_token"`
Type string `json:"token_type"`
Expires int `json:"expires_in"`
}
// key : API key containing an HREF
type key struct {
Href string
}
// keyedValue : API element containing a name, ID, and Key
type keyedValue struct {
Key key
Name string
ID int
}
// typedName : API type and name
type typedName struct {
Type string
Name string
}