Compare commits

...

4 Commits

Author SHA1 Message Date
3bed2b956d Comment update - remove later 2025-06-10 18:39:57 -05:00
ccbd430f0a Add WoW Connected Realm APIs and update Examples 2025-03-21 15:48:27 -05:00
ae532dd193 Move generics to their own file 2025-03-21 15:48:27 -05:00
a6bfea6b65 Update example filename 2025-03-21 15:48:26 -05:00
8 changed files with 290 additions and 53 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Daniel Milholland
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -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)
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)
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
}

32
example/getLeaderboard.go Normal file
View File

@@ -0,0 +1,32 @@
package example
import (
"encoding/json"
"fmt"
"os"
"battlenetapi/battlenet"
"battlenetapi/wow/gamedata"
)
func GetLeaderboard(params battlenet.BattleNetAPIParams, bracket string) {
// Get the current PVP season
response := battlenet.BattleNetAPI(params, nil)
var pvpIndex gamedata.PvpSeasonIndexAPI
json.Unmarshal(response, &pvpIndex)
// Get the leaderboards for the current PVP season and desired Bracket
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
}
defer file.Close()
_, err = file.Write(response)
if err != nil {
fmt.Println("Error writing data to file:", err)
return
}
}

74
example/getRealmStatus.go Normal file
View File

@@ -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
}
}

View File

@@ -1,13 +1,12 @@
package main
import (
"encoding/json"
"battlenetapi/battlenet"
"battlenetapi/example"
"battlenetapi/wow/gamedata"
"fmt"
"os"
"battlenetapi/battlenet"
"battlenetapi/wow/gamedata"
"github.com/joho/godotenv"
)
@@ -23,30 +22,28 @@ func main() {
clientSecret := os.Getenv("CLIENT_SECRET")
credentials := battlenet.GetAccessToken(clientID, clientSecret)
// Get the current PVP season
// Get the leaderboard for the shuffle bracket
params := battlenet.BattleNetAPIParams{
UrlOrEndpoint: gamedata.PvpSeasonIndexEndpoint,
Namespace: battlenet.DYNAMIC,
Region: battlenet.US,
Token: credentials.AccessToken,
}
response := battlenet.BattleNetAPI(params)
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))
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
_, err = file.Write(response)
if err != nil {
fmt.Println("Error writing data to file:", err)
return
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)
}

View File

@@ -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"`
}

20
wow/gamedata/generics.go Normal file
View File

@@ -0,0 +1,20 @@
package gamedata
const (
UP string = "UP"
DOWN string = "DOWN"
)
type href struct {
Href string `json:"href"`
}
type idAndKey struct {
ID int `json:"id"`
Key href `json:"key"`
}
type idAndType struct {
ID int `json:"id"`
Type string `json:"type"`
}

View File

@@ -53,20 +53,6 @@ const (
ClassWarriorProtection string = "warrior-protection"
)
type href struct {
Href string `json:"href"`
}
type idAndKey struct {
ID int `json:"id"`
Key href `json:"key"`
}
type idAndType struct {
ID int `json:"id"`
Type string `json:"type"`
}
type PvpSeasonIndexAPI struct {
Seasons []idAndKey `json:"seasons"`
CurrentSeason idAndKey `json:"current_season"`