Add version group entry with region_id 0 (no PokeAPI region) and region "lumiose". The Go fetch tool now skips route fetching for region_id 0 so manually provided data won't be overwritten by re-runs. Includes placeholder data file and box art. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
261 lines
7.4 KiB
Go
261 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Config structs for version_groups.json
|
|
type VersionGroupInfo struct {
|
|
Versions []string `json:"versions"`
|
|
Generation int `json:"generation"`
|
|
Region string `json:"region"`
|
|
RegionID int `json:"region_id"`
|
|
ExtraRegions []int `json:"extra_regions"`
|
|
Games map[string]GameInfo `json:"games"`
|
|
}
|
|
|
|
type GameInfo struct {
|
|
Name string `json:"name"`
|
|
Slug string `json:"slug"`
|
|
ReleaseYear int `json:"release_year"`
|
|
Color *string `json:"color"`
|
|
}
|
|
|
|
// Config structs for route_order.json
|
|
type RouteOrderFile struct {
|
|
Routes map[string][]string `json:"routes"`
|
|
Aliases map[string]string `json:"aliases"`
|
|
}
|
|
|
|
// Config structs for special_encounters.json
|
|
type SpecialEncountersFile struct {
|
|
Encounters map[string]map[string][]EncounterOutput `json:"encounters"`
|
|
Aliases map[string]string `json:"aliases"`
|
|
}
|
|
|
|
func getSpecialEncounters(se *SpecialEncountersFile, vgKey string) map[string][]EncounterOutput {
|
|
if se == nil {
|
|
return nil
|
|
}
|
|
if data, ok := se.Encounters[vgKey]; ok {
|
|
return data
|
|
}
|
|
if alias, ok := se.Aliases[vgKey]; ok {
|
|
if data, ok := se.Encounters[alias]; ok {
|
|
return data
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
clearCache := flag.Bool("clear-cache", false, "Delete cached API responses before fetching")
|
|
flag.Parse()
|
|
|
|
pokeapiURL := os.Getenv("POKEAPI_URL")
|
|
if pokeapiURL == "" {
|
|
pokeapiURL = "http://localhost:8000/api/v2"
|
|
}
|
|
|
|
// Resolve paths relative to this tool's location or use the standard layout
|
|
seedsDir := findSeedsDir()
|
|
dataDir := filepath.Join(seedsDir, "data")
|
|
cacheDir := filepath.Join(seedsDir, ".pokeapi_cache")
|
|
|
|
client := NewClient(pokeapiURL, cacheDir, 50)
|
|
|
|
if *clearCache {
|
|
if err := client.ClearCache(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Warning: could not clear cache: %v\n", err)
|
|
} else {
|
|
fmt.Println("Cleared API cache.")
|
|
}
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Connectivity check
|
|
fmt.Printf("Connecting to PokeAPI at %s...\n", pokeapiURL)
|
|
if _, err := client.Get(ctx, "pokemon-species/1"); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: Cannot connect to PokeAPI at %s\n %v\nStart the local PokeAPI server or set POKEAPI_URL.\n", pokeapiURL, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Load configs
|
|
versionGroups, err := loadJSON[map[string]VersionGroupInfo](filepath.Join(seedsDir, "version_groups.json"))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error loading version_groups.json: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
routeOrder, err := loadRouteOrder(filepath.Join(seedsDir, "route_order.json"))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error loading route_order.json: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
specialEnc, err := loadJSON[SpecialEncountersFile](filepath.Join(seedsDir, "special_encounters.json"))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Warning: could not load special_encounters.json: %v\n", err)
|
|
// Continue without special encounters
|
|
}
|
|
|
|
if err := os.MkdirAll(dataDir, 0o755); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error creating data dir: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Build games.json
|
|
var games []GameOutput
|
|
for _, vgInfo := range *versionGroups {
|
|
for _, gameInfo := range vgInfo.Games {
|
|
games = append(games, GameOutput{
|
|
Name: gameInfo.Name,
|
|
Slug: gameInfo.Slug,
|
|
Generation: vgInfo.Generation,
|
|
Region: vgInfo.Region,
|
|
ReleaseYear: gameInfo.ReleaseYear,
|
|
Color: gameInfo.Color,
|
|
})
|
|
}
|
|
}
|
|
|
|
writeJSON(filepath.Join(dataDir, "games.json"), games)
|
|
fmt.Printf("Wrote %d games to games.json\n", len(games))
|
|
|
|
// Process each version
|
|
pokeIDCollector := NewPokeIDCollector()
|
|
|
|
for vgKey, vgInfo := range *versionGroups {
|
|
if vgInfo.RegionID == 0 {
|
|
fmt.Printf("\nSkipping %s (no PokeAPI region, data managed manually)\n", vgKey)
|
|
continue
|
|
}
|
|
for _, verName := range vgInfo.Versions {
|
|
routes, err := processVersion(ctx, client, verName, vgInfo, vgKey, routeOrder, specialEnc, pokeIDCollector)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error processing %s: %v\n", verName, err)
|
|
os.Exit(1)
|
|
}
|
|
writeJSON(filepath.Join(dataDir, verName+".json"), routes)
|
|
}
|
|
}
|
|
|
|
// Fetch all species data (reused for pokemon discovery + evolutions)
|
|
speciesData, err := fetchAllSpecies(ctx, client)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error fetching species: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Fetch all Pokemon (base + all forms)
|
|
pokemonList, err := fetchAllPokemon(ctx, client, speciesData, pokeIDCollector.IDs())
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error fetching pokemon: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
writeJSON(filepath.Join(dataDir, "pokemon.json"), pokemonList)
|
|
fmt.Printf("\nWrote %d Pokemon to pokemon.json\n", len(pokemonList))
|
|
|
|
// Build set of all seeded PokeAPI IDs for evolution filtering
|
|
allSeededDex := make(map[int]bool)
|
|
for _, p := range pokemonList {
|
|
allSeededDex[p.PokeAPIID] = true
|
|
}
|
|
|
|
// Fetch evolution chains
|
|
evolutions, err := fetchEvolutionData(ctx, client, speciesData, allSeededDex)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error fetching evolutions: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
evolutions, err = applyEvolutionOverrides(evolutions, filepath.Join(dataDir, "evolution_overrides.json"))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error applying evolution overrides: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
writeJSON(filepath.Join(dataDir, "evolutions.json"), evolutions)
|
|
fmt.Printf("\nWrote %d evolution pairs to evolutions.json\n", len(evolutions))
|
|
|
|
fmt.Println("\nDone! JSON files written to seeds/data/")
|
|
fmt.Println("Review route ordering and curate as needed.")
|
|
}
|
|
|
|
// findSeedsDir locates the backend/src/app/seeds directory.
|
|
func findSeedsDir() string {
|
|
// Try relative to CWD (from repo root)
|
|
candidates := []string{
|
|
"backend/src/app/seeds",
|
|
"../../backend/src/app/seeds", // from tools/fetch-pokeapi/
|
|
}
|
|
for _, c := range candidates {
|
|
if _, err := os.Stat(filepath.Join(c, "version_groups.json")); err == nil {
|
|
abs, _ := filepath.Abs(c)
|
|
return abs
|
|
}
|
|
}
|
|
// Fallback: try to find from executable location
|
|
exe, _ := os.Executable()
|
|
exeDir := filepath.Dir(exe)
|
|
rel := filepath.Join(exeDir, "../../backend/src/app/seeds")
|
|
if _, err := os.Stat(filepath.Join(rel, "version_groups.json")); err == nil {
|
|
abs, _ := filepath.Abs(rel)
|
|
return abs
|
|
}
|
|
// Default
|
|
abs, _ := filepath.Abs("backend/src/app/seeds")
|
|
return abs
|
|
}
|
|
|
|
func loadJSON[T any](path string) (*T, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result T
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
func loadRouteOrder(path string) (map[string][]string, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rof RouteOrderFile
|
|
if err := json.Unmarshal(data, &rof); err != nil {
|
|
return nil, err
|
|
}
|
|
routes := make(map[string][]string)
|
|
for k, v := range rof.Routes {
|
|
routes[k] = v
|
|
}
|
|
for alias, target := range rof.Aliases {
|
|
routes[alias] = routes[target]
|
|
}
|
|
return routes, nil
|
|
}
|
|
|
|
func writeJSON(path string, data interface{}) {
|
|
content, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error marshaling JSON for %s: %v\n", path, err)
|
|
os.Exit(1)
|
|
}
|
|
content = append(content, '\n')
|
|
if err := os.WriteFile(path, content, 0o644); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf(" -> %s\n", path)
|
|
}
|