Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ Clone and install with `pipx install scry`

## use

- Draw a random card and add it to your database with `scry random`
- Get a list of cards based on a scryfall [search query](https://scryfall.com/docs/syntax) and add them to your database:
- `scry list "set:blb"` returns unique cards from the Bloomburrow set and shows stats for that list.
- Request a list of set releases with: `scry setcodes`
- Return stats for your entire database: `scry stats`
- (Optional:) Clear your database: `scry clear`
- Request a reference list of set releases with: `scry setlist`
- Get stats for a specific set:
- `scry set BLB` returns all cards from the _Bloomburrow_ set
- `scry set latest` finds the most recent release.
- Get stats for cards based on a scryfall [search query](https://scryfall.com/docs/syntax):
- `scry search id:orzhov type:land legal:modern` returns all unique Orzhov Land cards that are legal in Modern format, and shows stats for that list.
- Get help with `scry --help`

### local database

Scry creates a local sqlite database and adds your queried cards to it. This means you can build a larger collection of cards by executing multiple searches, and then view stats for the entire database with `scry stats`

To clear your database (for instance, to start a fresh collection to view stats on), run `scry clear` and confirm at the prompt.

## about

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ readme = "README.md"

# defines the command and script that will be used to run
[project.scripts]
scry = "scry.cli:main"
scry = "scry.main:main"

[project.urls]
repository = "https://github.com/thrly/scry"
Expand Down
258 changes: 189 additions & 69 deletions src/scry/cli.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,192 @@
# add cards to db using scryfall
# query and return stats


import argparse
from datetime import datetime
from sys import argv

from scry.request import find_current_release
from . import (
create_table,
get_random_card,
insert_cards,
get_card_list,
db_stats,
get_total_cards,
clear_database,
set_codes,
db_connect,
)


def main():
connection = db_connect()
try:
create_table(connection)

if len(argv) > 1:
# TODO: swap argv for argparse
req_type = argv[1]
# use argument inputs from CLI: random (single card) or list (multiple cards)
if req_type == "random":
# optional query argument for random card, otherwise no constraint
if 1 < len(argv) > 2:
query = argv[1]
else:
query = ""
# get a single random card, based on search parameters
card = get_random_card(query) or []
insert_cards(card, get_timestamp(), connection)
print(get_total_cards(connection), "cards currently in database.")

elif req_type == "list":
if len(argv) > 2:
search_param = argv[2]
card_list = get_card_list(search_param) or []
stamp = get_timestamp()
insert_cards(card_list, stamp, connection)
print(get_total_cards(connection), "cards currently in database.")
print(
f"================================================\nSTATS for '{search_param}':"
)
print_stats(connection, stamp)

else:
print("Lists need a query parameter (i.e. 'color:black set:BLB')")

elif req_type == "setcodes":
for set_code in set_codes():
if set_code[3] == "expansion" or set_code[3] == "commander":
# extract year from YYYY-MM-DD
date = datetime.fromisoformat(set_code[2])
print(
f"{set_code[0]} : {set_code[1]}\t{set_code[4]} cards\t{date.year}"
)
elif req_type == "clear":
clear_database()

elif req_type == "stats":
print(
"================================================\nSTATS for ALL cards in database:"
)
print_stats(connection)
def build_arg_parser() -> argparse.ArgumentParser:
# setup parser and sub command parsers
parser = argparse.ArgumentParser(
description="🃏 Stats for card sets from Scryfall.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"""
Examples:
scry setlist
scry set BLB
scry set latest
scry search t:creature c:green legal:modern
"""
),
)
subparsers = parser.add_subparsers(
# dest="subcommand",
title="subcommands",
description="Basic scrying functions. Some require additional parameters.",
required=True,
)

# Define each subcommand:
# RANDOM ------------------------
random_parser = subparsers.add_parser(
"random",
help="Draw random cards from Scryfall",
)
random_parser.add_argument(
"-n",
"--number",
type=int,
default=1,
help="Number of random cards to draw",
)
random_parser.set_defaults(func=handle_random)

# SEARCH ------------------------
# TODO: bypass this completely and just run with `scry <search_query>`
list_parser = subparsers.add_parser(
"search", help="Returns a list of cards matching search parameters"
)
list_parser.add_argument(
"search_query",
nargs="+",
help="Arguments as Scryfall-syntax search query (e.g. 't:creature c:green')",
# TODO: is it possible to set a default value if no query arg is given? 't:land' etc.
)
list_parser.set_defaults(func=handle_search)

# SET ------------------------
set_parser = subparsers.add_parser("set")
set_parser.add_argument(
"set_query",
help="Specify setcode (run `scry setlist` for reference) or 'latest'",
)
set_parser.set_defaults(func=handle_set)

# SETLIST ------------------------
setlist_parser = subparsers.add_parser(
"setlist",
help="Return list of sets with code, card total, and year of release",
)
setlist_parser.set_defaults(func=handle_setlist)

# STATS -----------------------
stats_parser = subparsers.add_parser(
"stats", help="Return stats for current database"
)
# TODO: Add additional optional args to filter search query: type, colour, set, etc.
#
# stats_parser.add_argument(
# "-s", "--set", help="search database for cards matching this setcode"
# )
# stats_parser.add_argument(
# "-ct", "--card-type", help="search database for cards matching this type"
# )

stats_parser.set_defaults(func=handle_stats)

# CLEAR ------------------------
clear_parser = subparsers.add_parser("clear", help="Clear the database")
clear_parser.set_defaults(func=handle_clear)

return parser


################
# Command handlers
#


def handle_random(args, db_connection):
if args.number == 1:
print("Drawing a random card from Scryfall.com...")
else:
print(f"Drawing {args.number} random cards from Scryfall.com...")

query = "" # TODO: add optional search flag to random argparse

# get a single random card, based on search parameters
card = get_random_card(query) or []
insert_cards(card, get_timestamp(), db_connection)
print(get_total_cards(db_connection), "cards currently in database.")


def handle_search(args, db_connection):
query = " ".join(args.search_query)
print(f"Searching for cards matching: {query}")

card_list = get_card_list(query) or []
stamp = get_timestamp()
insert_cards(card_list, stamp, db_connection)
# print(get_total_cards(db_connection), "cards currently in database.")
print(f"{len(card_list)} cards found.")
print_stats(db_connection, stamp)


def handle_set(args, db_connection):
# Search for a set of cards
connection = db_connection
query_setcode = ""
if args.set_query.lower() == "latest":
# Find the moce recent release and query stats for it
current_set = find_current_release(set_codes())
current_set_code = current_set.get("set_code")
query_setcode = str(current_set_code)
current_set_name = current_set.get("name")
print(f"Stats for {current_set_name} ({current_set_code}):")
else:
query_setcode = str(args.set_query)
print(f"Stats for {lookup_set_info("name", args.set_query.upper())}")

query = f"set:{query_setcode} unique:prints" # unique:prints includes variations within set

print(f" Released: {lookup_set_info("release_date", query_setcode.upper())}")

print(f" {lookup_set_info("card_count", query_setcode.upper())} cards in set")

card_list = get_card_list(query) or []
stamp = get_timestamp()

insert_cards(card_list, stamp, connection)
print_stats(connection, stamp)


def handle_setlist(args, db_connection):
print("All Main and Commander MTG expansion sets:\n")
setlist = set_codes()
current_set = find_current_release(setlist)
for set_info in setlist:
print(
format_set_info(set_info),
end="",
)
if set_info == current_set:
print(" <- current release")
else:
print(
"No valid search parameters. Try `scry random` or `scry list '<scryfall query>'"
)
except Exception as err:
print("Error in __main__: ", err)
finally:
connection.close() # finally close db connection
print()


def handle_stats(args, db_connection):
print("STATS for ALL cards in database:")
print(get_total_cards(db_connection), "cards in database")
print_stats(db_connection)


def handle_clear(args, db_connection):
# HACK: why does this only work with db_connection and args, even though neither
# are required? Same with handle_setlist...
clear_database()


# Helper functions:


def print_stats(connection, timestamp=None):
Expand All @@ -84,9 +195,18 @@ def print_stats(connection, timestamp=None):
print(s)


def get_timestamp():
return datetime.now()
def format_set_info(set_details) -> str:
date = datetime.fromisoformat(set_details["release_date"])
return f"{set_details["set_code"]: <5} {set_details["name"]:<38} {set_details["card_count"]:>6} cards {date.year:>10}"


def lookup_set_info(info: str, set_code: str) -> str:
setlist = set_codes()
for s in setlist:
if set_code == s["set_code"]:
return s[info]
return "Set info not found. Check the setcode is correct. You can request name, card_count, release_date"


if __name__ == "__main__":
main()
def get_timestamp():
return datetime.now()
5 changes: 0 additions & 5 deletions src/scry/db_insert.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ def insert_cards(cards: list, timestamp: datetime, connection) -> int:
cursor.executemany(insert_query, rows)
connection.commit()

if len(cards) > 1:
print(f"Added {len(cards)} cards into database.")
else:
print(f"Added '{cards[0]['name']}' into database.")

return len(cards)
except Exception as err:
print(f"Error occured talking to database: {err}")
Expand Down
Loading