scrcards/main.go

330 lines
9.6 KiB
Go

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
}