mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
fetch/gen magicitems subcommands vendor data/open5e/magicitems.json (237 SRD items) and classify them into a generated registry. magic_items.go holds the MagicItem struct + Kind enum + an init-time overlay merge where a hand-authored entry wins on ID collision, mirroring the spell and bestiary imports. Not yet wired into zone loot or the shop — that integration is a deliberate follow-up.
163 lines
4.8 KiB
Go
163 lines
4.8 KiB
Go
// Command open5e-import vendors Open5e SRD data (CC-BY-4.0) into the repo and
|
|
// regenerates the Go data files the plugin compiles against.
|
|
//
|
|
// It is a pull-once / use-many tool — the generated files are committed, so
|
|
// the running bot has no runtime dependency on the Open5e API.
|
|
//
|
|
// open5e-import fetch spells # vendor data/open5e/spells.json from the API
|
|
// open5e-import gen spells # classify → internal/plugin/dnd_spells_srd_data.go
|
|
// open5e-import fetch bestiary # vendor data/open5e/monsters.json from the API
|
|
// open5e-import gen bestiary # classify → internal/plugin/bestiary_srd_data.go
|
|
// open5e-import gen tuned # tuning formula → internal/plugin/bestiary_tuned_data.go
|
|
// open5e-import fetch magicitems # vendor data/open5e/magicitems.json from the API
|
|
// open5e-import gen magicitems # classify → internal/plugin/magic_items_srd_data.go
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
spellsAPIBase = "https://api.open5e.com/v1/spells/?document__slug=wotc-srd&limit=50"
|
|
spellsJSON = "data/open5e/spells.json"
|
|
spellsGenGo = "internal/plugin/dnd_spells_srd_data.go"
|
|
|
|
monstersAPIBase = "https://api.open5e.com/v1/monsters/?document__slug=wotc-srd&limit=50"
|
|
monstersJSON = "data/open5e/monsters.json"
|
|
bestiaryGenGo = "internal/plugin/bestiary_srd_data.go"
|
|
tunedGenGo = "internal/plugin/bestiary_tuned_data.go"
|
|
|
|
magicItemsAPIBase = "https://api.open5e.com/v1/magicitems/?document__slug=wotc-srd&limit=50"
|
|
magicItemsJSON = "data/open5e/magicitems.json"
|
|
magicItemsGenGo = "internal/plugin/magic_items_srd_data.go"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 3 {
|
|
usage()
|
|
}
|
|
verb, noun := os.Args[1], os.Args[2]
|
|
switch verb {
|
|
case "fetch":
|
|
switch noun {
|
|
case "spells":
|
|
must(fetchSpells())
|
|
case "bestiary":
|
|
must(fetchMonsters())
|
|
case "magicitems":
|
|
must(fetchMagicItems())
|
|
default:
|
|
usage()
|
|
}
|
|
case "gen":
|
|
switch noun {
|
|
case "spells":
|
|
must(genSpells())
|
|
case "bestiary":
|
|
must(genBestiary())
|
|
case "tuned":
|
|
must(genTuned())
|
|
case "magicitems":
|
|
must(genMagicItems())
|
|
default:
|
|
usage()
|
|
}
|
|
default:
|
|
usage()
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "usage: open5e-import fetch (spells|bestiary|magicitems) | gen (spells|bestiary|tuned|magicitems)")
|
|
os.Exit(2)
|
|
}
|
|
|
|
func must(err error) {
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "error:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// open5eSpell is the subset of the Open5e v1 spell schema we consume. Unknown
|
|
// fields are ignored; the vendored JSON keeps the full payload.
|
|
type open5eSpell struct {
|
|
Slug string `json:"slug"`
|
|
Name string `json:"name"`
|
|
Desc string `json:"desc"`
|
|
HigherLevel string `json:"higher_level"`
|
|
Range string `json:"range"`
|
|
Material string `json:"material"`
|
|
CanBeCastAsRitual bool `json:"can_be_cast_as_ritual"`
|
|
Duration string `json:"duration"`
|
|
RequiresConcentration bool `json:"requires_concentration"`
|
|
CastingTime string `json:"casting_time"`
|
|
LevelInt int `json:"level_int"`
|
|
School string `json:"school"`
|
|
// SpellLists is the structured class list, but the SRD dump leaves it
|
|
// incomplete (no paladin entries at all). DndClass is the free-text
|
|
// "Druid, Wizard" field and is the more complete source — mapClasses
|
|
// unions both. Keep both fields vendored.
|
|
SpellLists []string `json:"spell_lists"`
|
|
DndClass string `json:"dnd_class"`
|
|
}
|
|
|
|
type spellsPage struct {
|
|
Next string `json:"next"`
|
|
Results []open5eSpell `json:"results"`
|
|
}
|
|
|
|
// fetchSpells pages through the SRD spell list and writes the full result set
|
|
// to data/open5e/spells.json, pretty-printed for a reviewable diff.
|
|
func fetchSpells() error {
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
var all []open5eSpell
|
|
url := spellsAPIBase
|
|
for url != "" {
|
|
fmt.Fprintln(os.Stderr, "GET", url)
|
|
page, err := getSpellsPage(client, url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
all = append(all, page.Results...)
|
|
url = page.Next
|
|
}
|
|
fmt.Fprintf(os.Stderr, "fetched %d spells\n", len(all))
|
|
|
|
if err := os.MkdirAll(filepath.Dir(spellsJSON), 0o755); err != nil {
|
|
return err
|
|
}
|
|
out, err := json.MarshalIndent(all, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out = append(out, '\n')
|
|
return os.WriteFile(spellsJSON, out, 0o644)
|
|
}
|
|
|
|
func getSpellsPage(client *http.Client, url string) (*spellsPage, error) {
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("%s: HTTP %d", url, resp.StatusCode)
|
|
}
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var page spellsPage
|
|
if err := json.Unmarshal(body, &page); err != nil {
|
|
return nil, err
|
|
}
|
|
return &page, nil
|
|
}
|