226 lines
6.5 KiB
Python
226 lines
6.5 KiB
Python
"""Card database service for importing and querying card data."""
|
|
import httpx
|
|
from typing import List, Dict, Optional
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
|
|
class CardType(str, Enum):
|
|
"""Card types from Magic: The Gathering."""
|
|
CREATURES = "Creature"
|
|
INSTANT = "Instant"
|
|
SORCERY = "Sorcery"
|
|
ENCHANTMENT = "Enchantment"
|
|
ARTIFACT = "Artifact"
|
|
PLANE = "Plane"
|
|
PLANESWALKER = "Planeswalker"
|
|
LAND = "Land"
|
|
BATTLE = "Battle"
|
|
|
|
|
|
class CardColor(str, Enum):
|
|
"""Card colors."""
|
|
WHITE = "W"
|
|
BLUE = "U"
|
|
BLACK = "B"
|
|
RED = "R"
|
|
GREEN = "G"
|
|
COLORLESS = "C"
|
|
MULTICOLOR = "M"
|
|
SHARD = "S"
|
|
WIDGET = "X"
|
|
|
|
|
|
class CardRarity(str, Enum):
|
|
"""Card rarities."""
|
|
COMMON = "common"
|
|
UNCOMMON = "uncommon"
|
|
RARE = "rare"
|
|
MYTHIC = "mythic"
|
|
SPECIAL = "special"
|
|
|
|
|
|
@dataclass
|
|
class CardData:
|
|
"""Card information from card database."""
|
|
id: int
|
|
name: str
|
|
types: List[CardType]
|
|
colors: List[CardColor]
|
|
rarity: CardRarity
|
|
set_code: str
|
|
collector_number: str
|
|
flavor_text: Optional[str] = None
|
|
rules_text: Optional[str] = None
|
|
power: Optional[str] = None
|
|
toughness: Optional[str] = None
|
|
artist: Optional[str] = None
|
|
image_url: Optional[str] = None
|
|
provider_id: Optional[str] = None
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.name} ({self.set_code}-{self.collector_number})"
|
|
|
|
|
|
class CardDatabase:
|
|
"""Card database service for importing and querying card data."""
|
|
|
|
def __init__(self):
|
|
self.cards: Dict[int, CardData] = {}
|
|
self._next_id = 1
|
|
|
|
async def import_from_mtjson(self, url: str = "https://mtjson.xyz/api/5.0.0/") -> List[CardData]:
|
|
"""Import card data from MTJSON API."""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(url)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
imported_cards = []
|
|
for card_data in data:
|
|
card = self._parse_mtjson_card(card_data)
|
|
self.cards[self._next_id] = card
|
|
imported_cards.append(card)
|
|
self._next_id += 1
|
|
|
|
return imported_cards
|
|
|
|
def _parse_mtjson_card(self, data: dict) -> CardData:
|
|
"""Parse MTJSON card data into CardData."""
|
|
card_id = self._next_id
|
|
|
|
# Extract types
|
|
types = []
|
|
if "types" in data:
|
|
for type_str in data["types"]:
|
|
try:
|
|
types.append(CardType(type_str))
|
|
except ValueError:
|
|
pass
|
|
|
|
# Extract colors
|
|
colors = []
|
|
if "colors" in data:
|
|
for color_str in data["colors"]:
|
|
try:
|
|
colors.append(CardColor(color_str))
|
|
except ValueError:
|
|
pass
|
|
|
|
# Extract rarity
|
|
rarity = CardRarity.COMMON
|
|
if "rarity" in data:
|
|
try:
|
|
rarity = CardRarity(data["rarity"].lower())
|
|
except ValueError:
|
|
pass
|
|
|
|
# Extract set and collector number
|
|
set_code = ""
|
|
collector_number = ""
|
|
if "set" in data:
|
|
set_code = data["set"]
|
|
if "collectorNumber" in data:
|
|
collector_number = data["collectorNumber"]
|
|
|
|
# Extract image URL
|
|
image_url = None
|
|
if "imageUris" in data and "normal" in data["imageUris"]:
|
|
image_url = data["imageUris"]["normal"]
|
|
|
|
return CardData(
|
|
id=card_id,
|
|
name=data.get("name", ""),
|
|
types=types,
|
|
colors=colors,
|
|
rarity=rarity,
|
|
set_code=set_code,
|
|
collector_number=collector_number,
|
|
flavor_text=data.get("flavorText"),
|
|
rules_text=data.get("rulesText"),
|
|
power=data.get("power"),
|
|
toughness=data.get("toughness"),
|
|
artist=data.get("artist"),
|
|
image_url=image_url,
|
|
provider_id=data.get("multiverseId"),
|
|
)
|
|
|
|
def get_card_by_id(self, card_id: int) -> Optional[CardData]:
|
|
"""Get card by ID."""
|
|
return self.cards.get(card_id)
|
|
|
|
def get_card_by_name(self, name: str) -> List[CardData]:
|
|
"""Get cards by name (case-insensitive)."""
|
|
name_lower = name.lower()
|
|
return [card for card in self.cards.values() if card.name.lower() == name_lower]
|
|
|
|
def search_cards(
|
|
self,
|
|
query: str = "",
|
|
card_type: Optional[CardType] = None,
|
|
color: Optional[CardColor] = None,
|
|
rarity: Optional[CardRarity] = None,
|
|
set_code: Optional[str] = None,
|
|
limit: int = 50,
|
|
) -> List[CardData]:
|
|
"""Search cards with filters."""
|
|
results = list(self.cards.values())
|
|
|
|
# Filter by query
|
|
if query:
|
|
query_lower = query.lower()
|
|
results = [card for card in results if query_lower in card.name.lower()]
|
|
|
|
# Filter by type
|
|
if card_type:
|
|
results = [card for card in results if card_type in card.types]
|
|
|
|
# Filter by color
|
|
if color:
|
|
results = [card for card in results if color in card.colors]
|
|
|
|
# Filter by rarity
|
|
if rarity:
|
|
results = [card for card in results if card.rarity == rarity]
|
|
|
|
# Filter by set
|
|
if set_code:
|
|
results = [card for card in results if card.set_code == set_code.upper()]
|
|
|
|
return results[:limit]
|
|
|
|
def get_random_card(self) -> Optional[CardData]:
|
|
"""Get a random card from the database."""
|
|
import random
|
|
if not self.cards:
|
|
return None
|
|
return random.choice(list(self.cards.values()))
|
|
|
|
def get_card_count(self) -> int:
|
|
"""Get total number of cards in database."""
|
|
return len(self.cards)
|
|
|
|
|
|
# Singleton instance
|
|
card_database = CardDatabase()
|
|
|
|
|
|
async def import_cards() -> List[CardData]:
|
|
"""Import cards from MTJSON."""
|
|
return await card_database.import_from_mtjson()
|
|
|
|
|
|
def search_cards(**kwargs) -> List[CardData]:
|
|
"""Search cards with filters."""
|
|
return card_database.search_cards(**kwargs)
|
|
|
|
|
|
def get_card_by_name(name: str) -> List[CardData]:
|
|
"""Get cards by name."""
|
|
return card_database.get_card_by_name(name)
|
|
|
|
|
|
def get_card_by_id(card_id: int) -> Optional[CardData]:
|
|
"""Get card by ID."""
|
|
return card_database.get_card_by_id(card_id)
|