Skip to content

Instantly share code, notes, and snippets.

@s1113950
Last active June 19, 2022 02:58
Show Gist options
  • Save s1113950/4775caa90089ebcb01d134732235d765 to your computer and use it in GitHub Desktop.
Save s1113950/4775caa90089ebcb01d134732235d765 to your computer and use it in GitHub Desktop.
Query scryfall + dump card names out for easy import into moxfield
# 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