├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── account.go ├── account_test.go ├── client.go ├── client_test.go ├── endpoint.go ├── error.go ├── profile.go ├── profile_test.go ├── response.go ├── response_test.go └── sc2.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Idea IDE files 2 | .idea/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.6 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Mitchell Hashimoto 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-bnet 2 | 3 | go-bnet is a Go client library for accessing the 4 | [Battle.net API](https://dev.battle.net). In addition to providing an API 5 | client, this package provides OAuth endpoints. 6 | 7 | **Documentation:** [![GoDoc](https://godoc.org/github.com/mitchellh/go-bnet?status.svg)](https://godoc.org/github.com/mitchellh/go-bnet) 8 | 9 | **Build Status:** [![Build Status](https://travis-ci.org/mitchellh/go-bnet.svg?branch=master)](https://travis-ci.org/mitchellh/go-bnet) 10 | 11 | **API Coverage:** Currently only the account information API is implemented. 12 | However, the base helpers are there to easily and quickly implement any other 13 | APIs such as the WoW or SC2 data. 14 | 15 | ## Usage 16 | 17 | ```go 18 | import "github.com/mitchellh/go-bnet" 19 | ``` 20 | 21 | ### Authentication 22 | 23 | Authenticate using the [Go OAuth2](https://golang.org/x/oauth2) library. 24 | Endpoints are provided via the `Endpoint` function. A guide to using OAuth2 25 | to authenticate [is available in this blog post](https://blog.kowalczyk.info/article/f/Accessing-GitHub-API-from-Go.html). 26 | The blog post uses GitHub as an example but it is almost identical for 27 | Battle.net and this library. 28 | 29 | Battle.net endpoints are region-specific, so specify the region to the 30 | `Endpoint` function and use the resulting value. Example: 31 | 32 | ```go 33 | oauthCfg := &oauth2.Config{ 34 | // Get from dev.battle.net 35 | ClientID: "", 36 | ClientSecret: "", 37 | 38 | // Endpoint from this library 39 | Endpoint: bnet.Endpoint("us"), 40 | } 41 | ``` 42 | 43 | Once you have access to the OAuth client, you can initilize the Battle.net 44 | API client: 45 | 46 | ```go 47 | // Token from prior auth 48 | authClient := oauthCfg.Client(oauth2.NoContext, token) 49 | 50 | // Initialize the client 51 | client := bnet.NewClient(oauthClient) 52 | 53 | // ... API calls 54 | ``` 55 | 56 | ### API Calls 57 | 58 | Once a client is made, basic API calls can easliy be made: 59 | 60 | ```go 61 | user, resp, err := client.Account().User() 62 | fmt.Printf("User: %#v", user) 63 | ``` 64 | 65 | All API calls return a `*Response` value in addition to a richer type 66 | and error. The response contains the http.Response as well as metadata 67 | such as quotas, QPS, etc. from Battle.net 68 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | // AccountService has account-related APIs. See Client. 4 | type AccountService struct { 5 | client *Client 6 | } 7 | 8 | // User represents the user information for a Battle.net account 9 | type User struct { 10 | ID int `json:"id"` 11 | BattleTag string `json:"battletag"` 12 | } 13 | 14 | // User calls the /account/user endpoint. See Battle.net docs. 15 | func (s *AccountService) User() (*User, *Response, error) { 16 | req, err := s.client.NewRequest("GET", "account/user", nil) 17 | if err != nil { 18 | return nil, nil, err 19 | } 20 | 21 | var user User 22 | resp, err := s.client.Do(req, &user) 23 | if err != nil { 24 | return nil, resp, err 25 | } 26 | 27 | return &user, resp, nil 28 | } 29 | -------------------------------------------------------------------------------- /account_test.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestAccountService_User(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | mux.HandleFunc("/account/user", func(w http.ResponseWriter, r *http.Request) { 15 | testMethod(t, r, "GET") 16 | fmt.Fprint(w, `{ 17 | "id": 12345, 18 | "battletag": "foobar#1234" 19 | }`) 20 | }) 21 | 22 | actual, _, err := client.Account().User() 23 | if err != nil { 24 | t.Fatalf("err: %s", err) 25 | } 26 | 27 | want := &User{ID: 12345, BattleTag: "foobar#1234"} 28 | if !reflect.DeepEqual(actual, want) { 29 | t.Fatalf("returned %+v, want %+v", actual, want) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | libraryVersion = "0.1" 16 | userAgent = "go-bnet/" + libraryVersion 17 | ) 18 | 19 | // Client is the API client for Battle.net. Create this using NewClient. 20 | // This can also be constructed manually but it isn't recommended. 21 | type Client struct { 22 | // Client is the HTTP client to use for communication. 23 | Client *http.Client 24 | 25 | // BaseURL is the base URL for API requests. This should match 26 | // the region with the auth region used for Client. 27 | BaseURL *url.URL 28 | 29 | // UserAgent is the user agent to set on API requests. 30 | UserAgent string 31 | } 32 | 33 | // NewClient creates a new Battle.net client. 34 | // 35 | // region must be a valid Battle.net region. This will not validate it 36 | // is valid. 37 | // 38 | // The http.Client argument should usually be retrieved via the 39 | // oauth2 Go library NewClient function. It must be a client that 40 | // automatically injects authentication details into requests. 41 | func NewClient(region string, c *http.Client) *Client { 42 | region = strings.ToLower(region) 43 | 44 | if c == nil { 45 | c = http.DefaultClient 46 | } 47 | 48 | // Determine the API base URL based on the region 49 | baseURLStr := fmt.Sprintf("https://%s.api.battle.net/", region) 50 | if region == "cn" { 51 | baseURLStr = "https://api.battlenet.com.cn/" 52 | } 53 | 54 | baseURL, err := url.Parse(baseURLStr) 55 | if err != nil { 56 | // We panic because we manually construct it above so it should 57 | // never really fail unless the user gives us a REALLY bad region. 58 | panic(err) 59 | } 60 | 61 | return &Client{ 62 | Client: c, 63 | BaseURL: baseURL, 64 | UserAgent: userAgent, 65 | } 66 | } 67 | 68 | func (c *Client) Account() *AccountService { 69 | return &AccountService{client: c} 70 | } 71 | 72 | func (c *Client) Profile() *ProfileService { 73 | return &ProfileService{client: c} 74 | } 75 | 76 | // NewRequest creates an API request. A relative URL can be provided in urlStr, 77 | // in which case it is resolved relative to the BaseURL of the Client. 78 | // Relative URLs should always be specified without a preceding slash. If 79 | // specified, the value pointed to by body is JSON encoded and included as the 80 | // request body. 81 | func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { 82 | rel, err := url.Parse(urlStr) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | u := c.BaseURL.ResolveReference(rel) 88 | 89 | var buf io.ReadWriter 90 | if body != nil { 91 | buf = new(bytes.Buffer) 92 | err := json.NewEncoder(buf).Encode(body) 93 | if err != nil { 94 | return nil, err 95 | } 96 | } 97 | 98 | req, err := http.NewRequest(method, u.String(), buf) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | req.Header.Add("Accept", "application/json") 104 | if c.UserAgent != "" { 105 | req.Header.Add("User-Agent", c.UserAgent) 106 | } 107 | 108 | return req, nil 109 | } 110 | 111 | // Do sends an API request and returns the API response. The API response is 112 | // JSON decoded and stored in the value pointed to by v, or returned as an 113 | // error if an API error has occurred. If v implements the io.Writer 114 | // interface, the raw response body will be written to v, without attempting to 115 | // first decode it. 116 | func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { 117 | resp, err := c.Client.Do(req) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | defer func() { 123 | // Drain up to 512 bytes and close the body to let the Transport reuse the connection 124 | io.CopyN(ioutil.Discard, resp.Body, 512) 125 | resp.Body.Close() 126 | }() 127 | 128 | response := newResponse(resp) 129 | 130 | if err := CheckError(resp); err != nil { 131 | return response, err 132 | } 133 | 134 | if v != nil { 135 | if w, ok := v.(io.Writer); ok { 136 | io.Copy(w, resp.Body) 137 | } else { 138 | err = json.NewDecoder(resp.Body).Decode(v) 139 | if err == io.EOF { 140 | err = nil // ignore EOF errors caused by empty response body 141 | } 142 | } 143 | } 144 | 145 | return response, err 146 | } 147 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "net/url" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | // mux is the HTTP request multiplexer used with the test server. 12 | mux *http.ServeMux 13 | 14 | // client is the Battle.net client being tested. 15 | client *Client 16 | 17 | // server is a test HTTP server used to provide mock API responses. 18 | server *httptest.Server 19 | ) 20 | 21 | // setup sets up a test HTTP server along with a bnet.Client that is 22 | // configured to talk to that test server. Tests should register handlers on 23 | // mux which provide mock responses for the API method being tested. 24 | func setup() { 25 | // test server 26 | mux = http.NewServeMux() 27 | server = httptest.NewServer(mux) 28 | 29 | // bnet client configured to use test server 30 | client = NewClient("us", nil) 31 | url, _ := url.Parse(server.URL) 32 | client.BaseURL = url 33 | } 34 | 35 | // teardown closes the test HTTP server. 36 | func teardown() { 37 | server.Close() 38 | } 39 | 40 | func testMethod(t *testing.T, r *http.Request, want string) { 41 | if got := r.Method; got != want { 42 | t.Errorf("Request method: %v, want %v", got, want) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /endpoint.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "sync" 7 | 8 | "golang.org/x/oauth2" 9 | ) 10 | 11 | // The variables below synchronize access to register an endpoint as 12 | // broken with the OAuth2 library. Battle.net uses a "broken" implementation 13 | // of OAuth2. 14 | var brokenLock sync.Mutex 15 | var brokenMap = map[string]struct{}{} 16 | 17 | // Endpoint returns the endpoint for the given region. This doesn't 18 | // validate the region name so you must use one that is valid from Battle.net. 19 | func Endpoint(region string) oauth2.Endpoint { 20 | region = strings.ToLower(region) 21 | domain := fmt.Sprintf("https://%s.battle.net/", region) 22 | if region == "cn" { 23 | domain = "https://www.battlenet.com.cn/" 24 | } 25 | 26 | // Register the broken provider 27 | brokenLock.Lock() 28 | defer brokenLock.Unlock() 29 | if _, ok := brokenMap[domain]; !ok { 30 | brokenMap[domain] = struct{}{} 31 | oauth2.RegisterBrokenAuthHeaderProvider(domain) 32 | } 33 | 34 | // Build the endpoint 35 | return oauth2.Endpoint{ 36 | AuthURL: domain + "oauth/authorize", 37 | TokenURL: domain + "oauth/token", 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | // ErrorResponse is the error response structure from the Battle.net API 11 | type ErrorResponse struct { 12 | Response *http.Response 13 | 14 | Code string `json:"error"` 15 | Description string `json:"error_description"` 16 | Scope string `json:"scope"` 17 | } 18 | 19 | func (r *ErrorResponse) Error() string { 20 | return fmt.Sprintf("%s: %s", r.Code, r.Description) 21 | } 22 | 23 | // CheckError checks for an error in the given response. 24 | func CheckError(r *http.Response) error { 25 | if c := r.StatusCode; 200 <= c && c <= 299 { 26 | return nil 27 | } 28 | 29 | errorResponse := &ErrorResponse{Response: r} 30 | data, err := ioutil.ReadAll(r.Body) 31 | if err == nil && data != nil { 32 | json.Unmarshal(data, errorResponse) 33 | } 34 | 35 | return errorResponse 36 | } 37 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | // ProfileService has OAuth Profile APIs. See Client. 4 | type ProfileService struct { 5 | client *Client 6 | } 7 | 8 | // SC2Profile represents the profile information for a user's Starcraft 2 profile. 9 | type SC2Profile struct { 10 | Characters []SC2Character `json:"characters"` 11 | } 12 | 13 | // SC2() calls the /sc2/profile/user endpoint. This endpoint uses OAuth2 14 | // to retrieve a user's Starcraft 2 profile. See Battle.net docs. 15 | func (s *ProfileService) SC2() (*SC2Profile, *Response, error) { 16 | req, err := s.client.NewRequest("GET", "sc2/profile/user", nil) 17 | if err != nil { 18 | return nil, nil, err 19 | } 20 | 21 | var profile SC2Profile 22 | resp, err := s.client.Do(req, &profile) 23 | if err != nil { 24 | return nil, resp, err 25 | } 26 | 27 | return &profile, resp, nil 28 | } 29 | -------------------------------------------------------------------------------- /profile_test.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | "reflect" 8 | ) 9 | const sc2ProfileResp = `{ "characters": 10 | [{ 11 | "id": 1234567, 12 | "realm": 1, 13 | "displayName": "foobar", 14 | "clanName": "foobar", 15 | "clanTag": "foobar", 16 | "profilePath": "/profile/1234567/1/foobar/", 17 | "portrait": { 18 | "x": -10, 19 | "y": -10, 20 | "w": 10, 21 | "h": 10, 22 | "offset": 10, 23 | "url": "http://media.blizzard.com/sc2/portraits/dummy.jpg" 24 | }, 25 | "career": { 26 | "primaryRace": "PROTOSS", 27 | "terranWins": 0, 28 | "protossWins": 0, 29 | "zergWins": 0, 30 | "highest1v1Rank": "DIAMOND", 31 | "seasonTotalGames": 0, 32 | "careerTotalGames": 100 33 | }, 34 | "swarmLevels": { 35 | "level": 10, 36 | "terran": { 37 | "level": 1, 38 | "totalLevelXP": 1000, 39 | "currentLevelXP": 0 40 | }, 41 | "zerg": { 42 | "level": 2, 43 | "totalLevelXP": 1000, 44 | "currentLevelXP": 0 45 | }, 46 | "protoss": { 47 | "level": 3, 48 | "totalLevelXP": 1000, 49 | "currentLevelXP": 0 50 | } 51 | }, 52 | "campaign": {}, 53 | "season": { 54 | "seasonId": 123, 55 | "seasonNumber": 1, 56 | "seasonYear": 2017, 57 | "totalGamesThisSeason": 0 58 | }, 59 | "rewards": { 60 | "selected": [12345678, 12345678], 61 | "earned": [12345678, 12345678] 62 | }, 63 | "achievements": { 64 | "points": { 65 | "totalPoints": 1234, 66 | "categoryPoints": {} 67 | }, 68 | "achievements": [{ 69 | "achievementId": 123456789, 70 | "completionDate": 123456789 71 | }] 72 | } 73 | }] 74 | }` 75 | 76 | func TestProfileService_SC2(t *testing.T) { 77 | setup() 78 | defer teardown() 79 | mux.HandleFunc("/sc2/profile/user", func(w http.ResponseWriter, r *http.Request) { 80 | testMethod(t, r, "GET") 81 | fmt.Fprint(w, sc2ProfileResp) 82 | }) 83 | actual, _, err := client.Profile().SC2() 84 | if err != nil { 85 | t.Fatalf("err: %s", err) 86 | } 87 | if actual.Characters == nil { 88 | t.Fatal("err: Profile is empty -> &SC2Profile{Characters: []SC2Character{}(nil)}") 89 | } 90 | want := SC2Character{ID: 1234567, 91 | Realm: 1, 92 | DisplayName: "foobar", 93 | ClanName: "foobar", 94 | ClanTag: "foobar", 95 | ProfilePath: "/profile/1234567/1/foobar/", 96 | Portrait: CharacterImage{-10, -10, 10, 10, 10, 97 | "http://media.blizzard.com/sc2/portraits/dummy.jpg"}, 98 | Career: Career{"PROTOSS", 0, 0, 0, 99 | "DIAMOND", 0, 100}, 100 | SwarmLevels: SwarmLevels{10, 101 | Level{1, 1000, 0}, 102 | Level{2, 1000, 0}, 103 | Level{3, 1000, 0}}, 104 | Season: Season{123, 1, 2017, 0}, 105 | Rewards: Rewards{[]int{12345678, 12345678}, []int{12345678, 12345678}}, 106 | Achievements: Achievements{Points{1234}, 107 | []Achievement{Achievement{123456789, 123456789}}}, 108 | } 109 | if !reflect.DeepEqual(actual.Characters[0], want) { 110 | t.Fatalf("returned %+v, want %+v", actual.Characters[0], want) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | // Reponse is a Battle.net API response. This wraps the standard http.Response 10 | // and provides convenient access to some of the metadata returned. 11 | type Response struct { 12 | *http.Response 13 | 14 | QPSCurrent int 15 | QPSAllotted int 16 | QuotaCurrent int 17 | QuotaAllotted int 18 | QuotaReset time.Time 19 | } 20 | 21 | func newResponse(r *http.Response) *Response { 22 | result := &Response{Response: r} 23 | result.parseMeta() 24 | return result 25 | } 26 | 27 | func (r *Response) parseMeta() { 28 | // Parse the basic ints 29 | intMaps := map[string]*int{ 30 | "X-Plan-Qps-Allotted": &r.QPSAllotted, 31 | "X-Plan-Qps-Current": &r.QPSCurrent, 32 | "X-Plan-Quota-Allotted": &r.QuotaAllotted, 33 | "X-Plan-Quota-Current": &r.QuotaCurrent, 34 | } 35 | 36 | for k, ptr := range intMaps { 37 | if v := r.Response.Header.Get(k); v != "" { 38 | *ptr, _ = strconv.Atoi(v) 39 | } 40 | } 41 | 42 | // Parse the reset time 43 | if v := r.Response.Header.Get("X-Plan-Quota-Reset"); v != "" { 44 | r.QuotaReset, _ = time.Parse("Monday, January 2, 2006 3:04:05 PM MST", v) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /response_test.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestResponse(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { 15 | w.Header().Set("X-Plan-Qps-Allotted", "100") 16 | w.Header().Set("X-Plan-Qps-Current", "5") 17 | w.Header().Set("X-Plan-Quota-Allotted", "36000") 18 | w.Header().Set("X-Plan-Quota-Current", "32") 19 | w.Header().Set("X-Plan-Quota-Reset", "Sunday, April 17, 2016 7:00:00 PM GMT") 20 | }) 21 | 22 | req, err := client.NewRequest("GET", "test", nil) 23 | if err != nil { 24 | t.Fatalf("err: %s", err) 25 | } 26 | 27 | actual, err := client.Do(req, nil) 28 | if err != nil { 29 | t.Fatalf("err: %s", err) 30 | } 31 | actual.Response = nil 32 | 33 | gmt, err := time.LoadLocation("GMT") 34 | if err != nil { 35 | t.Fatalf("err: %s", err) 36 | } 37 | 38 | want := &Response{ 39 | QPSCurrent: 5, 40 | QPSAllotted: 100, 41 | QuotaCurrent: 32, 42 | QuotaAllotted: 36000, 43 | QuotaReset: time.Date(2016, time.April, 17, 19, 0, 0, 0, gmt), 44 | } 45 | 46 | if !reflect.DeepEqual(actual, want) { 47 | t.Errorf("returned %+v, want %+v", actual, want) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sc2.go: -------------------------------------------------------------------------------- 1 | package bnet 2 | 3 | // SC2Service has Starcraft2-related APIs. See Client. 4 | type SC2Service struct { 5 | client *Client 6 | } 7 | 8 | // TODO: Create a 'Campaign' struct to represent a character's campaign progress. 9 | 10 | // CharacterImage is a character's portrait or avatar. 11 | type CharacterImage struct { 12 | X int `json:"x"` 13 | Y int `json:"y"` 14 | W int `json:"w"` 15 | H int `json:"h"` 16 | Offset int `json:"offset"` 17 | Url string `json:"url"` 18 | } 19 | 20 | // Career represents game statistics for a character's Battle.net career. 21 | type Career struct { 22 | PrimaryRace string `json:"primaryRace"` 23 | TerranWins int `json:"terranWins"` 24 | ProtossWins int `json:"protossWins"` 25 | ZergWins int `json:"zergWins"` 26 | Highest1v1Rank string `json:"highest1v1Rank"` 27 | SeasonTotalGames int `json:"seasonTotalGames"` 28 | CareerTotalGames int `json:"careerTotalGames"` 29 | } 30 | 31 | // Level is the current level and XP a character has earned. 32 | type Level struct { 33 | Level int `json:"level"` 34 | TotalLevelXP int `json:"totalLevelXP"` 35 | CurrentLevelXP int `json:"currentLevelXP"` 36 | } 37 | 38 | // SwarmLevels represents a character's level for each swarm (race) as well as their overall level. 39 | type SwarmLevels struct { 40 | Level int `json:"level"` 41 | Terran Level `json:"terran"` 42 | Zerg Level `json:"zerg"` 43 | Protoss Level `json:"protoss"` 44 | } 45 | 46 | // Season is the current Starcraft 2 online multiplayer season. 47 | type Season struct { 48 | ID int `json:"seasonId"` 49 | Number int `json:"seasonNumber"` 50 | Year int `json:"seasonYear"` 51 | TotalGames int `json:"totalGamesThisSeason"` 52 | } 53 | 54 | // Rewards represents selected and earned rewards for a profile. 55 | type Rewards struct { 56 | Selected []int `json:"selected"` 57 | Earned []int `json:"earned"` 58 | } 59 | 60 | // Points holds a character's total achievement points. 61 | type Points struct { 62 | Total int `json:"totalPoints"` 63 | } 64 | 65 | // Achievement represents a single Starcraft 2 achievement. 66 | type Achievement struct { 67 | ID int `json:"achievementId"` 68 | CompletionDate int `json:"completionDate"` 69 | } 70 | 71 | // Achievements represents achievement information for a Starcraft 2 profile. 72 | type Achievements struct { 73 | Points Points `json:"points"` 74 | Achievements []Achievement `json:"achievements"` 75 | } 76 | 77 | // SC2Character represents a character in a user's Starcraft 2 profile. 78 | type SC2Character struct { 79 | ID int `json:"id"` 80 | Realm int `json:"realm"` 81 | Name string `json:"name"` 82 | DisplayName string `json:"displayName"` 83 | ClanName string `json:"clanName"` 84 | ClanTag string `json:"clanTag"` 85 | ProfilePath string `json:"profilePath"` 86 | Portrait CharacterImage `json:"portrait"` 87 | Avatar CharacterImage `json:"avatar"` 88 | Career Career `json:"career"` 89 | SwarmLevels SwarmLevels `json:"swarmLevels"` 90 | Season Season `json:"season"` 91 | Rewards Rewards `json:"rewards"` 92 | Achievements Achievements `json:"achievements"` 93 | } 94 | 95 | --------------------------------------------------------------------------------