Files
umadump-gametora/umadump-gametora.go

162 lines
3.9 KiB
Go

package main
import (
"cmp"
"context"
"encoding/json"
"flag"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"os/signal"
"path/filepath"
"time"
)
type DumpCard struct {
ID int `json:"card_id"`
Rarity int `json:"rarity"`
// Level int `json:"talent_level"`
// CreateTime int `json:"create_time"`
}
type DumpSupport struct {
ID int `json:"support_card_id"`
LB int `json:"limit_break_count"`
// many other fields we don't need
}
type Collection struct {
App string `json:"app"` // "gametora"
Game string `json:"game"` // "umamusume"
Type string `json:"type"` // "collection"
Version int `json:"version"` // 4
Timestamp string `json:"timestamp"` // time.Now().String()
Servers map[string]CollectionServer `json:"servers"`
}
type CollectionServer struct {
CharCards map[string]int `json:"charCards"`
Supports map[string]int `json:"supports"`
Veterans struct{} `json:"-"` // `json:"veterans"`
}
type Localize struct {
CardID int `json:"card_id"` // character card, or trainee
SupportID int `json:"id"` // support card
TID string `json:"tid"`
// many other fields we don't need
}
func decode[T any](r io.Reader) ([]T, error) {
var l []T
d := json.NewDecoder(r)
err := d.Decode(&l)
return l, err
}
func tidMap(ctx context.Context, url string) (map[int]string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
l, err := decode[Localize](resp.Body)
m := make(map[int]string, len(l))
for _, v := range l {
m[cmp.Or(v.CardID, v.SupportID)] = v.TID
}
return m, err
}
func filejson[T any](path string) ([]T, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return decode[T](f)
}
func main() {
ts := time.Now().UTC().Format(time.DateOnly) // can't use RFC3339 because :s in filename
var (
dumpDir string
out string
)
flag.StringVar(&dumpDir, "dump", ".", "`dir`ectory containing umadump output files")
flag.StringVar(&out, "o", fmt.Sprintf("uma-col-all-%s.json", ts), "output `file` (stdout if empty)")
flag.Parse()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
go func() {
<-ctx.Done()
stop()
}()
var err error
w := os.Stdout
if out != "" {
w, err = os.Create(out)
if err != nil {
fatal("output", slog.Any("err", err))
}
}
charas, err := filejson[DumpCard](filepath.Join(dumpDir, "card_data.json"))
if err != nil {
fatal("character dump", slog.Any("err", err))
}
supports, err := filejson[DumpSupport](filepath.Join(dumpDir, "support_card_data.json"))
if err != nil {
fatal("support card dump", slog.Any("err", err))
}
charaTID, err := tidMap(ctx, "https://gametora.com/loc/umamusume/character_cards.json")
if err != nil {
fatal("character localization file", slog.Any("err", err))
}
supportTID, err := tidMap(ctx, "https://gametora.com/loc/umamusume/support_cards.json")
if err != nil {
fatal("support card localization file", slog.Any("err", err))
}
charaLevels := make(map[string]int, len(charas))
for _, v := range charas {
charaLevels[charaTID[v.ID]] = v.Rarity
}
supportLevels := make(map[string]int, len(supports))
for _, v := range supports {
supportLevels[supportTID[v.ID]] = v.LB
}
collection := Collection{
App: "gametora",
Game: "umamusume",
Type: "collection",
Version: 4,
Timestamp: ts,
Servers: map[string]CollectionServer{
"en": {
CharCards: charaLevels,
Supports: supportLevels,
},
},
}
b, err := json.Marshal(collection)
if err != nil {
panic(err)
}
if _, err := w.Write(b); err != nil {
fatal("writing output", slog.Any("err", err))
}
}
func fatal(msg string, args ...any) {
slog.Error(msg, args...)
os.Exit(1)
}