From 14c12c2cdb83fbe37be2a334e89530043c9182a8 Mon Sep 17 00:00:00 2001 From: Milhound Date: Fri, 21 Mar 2025 15:23:42 -0500 Subject: [PATCH] Add WoW Connected Realm APIs and update Examples --- battlenet/battlenet.go | 57 ++++++++++++++++------- example/getLeaderboard.go | 33 +++---------- example/getRealmStatus.go | 74 ++++++++++++++++++++++++++++++ main.go | 49 ++++++++++++++++++++ wow/gamedata/connectedrealm.go | 84 ++++++++++++++++++++++++++++++++++ 5 files changed, 254 insertions(+), 43 deletions(-) create mode 100644 example/getRealmStatus.go create mode 100644 main.go create mode 100644 wow/gamedata/connectedrealm.go diff --git a/battlenet/battlenet.go b/battlenet/battlenet.go index 9c23588..3bda583 100644 --- a/battlenet/battlenet.go +++ b/battlenet/battlenet.go @@ -33,59 +33,82 @@ type BattleNetAPIParams struct { Namespace string Region string Token string + Options interface{} +} + +type URLFormatter interface { + FormatURL(baseURL, endpoint, namespace, region string, options interface{}) string } func GetAccessToken(clientID string, clientSecret string) clientCredentialsAPI { const authenticationUrl string = "https://oauth.battle.net/token" resp, err := http.Post(authenticationUrl, "application/x-www-form-urlencoded", strings.NewReader(fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", clientID, clientSecret))) if err != nil { - fmt.Println(err) + fmt.Println("Error creating request:", err) + return clientCredentialsAPI{} } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { - fmt.Println(err) + fmt.Println("Error reading response body:", err) + return clientCredentialsAPI{} } if resp.StatusCode != 200 { - fmt.Println(resp.Status) - panic("Failed to get access token") + fmt.Println("Error response from server:", resp.Status) + return clientCredentialsAPI{} } var credentials clientCredentialsAPI err = json.Unmarshal(respBody, &credentials) if err != nil { - fmt.Println(err) - panic("Failed to parse access token response") + fmt.Println("Error parsing response body:", err) + return clientCredentialsAPI{} } - fmt.Println("Access Token: ", credentials.AccessToken) + fmt.Println("Access Token:", credentials.AccessToken) return credentials } -func BattleNetAPI(params BattleNetAPIParams) []byte { +func BattleNetAPI(params BattleNetAPIParams, formatter URLFormatter) []byte { + if params.UrlOrEndpoint == "" || params.Namespace == "" || params.Region == "" || params.Token == "" { + fmt.Println("Invalid parameters") + return nil + } + var requestURL string if strings.HasPrefix(params.UrlOrEndpoint, "http") { requestURL = params.UrlOrEndpoint } else { - var baseURL string = fmt.Sprintf("https://%s.api.blizzard.com", params.Region) - requestURL = fmt.Sprintf("%s%s?namespace=%s-%s", baseURL, params.UrlOrEndpoint, params.Namespace, params.Region) + baseURL := fmt.Sprintf("https://%s.api.blizzard.com", params.Region) + if formatter != nil { + requestURL = formatter.FormatURL(baseURL, params.UrlOrEndpoint, params.Namespace, params.Region, params.Options) + } else { + requestURL = fmt.Sprintf("%s%s?namespace=%s-%s", baseURL, params.UrlOrEndpoint, params.Namespace, params.Region) + } } - fmt.Println(requestURL) + + fmt.Println("Request URL:", requestURL) req, err := http.NewRequest("GET", requestURL, nil) if err != nil { - fmt.Println(err) + fmt.Println("Error creating request:", err) + return nil } req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", params.Token)) + resp, err := http.DefaultClient.Do(req) if err != nil { - fmt.Println(err) + fmt.Println("Error making request:", err) + return nil } defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) if err != nil { - fmt.Println(err) + fmt.Println("Error reading response body:", err) + return nil } if resp.StatusCode != 200 { - fmt.Println(resp.Status) - panic("Failed to process the request") + fmt.Printf("Error response from server: %s\n", resp.Status) + return nil } - return (respBody) + + return respBody } diff --git a/example/getLeaderboard.go b/example/getLeaderboard.go index 41c627d..673d34e 100644 --- a/example/getLeaderboard.go +++ b/example/getLeaderboard.go @@ -1,4 +1,4 @@ -package main +package example import ( "encoding/json" @@ -7,38 +7,19 @@ import ( "battlenetapi/battlenet" "battlenetapi/wow/gamedata" - - "github.com/joho/godotenv" ) -func main() { - // Load the .env file - err := godotenv.Load(".env") - if err != nil { - fmt.Println("Error loading .env file") - } - - // Authenticate with the BattleNet API - clientID := os.Getenv("CLIENT_ID") - clientSecret := os.Getenv("CLIENT_SECRET") - credentials := battlenet.GetAccessToken(clientID, clientSecret) - +func GetLeaderboard(params battlenet.BattleNetAPIParams, bracket string) { // Get the current PVP season - params := battlenet.BattleNetAPIParams{ - UrlOrEndpoint: gamedata.PvpSeasonIndexEndpoint, - Namespace: battlenet.DYNAMIC, - Region: battlenet.US, - Token: credentials.AccessToken, - } - response := battlenet.BattleNetAPI(params) + response := battlenet.BattleNetAPI(params, nil) var pvpIndex gamedata.PvpSeasonIndexAPI json.Unmarshal(response, &pvpIndex) // Get the leaderboards for the current PVP season and Bracket - shuffleOrBlitz := fmt.Sprintf("blitz-%s", gamedata.ClassDemonHunterHavoc) - params.UrlOrEndpoint = fmt.Sprintf(gamedata.PvpLeaderboardEndpoint, pvpIndex.CurrentSeason.ID, shuffleOrBlitz) - response = battlenet.BattleNetAPI(params) - file, err := os.Create(fmt.Sprintf("pvp_season_%d_leaderboard-bracket_%s.json", pvpIndex.CurrentSeason.ID, shuffleOrBlitz)) + + params.UrlOrEndpoint = fmt.Sprintf(gamedata.PvpLeaderboardEndpoint, pvpIndex.CurrentSeason.ID, bracket) + response = battlenet.BattleNetAPI(params, nil) + file, err := os.Create(fmt.Sprintf("pvp_season_%d_leaderboard-bracket_%s.json", pvpIndex.CurrentSeason.ID, bracket)) if err != nil { fmt.Println("Error creating file:", err) return diff --git a/example/getRealmStatus.go b/example/getRealmStatus.go new file mode 100644 index 0000000..ef6414a --- /dev/null +++ b/example/getRealmStatus.go @@ -0,0 +1,74 @@ +package example + +import ( + "battlenetapi/battlenet" + "battlenetapi/wow/gamedata" + "encoding/json" + "fmt" + "os" +) + +func GetRealmStatus(params battlenet.BattleNetAPIParams, formatter battlenet.URLFormatter) { + // Get the realm status + response := battlenet.BattleNetAPI(params, formatter) + var searchResults gamedata.ConnectedRealmSearchAPI + var data []gamedata.RealmSearchResult + err := json.Unmarshal(response, &searchResults) + if err != nil { + fmt.Println("Unable to parse ConnectedRealmSearchAPI") + return + } + + // Handle more than one page of data + currentPage := params.Options.(gamedata.RealmStatusParams).Page + if currentPage > 1 { + for currentPage <= searchResults.PageCount { + currentPage += 1 + + // Update page to current page + realmStatusParams := params.Options.(*gamedata.RealmStatusParams) + realmStatusParams.Page = currentPage + params.Options = realmStatusParams + + // Call next page + response := battlenet.BattleNetAPI(params, formatter) + json.Unmarshal(response, &searchResults) + data = append(data, searchResults.Results...) + } + } else { + data = searchResults.Results + } + + file, err := os.Create("realm_status.json") + if err != nil { + fmt.Println("Error creating file:", err) + return + } + defer file.Close() + + // Print out select information from the data + for _, realm := range data { + info := realm.Data.Realms[0] + fmt.Printf( + "%s-%s (%s - %s): %s (%s) \n", + info.Name.US, + info.Type.Name.US, + info.Category.US, + info.Timezone, + realm.Data.Status.Name.US, + realm.Data.Population.Name.US, + ) + } + + // Save complete data + searchResults.Results = data + results, err := json.Marshal(searchResults) + if err != nil { + fmt.Println("Unable to convert search results to a seralized object") + } + _, err = file.Write(results) + if err != nil { + fmt.Println("Error writing data to file:", err) + return + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d95cbe9 --- /dev/null +++ b/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "battlenetapi/battlenet" + "battlenetapi/example" + "battlenetapi/wow/gamedata" + "fmt" + "os" + + "github.com/joho/godotenv" +) + +func main() { + // Load the .env file + err := godotenv.Load(".env") + if err != nil { + fmt.Println("Error loading .env file") + } + + // Authenticate with the BattleNet API + clientID := os.Getenv("CLIENT_ID") + clientSecret := os.Getenv("CLIENT_SECRET") + credentials := battlenet.GetAccessToken(clientID, clientSecret) + + // Get the leaderboard for the shuffle bracket + params := battlenet.BattleNetAPIParams{ + UrlOrEndpoint: gamedata.PvpSeasonIndexEndpoint, + Namespace: battlenet.DYNAMIC, + Region: battlenet.US, + Token: credentials.AccessToken, + } + shuffleOrBlitz := fmt.Sprintf("blitz-%s", gamedata.ClassDemonHunterHavoc) + example.GetLeaderboard(params, shuffleOrBlitz) + + // Get current realm status + params = battlenet.BattleNetAPIParams{ + UrlOrEndpoint: gamedata.ConnectedRealmSearchEndpoint, + Namespace: battlenet.DYNAMIC, + Region: battlenet.US, + Token: credentials.AccessToken, + Options: gamedata.RealmStatusParams{ + Status: gamedata.UP, + OrderBy: "id", + Page: 1, + }, + } + formatter := gamedata.URLFormatterImpl{} + example.GetRealmStatus(params, formatter) +} diff --git a/wow/gamedata/connectedrealm.go b/wow/gamedata/connectedrealm.go new file mode 100644 index 0000000..01f1095 --- /dev/null +++ b/wow/gamedata/connectedrealm.go @@ -0,0 +1,84 @@ +package gamedata + +import "fmt" + +const ( + ConnectedRealmsIndexEndpoint = "/data/wow/connected-realm/index" + ConnectedRealmEndpoint = "/data/wow/connected-realm/%d" + ConnectedRealmSearchEndpoint = "/data/wow/search/connected-realm" +) + +type Realm struct { + ID int `json:"id"` + Name localized `json:"name"` + Region struct { + Name localized `json:"name"` + ID int `json:"id"` + } `json:"region"` + Category localized `json:"category"` + Locale string `json:"locale"` + Type struct { + Name localized `json:"name"` + Type string `json:"type"` + } `json:"type"` + Slug string `json:"slug"` + + Timezone string `json:"timezone"` + Tournament bool `json:"is_tournament"` +} + +type RealmSearchResult struct { + Key href `json:"key"` + Data struct { + Realms []Realm `json:"realms"` + ID int `json:"id"` + Queue bool `json:"has_queue"` + Status struct { + Name localized `json:"name"` + Type string `json:"type"` + } `json:"status"` + Population struct { + Name localized `json:"name"` + Type string `json:"type"` + } `json:"population"` + } `json:"data"` +} + +type RealmStatusParams struct { + Status string + Timezone string + OrderBy string + Page int +} + +type URLFormatterImpl struct{} + +func (u URLFormatterImpl) FormatURL(baseURL, endpoint, namespace, region string, options interface{}) string { + o := options.(RealmStatusParams) + requestURL := fmt.Sprintf("%s%s?namespace=%s-%s", baseURL, endpoint, namespace, region) + requestURL = fmt.Sprintf("%s&status.type=%s&realms.timezone=%s&orderby=%s&_page=%d", requestURL, o.Status, o.Timezone, o.OrderBy, o.Page) + return requestURL +} + +type localized struct { + IT string `json:"it_IT"` + RU string `json:"ru_RU"` + GB string `json:"en_GB"` + TW string `json:"zh_TW"` + KR string `json:"ko_KR"` + US string `json:"en_US"` + MX string `json:"es_MX"` + BR string `json:"pt_BR"` + ES string `json:"es_ES"` + CN string `json:"zh_CN"` + FR string `json:"fr_FR"` + DE string `json:"de_DE"` +} + +type ConnectedRealmSearchAPI struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + MaxPageSize int `json:"maxPageSize"` + PageCount int `json:"pageCount"` + Results []RealmSearchResult `json:"results"` +}