Last active
June 19, 2022 02:58
-
-
Save s1113950/4775caa90089ebcb01d134732235d765 to your computer and use it in GitHub Desktop.
Query scryfall + dump card names out for easy import into moxfield
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# little script to search for cards in scryfall for commander decks and dump them out in text form | |
# see https://scryfall.com/docs/api/cards/search | |
import argparse | |
import requests | |
import sys | |
import time | |
# Python <= 3.9 hack, need NoneType to parse None values still | |
if sys.version_info.major == 3 and sys.version_info.minor <= 9: | |
NoneType = type(None) | |
def get_args(): | |
# to automatically parse optional args, make new ones in form: | |
# --optional-{nameOfKeyFromDataReturnedFromAPI} | |
# ex: "--optiona-mana-cost" returns data from the "mana_cost" attr in scryfall's card API | |
parser = argparse.ArgumentParser( | |
description="rip data from scryfall and dump out into test form " | |
"for easy import into moxfield") | |
parser.add_argument("--query", "-q", help="search query as single string", | |
type=str, required=True) | |
parser.add_argument("--optional-mana-cost", "-m", default=False, | |
help="include mana costs of cards", action="store_true") | |
parser.add_argument("--optional-oracle-text", "-t", default=False, | |
help="include oracle text of cards", action="store_true") | |
parser.add_argument("--optional-power", "-p", default=False, | |
help="include power of cards", action="store_true") | |
parser.add_argument("--optional-toughness", "-u", default=False, | |
help="include toughness of cards", action="store_true") | |
parser.add_argument("--optional-type-line", "-l", default=False, | |
help="include types of cards", action="store_true") | |
parser.add_argument("--optional-prices", "-c", default=False, | |
help="include cost of cards", action="store_true") | |
parser.add_argument("--order", "-o", help="scryfall api for sort order", | |
type=str, default="name") | |
parser.add_argument("--output-as-file", "-f", help="path to output file " | |
"for storing results, defaults to stdout", | |
type=str, default="") | |
return parser.parse_args() | |
def sanitize_query(query): | |
# only return cards legal in commander | |
query += " legal:commander" | |
return query | |
def get_results(args): | |
print(f"Running scryfall search on {args.query}") | |
cards = [] | |
page = 1 | |
has_more = True | |
while has_more: | |
params = { | |
"q": args.query, | |
"order": args.order, | |
"format": "json", | |
"page": page, | |
} | |
req = requests.get("https://api.scryfall.com/cards/search", params=params) | |
if req.status_code != 200: | |
print("There was a problem with the api call." | |
f"\nError code {req.status_code}\nError msg: {req.text}") | |
exit(1) | |
resp = req.json() | |
for card in resp["data"]: | |
cards.append(get_card_details(card, args)) | |
has_more = resp["has_more"] | |
if has_more: | |
page += 1 | |
# adding sleep as per https://scryfall.com/docs/api | |
time.sleep(0.1) | |
return cards | |
# mega overengineered this because I missed python | |
def get_card_details(card, args): | |
# TODO: could make this a Card class | |
details = f"{card['name']}\n" | |
return get_optional_card_details_dynamically(details, card, args) | |
def get_optional_card_details_dynamically(details, card, args): | |
for attr, val in args.__dict__.items(): | |
if attr.startswith("optional") and getattr(args, attr): | |
arg_name = attr[attr.find("optional") + 9:] | |
# data could be hiding in either top-level card details or card_faces | |
found_data = False | |
for face in card.get("card_faces", []): | |
# not all cards have all attributes, enchantments won't have power for example | |
arg_data_val = face.get(arg_name, "") | |
data = parse_optional_card_details(details, arg_name, arg_data_val, face_name=face["name"]) | |
if data: | |
details += data | |
found_data = True | |
# fallback to checking top-level card details for data | |
if not found_data: | |
arg_data_val = card.get(arg_name, "") | |
details += parse_optional_card_details(details, arg_name, arg_data_val, face_name="") | |
return details | |
def parse_optional_card_details(details, arg_name, arg_data_val, face_name): | |
arg_name = sanitize_arg_name(arg_name) | |
data = sanitize_arg_data(arg_data_val) | |
if data == "N/A": | |
return "" | |
if face_name: | |
return f"{arg_name} for {face_name}: " \ | |
f"{data}\n" | |
return f"{arg_name}: {data}\n" | |
def sanitize_arg_name(name): | |
return name.replace("_", " ").capitalize() | |
def sanitize_arg_data(data): | |
# no default parser here, if new val type add new parser | |
parsers = { | |
str: parse_string, | |
dict: parse_dict, | |
NoneType: parse_none, | |
} | |
return parsers[type(data)](data) | |
def parse_string(data): | |
return data if data != "" else "N/A" | |
def parse_none(data): | |
return "N/A" | |
def parse_dict(data): | |
# could json.dumps here but want to print data in nice string format instead of json | |
output = "" | |
for key, val in data.items(): | |
output += f"\n{sanitize_arg_name(key)} -> {sanitize_arg_data(val)}" | |
return output | |
def dump_output(cards, args): | |
print(f"Found {len(cards)} total matches!") | |
if args.output_as_file != "": | |
with open(args.output_as_file, "w") as f: | |
for card in cards: | |
f.write(card + '\n') | |
else: | |
for card in cards: | |
print(card) | |
def main(): | |
args = get_args() | |
args.query = sanitize_query(args.query) | |
cards = get_results(args) | |
dump_output(cards, args) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment