def build_mtgjson_card( sf_card: Dict[str, Any], sf_card_face: int = 0 ) -> List[Dict[str, Any]]: """ Build a mtgjson card (and all sub pieces of that card) :param sf_card: Card to build :param sf_card_face: Which part of the card (defaults to 0) :return: List of card(s) build (usually 1) """ mtgjson_cards: List[Dict[str, Any]] = [] mtgjson_card: Dict[str, Any] = {} # Let us know what card we're trying to parse -- good for debugging :) LOGGER.info("Parsing {0} from {1}".format(sf_card.get("name"), sf_card.get("set"))) # If flip-type, go to card_faces for alt attributes face_data: Dict[str, Any] = sf_card if "card_faces" in sf_card: mtgjson_card["names"] = sf_card["name"].split(" // ") # List[str] face_data = sf_card["card_faces"][sf_card_face] # Prevent duplicate UUIDs for split card halves # Remove the last character and replace with the id of the card face mtgjson_card["scryfallId"] = sf_card["id"] # Split cards and rotational cards have this field, flip cards do not. # Remove rotational cards via the additional check if "mana_cost" in sf_card and "//" in sf_card["mana_cost"]: mtgjson_card["colors"] = get_card_colors( sf_card["mana_cost"].split(" // ")[sf_card_face] ) mtgjson_card["faceConvertedManaCost"] = get_cmc( sf_card["mana_cost"].split("//")[sf_card_face].strip() ) elif sf_card["layout"] in ["split", "flip", "transform"]: # Handle non-normal cards, as they'll a face split mtgjson_card["faceConvertedManaCost"] = get_cmc( face_data.get("mana_cost", "0").strip() ) # Recursively parse the other cards within this card too # Only call recursive if it is the first time we see this card object if sf_card_face == 0: for i in range(1, len(sf_card["card_faces"])): LOGGER.info( "Parsing additional card {0} face {1}".format( sf_card.get("name"), i ) ) mtgjson_cards += build_mtgjson_card(sf_card, i) # Characteristics that can are not shared to both sides of flip-type cards if face_data.get("mana_cost"): mtgjson_card["manaCost"] = face_data.get("mana_cost") if "colors" not in mtgjson_card: if "colors" in face_data: mtgjson_card["colors"] = face_data.get("colors") else: mtgjson_card["colors"] = sf_card.get("colors") mtgjson_card["name"] = face_data.get("name") mtgjson_card["type"] = face_data.get("type_line") mtgjson_card["text"] = face_data.get("oracle_text") mtgjson_card["power"] = face_data.get("power") mtgjson_card["toughness"] = face_data.get("toughness") mtgjson_card["loyalty"] = face_data.get("loyalty") mtgjson_card["watermark"] = face_data.get("watermark") if "flavor_text" in face_data: mtgjson_card["flavorText"] = face_data.get("flavor_text") else: mtgjson_card["flavorText"] = sf_card.get("flavor_text") if "color_indicator" in face_data: mtgjson_card["colorIndicator"] = face_data.get("color_indicator") # List[str] elif "color_indicator" in sf_card: mtgjson_card["colorIndicator"] = sf_card.get("color_indicator") # List[str] try: mtgjson_card["multiverseId"] = sf_card["multiverse_ids"][sf_card_face] # int except IndexError: try: mtgjson_card["multiverseId"] = sf_card["multiverse_ids"][0] # int except IndexError: mtgjson_card["multiverseId"] = None # int # Characteristics that are shared to all sides of flip-type cards, that we don't have to modify mtgjson_card["artist"] = sf_card.get("artist") # str mtgjson_card["borderColor"] = sf_card.get("border_color") mtgjson_card["colorIdentity"] = sf_card.get("color_identity") # List[str] if "convertedManaCost" not in mtgjson_card: mtgjson_card["convertedManaCost"] = sf_card.get("cmc") # float mtgjson_card["frameVersion"] = sf_card.get("frame") mtgjson_card["hasFoil"] = sf_card.get("foil") mtgjson_card["hasNonFoil"] = sf_card.get("nonfoil") mtgjson_card["isOnlineOnly"] = sf_card.get("digital") mtgjson_card["isOversized"] = sf_card.get("oversized") mtgjson_card["layout"] = sf_card.get("layout") mtgjson_card["number"] = sf_card.get("collector_number") mtgjson_card["isReserved"] = sf_card.get("reserved") mtgjson_card["frameEffect"] = sf_card.get("frame_effect") mtgjson_card["tcgplayerProductId"] = sf_card.get("tcgplayer_id") # Vanguard fields mtgjson_card["life"] = sf_card.get("life_modifier") mtgjson_card["hand"] = sf_card.get("hand_modifier") # Add a "side" entry for split cards # Will only work for two faced cards (not meld, as they don't need this) if "names" in mtgjson_card and len(mtgjson_card["names"]) == 2: # chr(97) = 'a', chr(98) = 'b', ... mtgjson_card["side"] = chr( mtgjson_card["names"].index(mtgjson_card["name"]) + 97 ) if "scryfallId" not in mtgjson_card: mtgjson_card["scryfallId"] = sf_card.get("id") # Characteristics that we have to format ourselves from provided data mtgjson_card["isTimeshifted"] = (sf_card.get("frame") == "future") or ( sf_card.get("set") == "tsb" ) mtgjson_card["rarity"] = sf_card.get("rarity") # Characteristics that we need custom functions to parse print_search_url: str = sf_card["prints_search_uri"].replace("%22", "") mtgjson_card["legalities"] = scryfall.parse_legalities( sf_card["legalities"] ) # Dict[str, str] mtgjson_card["rulings"] = sorted( scryfall.parse_rulings(sf_card["rulings_uri"]), key=lambda ruling: ruling["date"], ) mtgjson_card["printings"] = sorted( scryfall.parse_printings(print_search_url) ) # List[str] card_types: Tuple[List[str], List[str], List[str]] = scryfall.parse_card_types( mtgjson_card["type"] ) mtgjson_card["supertypes"] = card_types[0] # List[str] mtgjson_card["types"] = card_types[1] # List[str] mtgjson_card["subtypes"] = card_types[2] # List[str] # Handle meld and all parts tokens issues # Will re-address naming if a split card already if "all_parts" in sf_card: meld_holder = [] mtgjson_card["names"] = [] for a_part in sf_card["all_parts"]: if a_part["component"] != "token": if "//" in a_part.get("name"): mtgjson_card["names"] = a_part.get("name").split(" // ") break # This is a meld only-fix, so we ignore tokens/combo pieces if "meld" in a_part["component"]: meld_holder.append(a_part["component"]) mtgjson_card["names"].append(a_part.get("name")) # If the only entry is the original card, empty the names array if ( len(mtgjson_card["names"]) == 1 and mtgjson_card["name"] in mtgjson_card["names"] ): del mtgjson_card["names"] # Meld cards should be CardA, Meld, CardB. This fixes that via swap # meld_holder if meld_holder and meld_holder[1] != "meld_result": mtgjson_card["names"][1], mtgjson_card["names"][2] = ( mtgjson_card["names"][2], mtgjson_card["names"][1], ) # Since we built meld cards later, we will add the "side" attribute now if len(mtgjson_card.get("names", [])) == 3: # MELD if mtgjson_card["name"] == mtgjson_card["names"][0]: mtgjson_card["side"] = "a" elif mtgjson_card["name"] == mtgjson_card["names"][2]: mtgjson_card["side"] = "b" else: mtgjson_card["side"] = "c" # Characteristics that we cannot get from Scryfall # Characteristics we have to do further API calls for mtgjson_card["foreignData"] = scryfall.parse_foreign( print_search_url, mtgjson_card["name"], mtgjson_card["number"], sf_card["set"] ) if mtgjson_card["multiverseId"] is not None: gatherer_cards = gatherer.get_cards(mtgjson_card["multiverseId"]) try: gatherer_card = gatherer_cards[sf_card_face] mtgjson_card["originalType"] = gatherer_card.original_types mtgjson_card["originalText"] = gatherer_card.original_text except IndexError: LOGGER.warning( "Unable to parse originals for {}".format(mtgjson_card["name"]) ) mtgjson_cards.append(mtgjson_card) return mtgjson_cards
def build_mtgjson_card(sf_card: Dict[str, Any], sf_card_face: int = 0) -> List[MTGJSONCard]: """ Build a mtgjson card (and all sub pieces of that card) :param sf_card: Card to build :param sf_card_face: Which part of the card (defaults to 0) :return: List of card(s) build (usually 1) """ mtgjson_cards: List[MTGJSONCard] = [] single_card = MTGJSONCard(sf_card["set"]) # Let us know what card we're trying to parse -- good for debugging :) LOGGER.info("Parsing {0} from {1}".format(sf_card.get("name"), sf_card.get("set"))) # If flip-type, go to card_faces for alt attributes face_data: Dict[str, Any] = sf_card if "card_faces" in sf_card: single_card.set_all({ "names": sf_card["name"].split(" // "), "scryfallId": sf_card["id"], "scryfallOracleId": sf_card["oracle_id"], "scryfallIllustrationId": sf_card.get("illustration_id"), }) face_data = sf_card["card_faces"][sf_card_face] # Split cards and rotational cards have this field, flip cards do not. # Remove rotational cards via the additional check if "mana_cost" in sf_card and "//" in sf_card["mana_cost"]: single_card.set( "colors", get_card_colors( sf_card["mana_cost"].split(" // ")[sf_card_face]), ) single_card.set( "faceConvertedManaCost", get_cmc( sf_card["mana_cost"].split("//")[sf_card_face].strip()), ) elif sf_card["layout"] in ["split", "transform", "aftermath"]: # Handle non-normal cards, as they'll a face split single_card.set( "faceConvertedManaCost", get_cmc(face_data.get("mana_cost", "0").strip()), ) # Watermark is only attributed on the front side, so we'll account for it single_card.set( "watermark", sf_card["card_faces"][0].get("watermark", None), single_card.clean_up_watermark, ) if sf_card["card_faces"][-1]["oracle_text"].startswith("Aftermath"): single_card.set("layout", "aftermath") single_card.set("artist", sf_card["card_faces"][sf_card_face].get("artist", "")) # Recursively parse the other cards within this card too # Only call recursive if it is the first time we see this card object if sf_card_face == 0: for i in range(1, len(sf_card["card_faces"])): LOGGER.info("Parsing additional card {0} face {1}".format( sf_card.get("name"), i)) mtgjson_cards += build_mtgjson_card(sf_card, i) else: single_card.set_all({ "scryfallId": sf_card.get("id"), "scryfallOracleId": sf_card["oracle_id"], "scryfallIllustrationId": sf_card.get("illustration_id"), }) # Characteristics that can are not shared to both sides of flip-type cards if face_data.get("mana_cost"): single_card.set("manaCost", face_data.get("mana_cost")) if "colors" not in single_card.keys(): if "colors" in face_data: single_card.set("colors", face_data.get("colors")) else: single_card.set("colors", sf_card.get("colors")) single_card.set_all({ "borderColor": sf_card.get("border_color"), "colorIdentity": sf_card.get("color_identity"), "convertedManaCost": sf_card.get("cmc"), "frameEffect": sf_card.get("frame_effect"), "frameVersion": sf_card.get("frame"), "hand": sf_card.get("hand_modifier"), "hasFoil": sf_card.get("foil"), "hasNonFoil": sf_card.get("nonfoil"), "isFullArt": sf_card.get("full_art"), "isOnlineOnly": sf_card.get("digital"), "isOversized": sf_card.get("oversized"), "isPromo": sf_card.get("promo"), "isReprint": sf_card.get("reprint"), "isReserved": sf_card.get("reserved"), "isStorySpotlight": sf_card.get("story_spotlight"), "isTextless": sf_card.get("textless"), "life": sf_card.get("life_modifier"), "loyalty": face_data.get("loyalty"), "name": face_data.get("name"), "number": sf_card.get("collector_number"), "power": face_data.get("power"), "tcgplayerProductId": sf_card.get("tcgplayer_id"), "text": face_data.get("oracle_text"), "toughness": face_data.get("toughness"), "type": face_data.get("type_line"), }) # Set MKM IDs if it exists if MKM_API.get(None): mkm_card_found = False for key, mkm_obj in MKM_SET_CARDS.get().items(): if single_card.get("name").lower() not in key: continue if "number" not in mkm_obj.keys() or ( mkm_obj.get("number") in single_card.get("number")): single_card.set_all({ "mcmId": mkm_obj["idProduct"], "mcmMetaId": mkm_obj["idMetaproduct"], }) single_card.set_mkm_url(mkm_obj["website"]) mkm_card_found = True break if not mkm_card_found: LOGGER.warning("Unable to find MKM information for #{} {}".format( single_card.get("number"), single_card.get("name"))) if "artist" not in single_card.keys(): single_card.set("artist", sf_card.get("artist")) if "layout" not in single_card.keys(): single_card.set("layout", sf_card.get("layout")) if "watermark" not in single_card.keys(): single_card.set( "watermark", face_data.get("watermark", None), single_card.clean_up_watermark, ) # "isPaper", "isMtgo", "isArena" for game_mode in sf_card.get("games", []): single_card.set("is{}".format(game_mode.capitalize()), True) if "flavor_text" in face_data: single_card.set("flavorText", face_data.get("flavor_text")) else: single_card.set("flavorText", sf_card.get("flavor_text")) if "color_indicator" in face_data: single_card.set("colorIndicator", face_data.get("color_indicator")) elif "color_indicator" in sf_card: single_card.set("colorIndicator", sf_card.get("color_indicator")) try: single_card.set("multiverseId", sf_card["multiverse_ids"][sf_card_face]) except IndexError: try: single_card.set("multiverseId", sf_card["multiverse_ids"][0]) except IndexError: single_card.set("multiverseId", None) # Add a "side" entry for split cards # Will only work for two faced cards (not meld, as they don't need this) if "names" in single_card.keys() and single_card.names_count(2): # chr(97) = 'a', chr(98) = 'b', ... single_card.set( "side", chr(single_card.get("names").index(single_card.get("name")) + 97)) # Characteristics that we have to format ourselves from provided data single_card.set( "isTimeshifted", (sf_card.get("frame") == "future") or (sf_card.get("set") == "tsb"), ) single_card.set("rarity", sf_card.get("rarity")) # Characteristics that we need custom functions to parse print_search_url: str = sf_card["prints_search_uri"].replace("%22", "") single_card.set("legalities", scryfall.parse_legalities(sf_card["legalities"])) single_card.set( "rulings", sorted( scryfall.parse_rulings(sf_card["rulings_uri"]), key=lambda ruling: ruling["date"], ), ) single_card.set("printings", sorted(scryfall.parse_printings(print_search_url))) card_types: Tuple[List[str], List[str], List[str]] = scryfall.parse_card_types( single_card.get("type")) single_card.set("supertypes", card_types[0]) single_card.set("types", card_types[1]) single_card.set("subtypes", card_types[2]) # Handle meld and all parts tokens issues # Will re-address naming if a split card already if "all_parts" in sf_card: meld_holder = [] single_card.set("names", []) for a_part in sf_card["all_parts"]: if a_part["component"] != "token": if "//" in a_part.get("name"): single_card.set("names", a_part.get("name").split(" // ")) break # This is a meld only-fix, so we ignore tokens/combo pieces if "meld" in a_part["component"]: meld_holder.append(a_part["component"]) single_card.append("names", a_part.get("name")) # If the only entry is the original card, empty the names array if single_card.names_count(1) and single_card.get( "name") in single_card.get("names"): single_card.remove("names") # Meld cards should be CardA, Meld, CardB. This fixes that via swap # meld_holder if meld_holder and meld_holder[1] != "meld_result": single_card.get("names")[1], single_card.get("names")[2] = ( single_card.get("names")[2], single_card.get("names")[1], ) # Since we built meld cards later, we will add the "side" attribute now if single_card.names_count(3): # MELD if single_card.get("name") == single_card.get("names")[0]: single_card.set("side", "a") elif single_card.get("name") == single_card.get("names")[2]: single_card.set("side", "b") else: single_card.set("side", "c") # Characteristics that we cannot get from Scryfall # Characteristics we have to do further API calls for single_card.set( "foreignData", scryfall.parse_foreign( print_search_url, single_card.get("name"), single_card.get("number"), sf_card["set"], ), ) if single_card.get("multiverseId") is not None: gatherer_cards = gatherer.get_cards(single_card.get("multiverseId"), single_card.set_code) try: gatherer_card = gatherer_cards[sf_card_face] single_card.set("originalType", gatherer_card.original_types) single_card.set("originalText", gatherer_card.original_text) except IndexError: LOGGER.warning("Unable to parse originals for {}".format( single_card.get("name"))) mtgjson_cards.append(single_card) return mtgjson_cards
def build_mtgjson_tokens(sf_tokens: List[Dict[str, Any]], sf_card_face: int = 0) -> List[MTGJSONCard]: """ Convert Scryfall tokens to MTGJSON tokens :param sf_tokens: All tokens in a set :param sf_card_face: Faces of the token index :return: List of MTGJSON tokens """ token_cards: List[MTGJSONCard] = [] for sf_token in sf_tokens: token_card = MTGJSONCard(sf_token["set"]) if "card_faces" in sf_token: token_card.set("names", sf_token["name"].split(" // ")) face_data = sf_token["card_faces"][sf_card_face] # Prevent duplicate UUIDs for split card halves # Remove the last character and replace with the id of the card face token_card.set("scryfallId", sf_token["id"]) token_card.set("scryfallOracleId", sf_token["oracle_id"]) token_card.set("scryfallIllustrationId", face_data.get("illustration_id")) # Recursively parse the other cards within this card too # Only call recursive if it is the first time we see this card object if sf_card_face == 0: for i in range(1, len(sf_token["card_faces"])): LOGGER.info( f"Parsing additional card {sf_token.get('name')} face {i}" ) token_cards += build_mtgjson_tokens([sf_token], i) if "id" not in sf_token.keys(): LOGGER.info( f"Scryfall_ID not found in {sf_token.get('name')}. Discarding {sf_token}" ) continue token_card.set_all({ "name": face_data.get("name"), "type": face_data.get("type_line", "Card"), "text": face_data.get("oracle_text"), "power": face_data.get("power"), "colors": face_data.get("colors"), "colorIdentity": sf_token.get("color_identity"), "toughness": face_data.get("toughness"), "loyalty": face_data.get("loyalty"), "watermark": sf_token.get("watermark"), "layout": "double_faced_token", "side": chr(97 + sf_card_face), "borderColor": face_data.get("border_color"), "artist": face_data.get("artist"), "isOnlineOnly": sf_token.get("digital"), "number": sf_token.get("collector_number"), }) else: token_card.set_all({ "name": sf_token.get("name"), "type": sf_token.get("type_line", "Card"), "text": sf_token.get("oracle_text"), "power": sf_token.get("power"), "colors": sf_token.get("colors"), "colorIdentity": sf_token.get("color_identity"), "toughness": sf_token.get("toughness"), "loyalty": sf_token.get("loyalty"), "watermark": sf_token.get("watermark"), "scryfallId": sf_token["id"], "scryfallOracleId": sf_token.get("oracle_id"), "scryfallIllustrationId": sf_token.get("illustration_id"), "borderColor": sf_token.get("border_color"), "artist": sf_token.get("artist"), "isOnlineOnly": sf_token.get("digital"), "number": sf_token.get("collector_number"), }) if sf_token.get("layout") == "token": token_card.set("layout", "normal") else: token_card.set("layout", sf_token.get("layout")) reverse_related: List[str] = [] if "all_parts" in sf_token: for a_part in sf_token["all_parts"]: if a_part.get("name") != token_card.get("name"): reverse_related.append(a_part.get("name")) token_card.set("reverseRelated", reverse_related) # Add super/sub/types card_types: Tuple[List[str], List[str], List[str]] = scryfall.parse_card_types( token_card.get("type")) token_card.set("supertypes", card_types[0]) token_card.set("types", card_types[1]) token_card.set("subtypes", card_types[2]) # Add brackets around Planeswalker loyalty abilities if "Planeswalker" in token_card.get("types"): token_card.set("text", token_card.get("text"), token_card.cleanup_planeswalker_costs) token_card.set("uuid", token_card.get_uuid()) LOGGER.info( f"Parsed {token_card.get('name')} from {sf_token.get('set')}") token_cards.append(token_card) return token_cards
def build_mtgjson_card( # pylint: disable=too-many-branches sf_card: Dict[str, Any], sf_card_face: int = 0) -> List[Dict[str, Any]]: """ Build a mtgjson card (and all sub pieces of that card) :param sf_card: Card to build :param sf_card_face: Which part of the card (defaults to 0) :return: List of card(s) build (usually 1) """ mtgjson_cards: List[Dict[str, Any]] = [] mtgjson_card: Dict[str, Any] = {} # Let us know what card we're trying to parse -- good for debugging :) LOGGER.info("Parsing {0} from {1}".format(sf_card.get("name"), sf_card.get("set"))) # If flip-type, go to card_faces for alt attributes face_data: Dict[str, Any] = sf_card if "card_faces" in sf_card: mtgjson_card["names"] = sf_card["name"].split(" // ") # List[str] face_data = sf_card["card_faces"][sf_card_face] # Prevent duplicate UUIDs for split card halves # Remove the last character and replace with the id of the card face mtgjson_card["uuid"] = sf_card["id"][:-1] + str(sf_card_face) # Split cards and rotational cards have this field, flip cards do not. # Remove rotational cards via the additional check if "mana_cost" in sf_card and "//" in sf_card["mana_cost"]: mtgjson_card["colors"] = get_card_colors( sf_card["mana_cost"].split(" // ")[sf_card_face]) mtgjson_card["convertedManaCost"] = get_cmc( sf_card["mana_cost"].split(" // ")[sf_card_face]) # Recursively parse the other cards within this card too # Only call recursive if it is the first time we see this card object if sf_card_face == 0: for i in range(1, len(sf_card["card_faces"])): LOGGER.info("Parsing additional card {0} face {1}".format( sf_card.get("name"), i)) mtgjson_cards += build_mtgjson_card(sf_card, i) # Characteristics that can are not shared to both sides of flip-type cards if face_data.get("mana_cost"): mtgjson_card["manaCost"] = face_data.get("mana_cost") # str if "colors" not in mtgjson_card: mtgjson_card["colors"] = face_data.get("colors") # List[str] mtgjson_card["name"] = face_data.get("name") # str mtgjson_card["type"] = face_data.get("type_line") # str mtgjson_card["text"] = face_data.get("oracle_text") # str mtgjson_card["power"] = face_data.get("power") # str mtgjson_card["toughness"] = face_data.get("toughness") # str mtgjson_card["loyalty"] = face_data.get("loyalty") # str mtgjson_card["watermark"] = face_data.get("watermark") # str if "color_indicator" in face_data: mtgjson_card["colorIndicator"] = face_data.get( "color_indicator") # List[str] elif "color_indicator" in sf_card: mtgjson_card["colorIndicator"] = sf_card.get( "color_indicator") # List[str] try: mtgjson_card["multiverseId"] = sf_card["multiverse_ids"][ sf_card_face] # int except IndexError: try: mtgjson_card["multiverseId"] = sf_card["multiverse_ids"][0] # int except IndexError: mtgjson_card["multiverseId"] = None # int # Characteristics that are shared to all sides of flip-type cards, that we don't have to modify mtgjson_card["artist"] = sf_card.get("artist") # str mtgjson_card["borderColor"] = sf_card.get("border_color") mtgjson_card["colorIdentity"] = sf_card.get("color_identity") # List[str] if "convertedManaCost" not in mtgjson_card: mtgjson_card["convertedManaCost"] = sf_card.get("cmc") # float mtgjson_card["flavorText"] = sf_card.get("flavor_text") # str mtgjson_card["frameVersion"] = sf_card.get("frame") # str mtgjson_card["hasFoil"] = sf_card.get("foil") # bool mtgjson_card["hasNonFoil"] = sf_card.get("nonfoil") # bool mtgjson_card["isOnlineOnly"] = sf_card.get("digital") # bool mtgjson_card["isOversized"] = sf_card.get("oversized") # bool mtgjson_card["layout"] = sf_card.get("layout") # str mtgjson_card["number"] = sf_card.get("collector_number") # str mtgjson_card["isReserved"] = sf_card.get("reserved") # bool if "uuid" not in mtgjson_card: mtgjson_card["uuid"] = sf_card.get("id") # str # Characteristics that we have to format ourselves from provided data mtgjson_card["timeshifted"] = sf_card.get("timeshifted") or sf_card.get( "futureshifted") # bool mtgjson_card["rarity"] = ( sf_card.get("rarity") if not mtgjson_card.get("timeshifted") else "Special") # str # Characteristics that we need custom functions to parse print_search_url: str = sf_card["prints_search_uri"].replace("%22", "") mtgjson_card["legalities"] = scryfall.parse_legalities( sf_card["legalities"]) # Dict[str, str] mtgjson_card["rulings"] = sorted( scryfall.parse_rulings(sf_card["rulings_uri"]), key=lambda ruling: ruling["date"], ) mtgjson_card["printings"] = sorted( scryfall.parse_printings(print_search_url)) # List[str] card_types: Tuple[List[str], List[str], List[str]] = scryfall.parse_card_types( mtgjson_card["type"]) mtgjson_card["supertypes"] = card_types[0] # List[str] mtgjson_card["types"] = card_types[1] # List[str] mtgjson_card["subtypes"] = card_types[2] # List[str] # Handle meld issues if "all_parts" in sf_card: mtgjson_card["names"] = [] for a_part in sf_card["all_parts"]: if "//" in a_part.get("name"): mtgjson_card["names"] = a_part.get("name").split(" // ") break mtgjson_card["names"].append(a_part.get("name")) # Characteristics that we cannot get from Scryfall # Characteristics we have to do further API calls for mtgjson_card["foreignData"] = scryfall.parse_foreign( print_search_url, mtgjson_card["name"], sf_card["set"]) if mtgjson_card["multiverseId"] is not None: gatherer_cards = gatherer.get_cards(mtgjson_card["multiverseId"]) try: gatherer_card = gatherer_cards[sf_card_face] mtgjson_card["originalType"] = gatherer_card.original_types mtgjson_card["originalText"] = gatherer_card.original_text except IndexError: LOGGER.warning("Unable to parse originals for {}".format( mtgjson_card["name"])) mtgjson_cards.append(mtgjson_card) return mtgjson_cards
def build_mtgjson_card( sf_card: Dict[str, Any], sf_card_face: int = 0 ) -> List[MTGJSONCard]: """ Build a mtgjson card (and all sub pieces of that card) :param sf_card: Card to build :param sf_card_face: Which part of the card (defaults to 0) :return: List of card(s) build (usually 1) """ mtgjson_cards: List[MTGJSONCard] = [] single_card = MTGJSONCard(sf_card["set"]) # Let us know what card we're trying to parse -- good for debugging :) LOGGER.info("Parsing {0} from {1}".format(sf_card.get("name"), sf_card.get("set"))) # If flip-type, go to card_faces for alt attributes face_data: Dict[str, Any] = sf_card if "card_faces" in sf_card: single_card.set_all( { "names": sf_card["name"].split(" // "), "scryfallId": sf_card["id"], "scryfallOracleId": sf_card["oracle_id"], "scryfallIllustrationId": sf_card.get("illustration_id"), } ) face_data = sf_card["card_faces"][sf_card_face] # Split cards and rotational cards have this field, flip cards do not. # Remove rotational cards via the additional check if "mana_cost" in sf_card and "//" in sf_card["mana_cost"]: single_card.set( "colors", get_card_colors(sf_card["mana_cost"].split(" // ")[sf_card_face]), ) single_card.set( "faceConvertedManaCost", get_cmc(sf_card["mana_cost"].split("//")[sf_card_face].strip()), ) elif sf_card["layout"] in ["split", "transform", "aftermath"]: # Handle non-normal cards, as they'll a face split single_card.set( "faceConvertedManaCost", get_cmc(face_data.get("mana_cost", "0").strip()), ) # Watermark is only attributed on the front side, so we'll account for it single_card.set( "watermark", sf_card["card_faces"][0].get("watermark", None), single_card.clean_up_watermark, ) if sf_card["card_faces"][-1]["oracle_text"].startswith("Aftermath"): single_card.set("layout", "aftermath") single_card.set("artist", sf_card["card_faces"][sf_card_face].get("artist", "")) # Recursively parse the other cards within this card too # Only call recursive if it is the first time we see this card object if sf_card_face == 0: for i in range(1, len(sf_card["card_faces"])): LOGGER.info( "Parsing additional card {0} face {1}".format( sf_card.get("name"), i ) ) mtgjson_cards += build_mtgjson_card(sf_card, i) else: single_card.set_all( { "scryfallId": sf_card.get("id"), "scryfallOracleId": sf_card["oracle_id"], "scryfallIllustrationId": sf_card.get("illustration_id"), } ) # Characteristics that can are not shared to both sides of flip-type cards if face_data.get("mana_cost"): single_card.set("manaCost", face_data.get("mana_cost")) if "colors" not in single_card.keys(): if "colors" in face_data: single_card.set("colors", face_data.get("colors")) else: single_card.set("colors", sf_card.get("colors")) single_card.set_all( { "name": face_data.get("name"), "type": face_data.get("type_line"), "text": face_data.get("oracle_text"), "power": face_data.get("power"), "toughness": face_data.get("toughness"), "loyalty": face_data.get("loyalty"), "borderColor": sf_card.get("border_color"), "colorIdentity": sf_card.get("color_identity"), "frameVersion": sf_card.get("frame"), "hasFoil": sf_card.get("foil"), "hasNonFoil": sf_card.get("nonfoil"), "isOnlineOnly": sf_card.get("digital"), "isOversized": sf_card.get("oversized"), "number": sf_card.get("collector_number"), "isReserved": sf_card.get("reserved"), "frameEffect": sf_card.get("frame_effect"), "tcgplayerProductId": sf_card.get("tcgplayer_id"), "life": sf_card.get("life_modifier"), "hand": sf_card.get("hand_modifier"), "convertedManaCost": sf_card.get("cmc"), } ) # Set MKM IDs if it exists if MKM_API.get(None): mkm_card_found = False for key, mkm_obj in MKM_SET_CARDS.get().items(): if single_card.get("name").lower() not in key: continue if "number" not in mkm_obj.keys() or ( mkm_obj.get("number") in single_card.get("number") ): single_card.set_all( { "mcmId": mkm_obj["idProduct"], "mcmMetaId": mkm_obj["idMetaproduct"], } ) single_card.set_mkm_url(mkm_obj["website"]) mkm_card_found = True break if not mkm_card_found: LOGGER.warning( "Unable to find MKM information for #{} {}".format( single_card.get("number"), single_card.get("name") ) ) if "artist" not in single_card.keys(): single_card.set("artist", sf_card.get("artist")) if "layout" not in single_card.keys(): single_card.set("layout", sf_card.get("layout")) if "watermark" not in single_card.keys(): single_card.set( "watermark", face_data.get("watermark", None), single_card.clean_up_watermark, ) if "flavor_text" in face_data: single_card.set("flavorText", face_data.get("flavor_text")) else: single_card.set("flavorText", sf_card.get("flavor_text")) if "color_indicator" in face_data: single_card.set("colorIndicator", face_data.get("color_indicator")) elif "color_indicator" in sf_card: single_card.set("colorIndicator", sf_card.get("color_indicator")) try: single_card.set("multiverseId", sf_card["multiverse_ids"][sf_card_face]) except IndexError: try: single_card.set("multiverseId", sf_card["multiverse_ids"][0]) except IndexError: single_card.set("multiverseId", None) # Add a "side" entry for split cards # Will only work for two faced cards (not meld, as they don't need this) if "names" in single_card.keys() and single_card.names_count(2): # chr(97) = 'a', chr(98) = 'b', ... single_card.set( "side", chr(single_card.get("names").index(single_card.get("name")) + 97) ) # Characteristics that we have to format ourselves from provided data single_card.set( "isTimeshifted", (sf_card.get("frame") == "future") or (sf_card.get("set") == "tsb"), ) single_card.set("rarity", sf_card.get("rarity")) # Characteristics that we need custom functions to parse print_search_url: str = sf_card["prints_search_uri"].replace("%22", "") single_card.set("legalities", scryfall.parse_legalities(sf_card["legalities"])) single_card.set( "rulings", sorted( scryfall.parse_rulings(sf_card["rulings_uri"]), key=lambda ruling: ruling["date"], ), ) single_card.set("printings", sorted(scryfall.parse_printings(print_search_url))) card_types: Tuple[List[str], List[str], List[str]] = scryfall.parse_card_types( single_card.get("type") ) single_card.set("supertypes", card_types[0]) single_card.set("types", card_types[1]) single_card.set("subtypes", card_types[2]) # Handle meld and all parts tokens issues # Will re-address naming if a split card already if "all_parts" in sf_card: meld_holder = [] single_card.set("names", []) for a_part in sf_card["all_parts"]: if a_part["component"] != "token": if "//" in a_part.get("name"): single_card.set("names", a_part.get("name").split(" // ")) break # This is a meld only-fix, so we ignore tokens/combo pieces if "meld" in a_part["component"]: meld_holder.append(a_part["component"]) single_card.append("names", a_part.get("name")) # If the only entry is the original card, empty the names array if single_card.names_count(1) and single_card.get("name") in single_card.get( "names" ): single_card.remove("names") # Meld cards should be CardA, Meld, CardB. This fixes that via swap # meld_holder if meld_holder and meld_holder[1] != "meld_result": single_card.get("names")[1], single_card.get("names")[2] = ( single_card.get("names")[2], single_card.get("names")[1], ) # Since we built meld cards later, we will add the "side" attribute now if single_card.names_count(3): # MELD if single_card.get("name") == single_card.get("names")[0]: single_card.set("side", "a") elif single_card.get("name") == single_card.get("names")[2]: single_card.set("side", "b") else: single_card.set("side", "c") # Characteristics that we cannot get from Scryfall # Characteristics we have to do further API calls for single_card.set( "foreignData", scryfall.parse_foreign( print_search_url, single_card.get("name"), single_card.get("number"), sf_card["set"], ), ) if single_card.get("multiverseId") is not None: gatherer_cards = gatherer.get_cards(single_card.get("multiverseId")) try: gatherer_card = gatherer_cards[sf_card_face] single_card.set("originalType", gatherer_card.original_types) single_card.set("originalText", gatherer_card.original_text) except IndexError: LOGGER.warning( "Unable to parse originals for {}".format(single_card.get("name")) ) mtgjson_cards.append(single_card) return mtgjson_cards