mirror of
https://github.com/tiennm99/loldle-data.git
synced 2026-05-24 19:34:51 +00:00
170 lines
4.5 KiB
Go
170 lines
4.5 KiB
Go
package parser
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ddragonResponse represents the top-level ddragon championFull.json response.
|
|
type ddragonResponse struct {
|
|
Data map[string]ddragonChampion `json:"data"`
|
|
}
|
|
|
|
type ddragonChampion struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Title string `json:"title"`
|
|
Partype string `json:"partype"`
|
|
Tags []string `json:"tags"`
|
|
Skins []json.RawMessage `json:"skins"`
|
|
Image ChampionImage `json:"image"`
|
|
Stats struct {
|
|
AttackRange float64 `json:"attackrange"`
|
|
} `json:"stats"`
|
|
}
|
|
|
|
// ChampionImage holds sprite sheet metadata with fields ordered to match the expected output.
|
|
type ChampionImage struct {
|
|
Full string `json:"full"`
|
|
Sprite string `json:"sprite"`
|
|
Group string `json:"group"`
|
|
X int `json:"x"`
|
|
Y int `json:"y"`
|
|
W int `json:"w"`
|
|
H int `json:"h"`
|
|
}
|
|
|
|
// ChampionResult holds the enriched data for a single champion.
|
|
type ChampionResult struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Title string `json:"title"`
|
|
Resource string `json:"resource"`
|
|
Genre string `json:"genre"`
|
|
SkinCount int `json:"skinCount"`
|
|
Image ChampionImage `json:"image"`
|
|
Gender string `json:"gender"`
|
|
AttackType string `json:"attackType"`
|
|
ReleaseDate int `json:"releaseDate,omitempty"`
|
|
Region string `json:"region,omitempty"`
|
|
Lane string `json:"lane,omitempty"`
|
|
}
|
|
|
|
// ParseChampions fetches base champion data from ddragon and detects gender concurrently.
|
|
func ParseChampions(version string) ([]ChampionResult, error) {
|
|
url := fmt.Sprintf("https://ddragon.leagueoflegends.com/cdn/%s/data/en_US/championFull.json", version)
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetching ddragon: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var ddResp ddragonResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&ddResp); err != nil {
|
|
return nil, fmt.Errorf("decoding ddragon: %w", err)
|
|
}
|
|
|
|
// Fetch gender concurrently for all champions.
|
|
genderMap := make(map[string]string, len(ddResp.Data))
|
|
var mu sync.Mutex
|
|
var wg sync.WaitGroup
|
|
sem := make(chan struct{}, 10) // limit to 10 concurrent requests
|
|
|
|
for id := range ddResp.Data {
|
|
wg.Add(1)
|
|
go func(champID string) {
|
|
defer wg.Done()
|
|
sem <- struct{}{}
|
|
defer func() { <-sem }()
|
|
|
|
gender := detectGender(champID)
|
|
mu.Lock()
|
|
genderMap[champID] = gender
|
|
mu.Unlock()
|
|
}(id)
|
|
}
|
|
wg.Wait()
|
|
|
|
// Map data to result slice.
|
|
results := make([]ChampionResult, 0, len(ddResp.Data))
|
|
for _, champ := range ddResp.Data {
|
|
attackType := "range"
|
|
if champ.Stats.AttackRange < 500 {
|
|
attackType = "close"
|
|
}
|
|
|
|
results = append(results, ChampionResult{
|
|
ID: champ.ID,
|
|
Name: champ.Name,
|
|
Title: champ.Title,
|
|
Resource: champ.Partype,
|
|
Genre: strings.Join(champ.Tags, ","),
|
|
SkinCount: len(champ.Skins),
|
|
Image: champ.Image,
|
|
Gender: genderMap[champ.ID],
|
|
AttackType: attackType,
|
|
})
|
|
fmt.Printf("Data fetched for %s\n", champ.Name)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// universeChampionResponse represents the biography response from the Universe API.
|
|
type universeChampionResponse struct {
|
|
Champion struct {
|
|
Biography struct {
|
|
Full string `json:"full"`
|
|
} `json:"biography"`
|
|
} `json:"champion"`
|
|
}
|
|
|
|
// detectGender infers champion gender from biography pronoun frequency.
|
|
func detectGender(championID string) string {
|
|
// Special case: Renata's URL slug differs from her ID.
|
|
urlID := strings.ToLower(championID)
|
|
if urlID == "renata" {
|
|
urlID = "renataglasc"
|
|
}
|
|
|
|
url := fmt.Sprintf("https://universe-meeps.leagueoflegends.com/v1/en_us/champions/%s/index.json", urlID)
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return "divers"
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var ucResp universeChampionResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&ucResp); err != nil {
|
|
return "divers"
|
|
}
|
|
|
|
words := strings.Fields(strings.ToLower(ucResp.Champion.Biography.Full))
|
|
maleKeywords := map[string]bool{"he": true, "him": true, "his": true}
|
|
femaleKeywords := map[string]bool{"she": true, "her": true, "hers": true}
|
|
|
|
var maleCount, femaleCount int
|
|
for _, w := range words {
|
|
if maleKeywords[w] {
|
|
maleCount++
|
|
}
|
|
if femaleKeywords[w] {
|
|
femaleCount++
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case maleCount > femaleCount:
|
|
return "male"
|
|
case femaleCount > maleCount:
|
|
return "female"
|
|
default:
|
|
return "divers"
|
|
}
|
|
}
|