From ccc8eb0da1b7ed286127d58bd9aad85b9fb286e4 Mon Sep 17 00:00:00 2001 From: Milhound Date: Thu, 20 Mar 2025 23:34:58 -0500 Subject: [PATCH] Implemented World of Warcraft - Game Data APIs - PvP Season API --- .gitignore | 2 + battlenet/battlenet.go | 91 +++++++++++++++++++++++++ example/main.go | 52 +++++++++++++++ go.mod | 5 ++ go.sum | 2 + wow/gamedata/pvpseasonapi.go | 126 +++++++++++++++++++++++++++++++++++ 6 files changed, 278 insertions(+) create mode 100644 .gitignore create mode 100644 battlenet/battlenet.go create mode 100644 example/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 wow/gamedata/pvpseasonapi.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59e32fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +*.json \ No newline at end of file diff --git a/battlenet/battlenet.go b/battlenet/battlenet.go new file mode 100644 index 0000000..9c23588 --- /dev/null +++ b/battlenet/battlenet.go @@ -0,0 +1,91 @@ +package battlenet + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +const ( + STATIC string = "static" + DYNAMIC string = "dynamic" + PROFILE string = "profile" +) + +const ( + US string = "us" + EU string = "eu" + KR string = "kr" + TW string = "tw" +) + +type clientCredentialsAPI struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + Sub string `json:"sub"` +} + +type BattleNetAPIParams struct { + UrlOrEndpoint string + Namespace string + Region string + Token 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) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + } + if resp.StatusCode != 200 { + fmt.Println(resp.Status) + panic("Failed to get access token") + } + var credentials clientCredentialsAPI + err = json.Unmarshal(respBody, &credentials) + if err != nil { + fmt.Println(err) + panic("Failed to parse access token response") + } + fmt.Println("Access Token: ", credentials.AccessToken) + return credentials +} + +func BattleNetAPI(params BattleNetAPIParams) []byte { + 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) + } + fmt.Println(requestURL) + req, err := http.NewRequest("GET", requestURL, nil) + if err != nil { + fmt.Println(err) + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", params.Token)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + fmt.Println(err) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + } + if resp.StatusCode != 200 { + fmt.Println(resp.Status) + panic("Failed to process the request") + } + return (respBody) +} diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..41c627d --- /dev/null +++ b/example/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "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) + + // Get the current PVP season + 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 + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4c5ec07 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module battlenetapi + +go 1.24.1 + +require github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/wow/gamedata/pvpseasonapi.go b/wow/gamedata/pvpseasonapi.go new file mode 100644 index 0000000..6bffdab --- /dev/null +++ b/wow/gamedata/pvpseasonapi.go @@ -0,0 +1,126 @@ +package gamedata + +const ( + PvpSeasonIndexEndpoint string = "/data/wow/pvp-season/index" + PvpSeasonEndpoint string = "/data/wow/pvp-season/%d" + PvpLeaderboardIndexEndpoint string = "/data/wow/pvp-season/%d/pvp-leaderboard/index" + PvpLeaderboardEndpoint string = "/data/wow/pvp-season/%d/pvp-leaderboard/%s" + PvpRewardsIndexEndpoint string = "/data/wow/pvp-season/%d/pvp-reward/index" +) + +const ( + Bracket2v2 string = "2v2" + Bracket3v3 string = "3v3" + BracketRBG string = "rbg" + ClassDeathKnightBlood string = "deathknight-blood" + ClassDeathKnightFrost string = "deathknight-frost" + ClassDeathKnightUnholy string = "deathknight-unholy" + ClassDemonHunterHavoc string = "demonhunter-havoc" + ClassDemonHunterVengeance string = "demonhunter-vengeance" + ClassDruidBalance string = "druid-balance" + ClassDruidFeral string = "druid-feral" + ClassDruidGuardian string = "druid-guardian" + ClassDruidRestoration string = "druid-restoration" + ClassEvokerAugmentation string = "evoker-augmentation" + ClassEvokerDevasation string = "evoker-devastation" + ClassEvokerPreservation string = "evoker-preservation" + ClassHunterBeastMastery string = "hunter-beastmastery" + ClassHunterMarksmanship string = "hunter-marksmanship" + ClassHunterSurvival string = "hunter-survival" + ClassMageArcane string = "mage-arcane" + ClassMageFire string = "mage-fire" + ClassMageFrost string = "mage-frost" + ClassMonkBrewmaster string = "monk-brewmaster" + ClassMonkMistweaver string = "monk-mistweaver" + ClassMonkWindwalker string = "monk-windwalker" + ClassPaladinHoly string = "paladin-holy" + ClassPaladinProtection string = "paladin-protection" + ClassPaladinRetribution string = "paladin-retribution" + ClassPriestDiscipline string = "priest-discipline" + ClassPriestHoly string = "priest-holy" + ClassPriestShadow string = "priest-shadow" + ClassRogueAssassination string = "rogue-assassination" + ClassRogueOutlaw string = "rogue-outlaw" + ClassRogueSubtlety string = "rogue-subtlety" + ClassShamanElemental string = "shaman-elemental" + ClassShamanEnhancement string = "shaman-enhancement" + ClassShamanRestoration string = "shaman-restoration" + ClassWarlockAffliction string = "warlock-affliction" + ClassWarlockDemonology string = "warlock-demonology" + ClassWarlockDestruction string = "warlock-destruction" + ClassWarriorArms string = "warrior-arms" + ClassWarriorFury string = "warrior-fury" + 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"` +} + +type PvpSeasonAPI struct { + ID int `json:"id"` + Leaderboards href `json:"leaderboards"` + Rewards href `json:"rewards"` + SeasonStartTimestamp int `json:"season_start_timestamp"` + SeasonEndTimestamp int `json:"season_end_timestamp"` +} + +type PvpLeaderboardAPI struct { + Season idAndKey `json:"season"` + Name string `json:"name"` + Bracket idAndType `json:"bracket"` + Entries []struct { + Character struct { + Name string `json:"name"` + ID int `json:"id"` + Realm struct { + Key href `json:"key"` + ID int `json:"id"` + Slug string `json:"slug"` + } `json:"realm"` + } `json:"character"` + Faction struct { + Type string `json:"type"` + } `json:"faction"` + Rank int `json:"rank"` + Rating int `json:"rating"` + SeasonMatchStatistics struct { + Played int `json:"played"` + Won int `json:"won"` + Lost int `json:"lost"` + } `json:"season_match_statistics"` + Tier idAndKey `json:"tier"` + } `json:"entries"` +} + +type PvpRewardsIndexAPI struct { + Season idAndKey `json:"season"` + Rewards struct { + Bracket idAndType `json:"bracket"` + Achievement struct { + ID int `json:"id"` + Key href `json:"key"` + Name string `json:"name"` + } `json:"achievement"` + RatingCutOff int `json:"rating_cutoff"` + Faction struct { + Type string `json:"type"` + Name string `json:"name"` + } `json:"faction"` + } `json:"rewards"` +}