218 lines
5.5 KiB
Go
218 lines
5.5 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"runtime"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func errorContext(skip int) string {
|
||
|
pc, file, line, ok := runtime.Caller(skip)
|
||
|
if !ok {
|
||
|
return "unknown"
|
||
|
}
|
||
|
|
||
|
fn := runtime.FuncForPC(pc).Name()
|
||
|
return fmt.Sprintf("%s:%d %s", file, line, fn)
|
||
|
}
|
||
|
|
||
|
func APITraceError(format string, args ...interface{}) error {
|
||
|
traceInfo := errorContext(2)
|
||
|
return fmt.Errorf("[%s] "+format, append([]interface{}{traceInfo}, args...)...)
|
||
|
}
|
||
|
|
||
|
type HypixelPlayerAchievements struct {
|
||
|
BedwarsStar int `json:"bedwars_level"`
|
||
|
}
|
||
|
|
||
|
type HypixelPlayerBedwarsStats struct {
|
||
|
Experience int `json:"Experience"`
|
||
|
Winstreak int `json:"winstreak"`
|
||
|
Wins int `json:"wins_bedwars"`
|
||
|
Losses int `json:"losses_bedwars"`
|
||
|
GamesPlayed int `json:"games_played_bedwars"`
|
||
|
WinLossRatio float32 // wins / gamesplayed
|
||
|
|
||
|
Kills int `json:"kills_bedwars"`
|
||
|
Deaths int `json:"deaths_bedwars"`
|
||
|
KillDeathRatio float32 // kills / deaths
|
||
|
|
||
|
FinalKills int `json:"final_kills_bedwars"`
|
||
|
FinalDeaths int `json:"final_deaths_bedwars"`
|
||
|
FinalKillDeathRatio float32 // final kills / final deaths
|
||
|
|
||
|
BedsBroken int `json:"beds_broken_bedwars"`
|
||
|
BedsLost int `json:"beds_lost_bedwars"`
|
||
|
BedsBrokenLostRatio float32 // beds broken / beds lost
|
||
|
}
|
||
|
|
||
|
func (stats *HypixelPlayerBedwarsStats) CalculateRatios() {
|
||
|
// WLR
|
||
|
if stats.GamesPlayed > 0 {
|
||
|
stats.WinLossRatio = float32(stats.Wins) / float32(stats.GamesPlayed)
|
||
|
} else {
|
||
|
stats.WinLossRatio = float32(stats.Wins)
|
||
|
}
|
||
|
|
||
|
// KDR
|
||
|
if stats.Deaths > 0 {
|
||
|
stats.KillDeathRatio = float32(stats.Kills) / float32(stats.Deaths)
|
||
|
} else {
|
||
|
stats.KillDeathRatio = float32(stats.Kills)
|
||
|
}
|
||
|
|
||
|
// FKDR
|
||
|
if stats.FinalDeaths > 0 {
|
||
|
stats.FinalKillDeathRatio = float32(stats.FinalKills) / float32(stats.FinalDeaths)
|
||
|
} else {
|
||
|
stats.FinalKillDeathRatio = float32(stats.FinalKills)
|
||
|
}
|
||
|
|
||
|
// BBLR
|
||
|
if stats.BedsLost > 0 {
|
||
|
stats.BedsBrokenLostRatio = float32(stats.BedsBroken) / float32(stats.BedsLost)
|
||
|
} else {
|
||
|
stats.BedsBrokenLostRatio = float32(stats.BedsBroken)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (stats *HypixelPlayerBedwarsStats) IsWinstreakDisabled() bool {
|
||
|
return stats.Winstreak == -1
|
||
|
}
|
||
|
|
||
|
type HypixelPlayerStats struct {
|
||
|
Bedwars HypixelPlayerBedwarsStats `json:"Bedwars"`
|
||
|
}
|
||
|
|
||
|
type HypixelPlayer struct {
|
||
|
DisplayName string `json:"displayname"`
|
||
|
Achievements HypixelPlayerAchievements `json:"achievements"`
|
||
|
Stats HypixelPlayerStats `json:"stats"`
|
||
|
MonthlyRankColor string `json:"monthlyRankColor"`
|
||
|
RankPlusColor string `json:"rankPlusColor"`
|
||
|
NetworkExp json.Number `json:"networkExp"`
|
||
|
}
|
||
|
|
||
|
type HypixelPlayerResponse struct {
|
||
|
Success bool `json:"success"`
|
||
|
Player HypixelPlayer `json:"player"`
|
||
|
}
|
||
|
|
||
|
type HypixelAPIKey struct {
|
||
|
Value string
|
||
|
Uses int
|
||
|
RateLimit time.Duration
|
||
|
LimitUsesLeft int
|
||
|
}
|
||
|
|
||
|
func NewHypixelAPIKey(key string) *HypixelAPIKey {
|
||
|
return &HypixelAPIKey{
|
||
|
Value: key,
|
||
|
Uses: 0,
|
||
|
RateLimit: time.Millisecond * 1,
|
||
|
LimitUsesLeft: -1,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type HypixelAPI struct {
|
||
|
BaseURL string
|
||
|
APIKey HypixelAPIKey
|
||
|
}
|
||
|
|
||
|
func NewHypixelAPI(apiKey string) *HypixelAPI {
|
||
|
// TODO: Ensure valid key before creating
|
||
|
key := NewHypixelAPIKey(apiKey)
|
||
|
return &HypixelAPI{
|
||
|
BaseURL: "https://api.hypixel.net/v2/",
|
||
|
APIKey: *key,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *HypixelAPI) FetchPlayer(PlayerUUID string) (HypixelPlayerResponse, error) {
|
||
|
// TODO: Handle nicked players
|
||
|
if len(PlayerUUID) != 32 && len(PlayerUUID) != 36 {
|
||
|
return HypixelPlayerResponse{}, fmt.Errorf("invalid Player UUID! please pass in a valid UUID (Not a name!)")
|
||
|
}
|
||
|
RequestUrl := h.BaseURL + "player?uuid=" + PlayerUUID
|
||
|
|
||
|
client := &http.Client{
|
||
|
Timeout: 10 * time.Second,
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequest("GET", RequestUrl, nil)
|
||
|
if err != nil {
|
||
|
return HypixelPlayerResponse{}, APITraceError("failed to create HTTP request: %v", err)
|
||
|
}
|
||
|
req.Header.Set("API-Key", h.APIKey.Value)
|
||
|
|
||
|
resp, err := client.Do(req)
|
||
|
if err != nil {
|
||
|
return HypixelPlayerResponse{}, APITraceError("failed to send HTTP request: %v", err)
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
if resp.StatusCode != http.StatusOK {
|
||
|
return HypixelPlayerResponse{}, APITraceError("received non-200 response: %d", resp.StatusCode)
|
||
|
}
|
||
|
|
||
|
body, err := io.ReadAll(resp.Body)
|
||
|
if err != nil {
|
||
|
return HypixelPlayerResponse{}, APITraceError("failed to read response body: %v", err)
|
||
|
}
|
||
|
|
||
|
var playerResponse HypixelPlayerResponse
|
||
|
err = json.Unmarshal(body, &playerResponse)
|
||
|
if err != nil {
|
||
|
return HypixelPlayerResponse{}, APITraceError("failed to unmarshal JSON: %v", err)
|
||
|
}
|
||
|
|
||
|
if !playerResponse.Success {
|
||
|
return HypixelPlayerResponse{}, APITraceError("API returned unsuccessful response")
|
||
|
}
|
||
|
|
||
|
playerResponse.Player.Stats.Bedwars.CalculateRatios()
|
||
|
|
||
|
return playerResponse, nil
|
||
|
}
|
||
|
|
||
|
func (h *HypixelAPI) FetchPlayerAsync(PlayerUUID string, resultChan chan<- HypixelPlayerResponse, errorChan chan<- error) {
|
||
|
go func() {
|
||
|
result, err := h.FetchPlayer(PlayerUUID)
|
||
|
if err != nil {
|
||
|
errorChan <- err
|
||
|
return
|
||
|
}
|
||
|
resultChan <- result
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func (h *HypixelAPI) FetchPlayersAsync(PlayerUUIDs []string, resultChan chan<- HypixelPlayerResponse, errorChan chan<- error) {
|
||
|
var wg sync.WaitGroup
|
||
|
|
||
|
for _, uuid := range PlayerUUIDs {
|
||
|
wg.Add(1)
|
||
|
|
||
|
go func(uuid string) {
|
||
|
defer wg.Done()
|
||
|
|
||
|
result, err := h.FetchPlayer(uuid)
|
||
|
if err != nil {
|
||
|
errorChan <- err
|
||
|
return
|
||
|
}
|
||
|
|
||
|
resultChan <- result
|
||
|
}(uuid)
|
||
|
}
|
||
|
|
||
|
go func() {
|
||
|
wg.Wait()
|
||
|
close(resultChan)
|
||
|
close(errorChan)
|
||
|
}()
|
||
|
}
|