Skip to content

Instantly share code, notes, and snippets.

@s1113950
Last active October 24, 2025 09:10
Show Gist options
  • Select an option

  • Save s1113950/4775caa90089ebcb01d134732235d765 to your computer and use it in GitHub Desktop.

Select an option

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()
@8ullyMaguire
Copy link

8ullyMaguire commented Oct 20, 2025

✅ This will create a fully Moxfield-compatible CSV with all cards from a Scryfall search.

There's more related code at: https://git.disroot.org/hirrolot19/mtg-legality-checker

import requests
import csv
import time

QUERY = "f:standard f:penny usd<=1"
BASE_URL = "https://api.scryfall.com/cards/search"
PARAMS = {
    "q": QUERY,
    "unique": "cards",
    "format": "json"
}

OUTPUT_FILE = "moxfield_import.csv"

FIELDNAMES = [
    "Count",
    "Tradelist Count",
    "Name",
    "Edition",
    "Condition",
    "Language",
    "Foil",
    "Tags",
    "Last Modified",
    "Collector Number",
    "Alter",
    "Proxy",
    "Purchase Price"
]

def fetch_all_cards():
    url = BASE_URL
    params = PARAMS.copy()
    while True:
        resp = requests.get(url, params=params)
        resp.raise_for_status()
        data = resp.json()
        for card in data.get("data", []):
            yield card
        if not data.get("has_more"):
            break
        url = data["next_page"]
        params = None
        time.sleep(0.2)

def write_cards_to_csv(filename):
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
        writer.writeheader()
        for card in fetch_all_cards():
            row = {
                "Count": 1,
                "Tradelist Count": "",
                "Name": card.get("name"),
                "Edition": card.get("set"),
                "Condition": "",
                "Language": card.get("lang"),
                "Foil": "Yes" if card.get("foil") else "No",
                "Tags": "",
                "Last Modified": "",
                "Collector Number": card.get("collector_number"),
                "Alter": "",
                "Proxy": "",
                "Purchase Price": ""
            }
            writer.writerow(row)

if __name__ == "__main__":
    write_cards_to_csv(OUTPUT_FILE)
    print(f"Saved all cards to {OUTPUT_FILE}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment