Compare commits

..

1 Commits

Author SHA1 Message Date
milhound 4dbe68cc04 Initial commit 2025-03-20 23:53:22 -05:00
12 changed files with 7 additions and 510 deletions
-2
View File
@@ -1,2 +0,0 @@
.env
*.json
+4 -16
View File
@@ -1,21 +1,9 @@
MIT License
Copyright (c) 2025 Daniel Milholland
Copyright (c) 2025 Milhound
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:
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 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.
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.
+3
View File
@@ -0,0 +1,3 @@
# battlenetapi
A simple Golang library for Battle.net APIs
-114
View File
@@ -1,114 +0,0 @@
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
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("Error creating request:", err)
return clientCredentialsAPI{}
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
return clientCredentialsAPI{}
}
if resp.StatusCode != 200 {
fmt.Println("Error response from server:", resp.Status)
return clientCredentialsAPI{}
}
var credentials clientCredentialsAPI
err = json.Unmarshal(respBody, &credentials)
if err != nil {
fmt.Println("Error parsing response body:", err)
return clientCredentialsAPI{}
}
fmt.Println("Access Token:", credentials.AccessToken)
return credentials
}
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 {
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("Request URL:", requestURL)
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
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("Error making request:", err)
return nil
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
return nil
}
if resp.StatusCode != 200 {
fmt.Printf("Error response from server: %s\n", resp.Status)
return nil
}
return respBody
}
-32
View File
@@ -1,32 +0,0 @@
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
View File
@@ -1,74 +0,0 @@
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
}
}
-5
View File
@@ -1,5 +0,0 @@
module battlenetapi
go 1.24.1
require github.com/joho/godotenv v1.5.1
-2
View File
@@ -1,2 +0,0 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
-49
View File
@@ -1,49 +0,0 @@
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)
}
-84
View File
@@ -1,84 +0,0 @@
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
View File
@@ -1,20 +0,0 @@
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"`
}
-112
View File
@@ -1,112 +0,0 @@
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 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"`
}