package main import ( "encoding/json" "fmt" "net/http" "net/url" "os" "strings" "time" ) // Card represents the structure of a card in the API response. type Card struct { ID string `json:"id"` Slug string `json:"slug"` Name string `json:"name"` Hotscore int `json:"hotscore"` Guardian Guardian `json:"guardian"` Elements []Element `json:"elements"` Variants []Variant `json:"variants"` } // Guardian represents the structure of a guardian in the card details. type Guardian struct { ID string `json:"id"` Type string `json:"type"` Rarity string `json:"rarity"` TypeText string `json:"typeText"` SubType string `json:"subType"` RulesText string `json:"rulesText"` Cost int `json:"cost"` Attack *int `json:"attack"` Defense *int `json:"defense"` Life *int `json:"life"` WaterThreshold int `json:"waterThreshold"` EarthThreshold int `json:"earthThreshold"` FireThreshold int `json:"fireThreshold"` AirThreshold int `json:"airThreshold"` CardID string `json:"cardId"` } // Element represents the structure of an element in the card details. type Element struct { ID string `json:"id"` Name string `json:"name"` } // Variant represents the structure of a variant in the card details. type Variant struct { ID string `json:"id"` Slug string `json:"slug"` Src string `json:"src"` Finish string `json:"finish"` Product string `json:"product"` Artist string `json:"artist"` FlavorText string `json:"flavorText"` CardID string `json:"cardId"` SetCardID string `json:"setCardId"` SetCard SetCard `json:"setCard"` } // SetCard represents the structure of a set card in the card details. type SetCard struct { ID string `json:"id"` Slug string `json:"slug"` SetID string `json:"setId"` CardID string `json:"cardId"` Meta MetaData `json:"meta"` SetDetails SetDetails `json:"set"` } // MetaData represents the structure of meta data in the set card details. type MetaData struct { ID string `json:"id"` Type string `json:"type"` Rarity string `json:"rarity"` TypeText string `json:"typeText"` SubType string `json:"subType"` RulesText string `json:"rulesText"` Cost int `json:"cost"` Attack *int `json:"attack"` Defense *int `json:"defense"` Life *int `json:"life"` WaterThreshold int `json:"waterThreshold"` EarthThreshold int `json:"earthThreshold"` FireThreshold int `json:"fireThreshold"` AirThreshold int `json:"airThreshold"` SetCardID string `json:"setCardId"` } // SetDetails represents the structure of set details in the set card. type SetDetails struct { ID string `json:"id"` Name string `json:"name"` ReleaseDate string `json:"releaseDate"` } // JSONPayload represents the structure of the nested JSON for the API request. type JSONPayload struct { Query string `json:"query"` Sort string `json:"sort"` Set string `json:"set"` Filters []interface{} `json:"filters"` Limit int `json:"limit"` VariantLimit bool `json:"variantLimit"` CollectionLimit bool `json:"collectionLimit"` Cursor int `json:"cursor"` Direction string `json:"direction"` } // Input represents the top-level structure of the input JSON for the API request. type Input struct { Part0 struct { JSON JSONPayload `json:"json"` } `json:"0"` } // fetchCards fetches cards from the API using the provided limit, cursor, and set type. func fetchCards(limit, cursor int, setType string) ([]*Card, error) { // Define the input data structure input := Input{ Part0: struct { JSON JSONPayload `json:"json"` }{ JSON: JSONPayload{ Query: "", Sort: "relevance", Set: setType, Filters: []interface{}{}, Limit: limit, VariantLimit: false, CollectionLimit: false, Cursor: cursor, Direction: "forward", }, }, } // Serialize the input data structure to JSON jsonData, err := json.Marshal(input) if err != nil { return nil, fmt.Errorf("failed to marshal input data: %w", err) } // URL encode the JSON data urlEncodedInput := url.QueryEscape(string(jsonData)) // Construct the full URL apiURL := fmt.Sprintf("https://curiosa.io/api/trpc/card.search?batch=1&input=%s", urlEncodedInput) // Create a new HTTP request req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } // Set essential headers setHeaders(req) // Perform the HTTP request start := time.Now() client := &http.Client{} resp, err := client.Do(req) duration := time.Since(start) if err != nil { return nil, fmt.Errorf("failed to perform request: %w", err) } defer resp.Body.Close() // Check for non-200 status codes if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("non-200 response: %s", resp.Status) } // Read and decode the JSON response var result []struct { Result struct { Data struct { JSON struct { Cards []*Card `json:"cards"` } `json:"json"` } `json:"data"` } `json:"result"` } err = json.NewDecoder(resp.Body).Decode(&result) if err != nil { return nil, fmt.Errorf("failed to decode response body: %w", err) } // Extract cards from the result var cards []*Card if len(result) > 0 { cards = result[0].Result.Data.JSON.Cards } fmt.Printf("Cursor: %d, Limit: %d, Duration: %s, Status: %d, Cards: %d\n", cursor, limit, duration, resp.StatusCode, len(cards)) return cards, nil } // fetchAllCards fetches all cards by making multiple requests with different cursor values. func fetchAllCards(limit int, setType string) ([]*Card, error) { var allCards []*Card cursor := 0 for { cards, err := fetchCards(limit, cursor, setType) if err != nil { return nil, err } allCards = append(allCards, cards...) // If the number of returned cards is less than the limit, we've fetched all cards. if len(cards) < limit { break } // Update cursor for the next batch cursor += limit } return allCards, nil } func main() { const limit = 100 const dataDir = "./data" // Ensure the data directory exists err := os.MkdirAll(dataDir, os.ModePerm) if err != nil { logErrorAndExit(fmt.Errorf("failed to create data directory: %w", err)) } // Fetch and save beta cards saveCards("bet", "beta", limit, dataDir) // Fetch and save alpha cards saveCards("alp", "alpha", limit, dataDir) } // saveCards fetches all cards for the given set type and saves them to files. func saveCards(apiSetType, filePrefix string, limit int, dataDir string) { allCards, err := fetchAllCards(limit, apiSetType) if err != nil { logErrorAndExit(err) } // Write all cards to cards.json in the data directory filename := fmt.Sprintf("%s/%s_cards.json", dataDir, filePrefix) writeCardsToFile(filename, allCards) // Group cards by element and write to files groupedByElement := groupCardsByElement(allCards) for element, cards := range groupedByElement { filename := fmt.Sprintf("%s/%s_%s_cards.json", dataDir, filePrefix, strings.ToLower(element)) writeCardsToFile(filename, cards) } // Group cards by element, rarity, and type and write to files groupedByElementRarityType := groupCardsByElementRarityType(allCards) for key, cards := range groupedByElementRarityType { filename := fmt.Sprintf("%s/%s_%s_cards.json", dataDir, filePrefix, strings.ToLower(key)) writeCardsToFile(filename, cards) } fmt.Printf("All cards have been written to %s_cards.json. Total cards: %d\n", filePrefix, len(allCards)) } // setHeaders sets the necessary headers for the HTTP request. func setHeaders(req *http.Request) { req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0") req.Header.Set("Accept", "*/*") req.Header.Set("content-type", "application/json") } // logErrorAndExit logs the error to stderr and exits with a non-zero status code. func logErrorAndExit(err error) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } // writeCardsToFile writes the given cards to a JSON file with the given filename. func writeCardsToFile(filename string, cards []*Card) { file, err := os.Create(filename) if err != nil { logErrorAndExit(fmt.Errorf("failed to create %s: %w", filename, err)) } defer file.Close() prettyData, err := json.MarshalIndent(cards, "", " ") if err != nil { logErrorAndExit(fmt.Errorf("failed to marshal pretty JSON: %w", err)) } _, err = file.Write(prettyData) if err != nil { logErrorAndExit(fmt.Errorf("failed to write to %s: %w", filename, err)) } fmt.Printf("%s has been written. Total cards: %d\n", filename, len(cards)) } // groupCardsByElement groups the given cards by their element. func groupCardsByElement(cards []*Card) map[string][]*Card { grouped := make(map[string][]*Card) for _, card := range cards { for _, element := range card.Elements { grouped[element.Name] = append(grouped[element.Name], card) } } return grouped } // groupCardsByElementRarityType groups the given cards by their element, rarity, and type. func groupCardsByElementRarityType(cards []*Card) map[string][]*Card { grouped := make(map[string][]*Card) for _, card := range cards { for _, element := range card.Elements { key := fmt.Sprintf("%s_%s_%s", element.Name, strings.ToLower(card.Guardian.Rarity), strings.ToLower(card.Guardian.Type)) grouped[key] = append(grouped[key], card) } } return grouped }