Esempio n. 1
0
def search_card(cardname, drive_order, grouping=0):
    # Search for card front and attach it to results
    cardname_split = None
    if "&" in cardname:
        cardname_split = cardname.split("&")[0]

    results_front = search_card_face(to_searchable(cardname), drive_order,
                                     grouping)
    results = (results_front, )

    if not results_front and cardname_split:
        # Search again but only consider text before the &
        return search_card(cardname_split, drive_order, grouping)

    # Determine if this card is a double-faced card
    # TODO: Not naive string contains for determining if a card is a double faced card?
    tf_result = [x for x in transforms.keys() if to_searchable(cardname) in x]
    if tf_result:
        # Search for the back face, and attach it to results
        cardname_back = transforms[tf_result[0]]
        if grouping > 0:
            results_back = search_card_face(to_searchable(cardname_back),
                                            drive_order, grouping + 1)
        else:
            results_back = search_card_face(to_searchable(cardname_back),
                                            drive_order)
        if results_back:
            results = (results_front, results_back)
    if not results_front:
        results = ({"query": cardname}, )
    return results
Esempio n. 2
0
def sync_dfcs():
    scryfall_query = "https://api.scryfall.com/cards/search?q=is:dfc%20-layout:art_series%20-layout:double_faced_token"
    response = json.loads(requests.get(scryfall_query).content)

    # maintain list of all dfcs found so far
    q_dfcpairs = []

    for x in response['data']:
        # retrieve front and back names for this card, then create a DFCPair for it and append to list
        front_name = x['card_faces'][0]['name']
        back_name = x['card_faces'][1]['name']
        q_dfcpairs.append(
            DFCPair(front=to_searchable(front_name),
                    back=to_searchable(back_name)))

    # synchronise the located DFCPairs to database
    t0 = time.time()
    key_fields = ('front', )
    ret = bulk_sync(new_models=q_dfcpairs,
                    key_fields=key_fields,
                    filters=None,
                    db_class=DFCPair)

    print(
        "Finished synchronising database with Scryfall DFCs, which took {} seconds."
        .format(time.time() - t0))
Esempio n. 3
0
def parse_text(input_lines, offset=0):
    transforms = dict((x.front, x.back) for x in DFCPair.objects.all())
    cards_dict = OrderDict()

    curr_slot = offset

    # loop over lines in the input text, and for each, parse it into usable information
    for line in input_lines.splitlines():
        # extract the query and quantity from the current line of the input text
        (query, qty) = process_line(line)

        if query:
            # cap at 612
            over_cap = False
            if qty + curr_slot >= 612:
                qty = 612 - curr_slot
                over_cap = True

            req_type = ""
            curr_slots = list(range(curr_slot, curr_slot + qty))

            # first, determine if this card is a DFC by virtue of it having its two faces separated by an ampersand
            query_faces = [query, ""]
            if "&" in query_faces[0]:
                query_split = [to_searchable(x) for x in query.split(" & ")]
                if (query_split[0] in transforms.keys()
                        and query_split[1] in transforms.values()):
                    query_faces = query_split
            elif query[0:2].lower() == "t:":
                query_faces[0] = to_searchable(query[2:])
                req_type = "token"
            else:
                query_faces[0] = to_searchable(query)
                # gotta check if query is the front of a DFC here as well
                if query_faces[0] in transforms.keys():
                    query_faces = [query, transforms[query_faces[0]]]

            # stick the front face into the dictionary
            cards_dict.insert(query_faces[0], curr_slots, "front", req_type,
                              "")

            if query_faces[1]:
                # is a DFC, gotta add the back face to the correct slots
                cards_dict.insert(query_faces[1], curr_slots, "back", req_type,
                                  "")

            else:
                # is not a DFC, so add this card's slots onto the common cardback's slots
                cards_dict.insert_empty(curr_slots, "back")

            curr_slot += qty

            if over_cap:
                break

    return cards_dict.order, curr_slot - offset
Esempio n. 4
0
def search_database(drive_order, query, s):
    # TODO: elasticsearch_dsl.serializer.serializer ?
    # search through the database for a given query, over the drives specified in drive_orders,
    # using the search index specified in s (this enables reuse of code between Card and Token search functions)

    results = []

    # set up search - match the query and use the AND operator
    match = Match(searchq={"query": to_searchable(query), "operator": "AND"})

    # match the cardname once instead of for every drive to save on search time
    s_query = s.query(match)

    # iterate over drives, filtering on the current drive, ordering by priority in descending order,
    # then add the returned hits to the results list
    hits = s_query.sort({
        'priority': {
            'order': 'desc'
        }
    }).params(preserve_order=True).scan()

    results0 = [x.to_dict() for x in hits]
    for drive in drive_order:
        results += [x for x in results0 if x['source'] == drive]

    return results
Esempio n. 5
0
def search(drive_order, query, req_type):
    # this function can either receive a request with "normal" type with query like "t:goblin",
    # or a request with "token" type with query like "goblin", so handle both of those cases here
    if query.lower()[0:2] == "t:":
        query = query[2:]
        req_type = "token"

    # now that we've potentially trimmed the query for tokens, convert the query to a searchable string
    query = to_searchable(query)

    # search for tokens if this request is for a token
    if req_type == "token":
        results = query_es_token(drive_order, query)

    # search for cardbacks if request is for cardbacks
    elif req_type == "back":
        results = query_es_cardback()

    # otherwise, search normally
    else:
        results = query_es_card(drive_order, query)

    return {
        "data": results,
        "req_type": req_type,
        "query": query,
    }
Esempio n. 6
0
def sync_dfcs():
    scryfall_query_dfc = "https://api.scryfall.com/cards/search?q=is:dfc%20-layout:art_series%20-layout:double_faced_token"
    response_dfc = json.loads(requests.get(scryfall_query_dfc).content)

    # maintain list of all dfcs found so far
    q_dfcpairs = []

    for x in response_dfc["data"]:
        # retrieve front and back names for this card, then create a DFCPair for it and append to list
        front_name = x["card_faces"][0]["name"]
        back_name = x["card_faces"][1]["name"]
        q_dfcpairs.append(
            DFCPair(front=to_searchable(front_name),
                    back=to_searchable(back_name)))

    # also retrieve meld pairs and save them as DFCPairs
    time.sleep(0.1)
    scryfall_query_meld = "https://api.scryfall.com/cards/search?q=is:meld%"
    response_meld = json.loads(requests.get(scryfall_query_meld).content)

    for x in response_meld["data"]:
        card_part = [y for y in x["all_parts"] if y["name"] == x["name"]][0]
        meld_result = [
            y for y in x["all_parts"] if y["component"] == "meld_result"
        ][0]["name"]
        if card_part["component"] == "meld_part":
            is_top = "\n(Melds with " not in x["oracle_text"]
            card_bit = "Top" if is_top else "Bottom"
            q_dfcpairs.append(
                DFCPair(
                    front=to_searchable(x["name"]),
                    back=to_searchable(f"{meld_result} {card_bit}"),
                ))

    # synchronise the located DFCPairs to database
    t0 = time.time()
    key_fields = ("front", )
    ret = bulk_sync(new_models=q_dfcpairs,
                    key_fields=key_fields,
                    filters=None,
                    db_class=DFCPair)

    print(
        "Finished synchronising database with Scryfall DFCs, which took {} seconds."
        .format(time.time() - t0))
def add_card(folderDict, parentDict, folder, db_datetime, item):
    if not item['trashed']:
        folderName = folderDict[item['parents'][0]]
        parentName = parentDict[item['parents'][0]]

        owner = item['owners'][0]['displayName']

        scryfall = False
        priority = 2
        if "Retro Cube" in parentName:
            priority = 0
        if ")" in item['name']:
            priority = 1
        source = "Unknown"
        if folder['name'] == "Chilli_Axe's MPC Proxies":
            source = "Chilli_Axe"
            if folderName == "12. Cardbacks":
                if "Black Lotus" in item['name']:
                    priority += 10
                source += "_cardbacks"
                priority += 5

        elif owner in OWNERS:
            source = OWNERS[owner]

        elif folder['name'] == "MPC Scryfall Scans":
            source = "berndt_toast83/" + folderName
            scryfall = True

        if "Basic" in folderName:
            priority += 5

        if scryfall:
            SOURCES["berndt_toast83"]["quantity"] += 1
        else:
            SOURCES[source]["quantity"] += 1

        # Store the image's static URL
        static_url = "https://drive.google.com/thumbnail?sz=w400-h400&id=" + item[
            'id']

        # Calculate source image DPI, rounded to tens
        dpi = 10 * round(
            int(item['imageMediaMetadata']['height']) * DPI_HEIGHT_RATIO / 10)

        # Return card info so we can insert into database
        return item['id'], cardname, priority, source, dpi, to_searchable(
            cardname), static_url
Esempio n. 8
0
def search_database(drive_order, query, model):
    if query == "":
        return []
    # search through the database for a given query, over the drives specified in drive_orders,
    # using the search index specified in s (this enables reuse of code between Card and Token search functions)
    results = []

    query_parsed = to_searchable(query)

    hits = [
        x.to_dict() for x in model.objects.filter(searchq__search=query_parsed)
    ]
    hits.sort(key=lambda x: distance(x["searchq"], query_parsed))

    for drive in drive_order:
        results += [x for x in hits if x["source"] == drive]

    return results
Esempio n. 9
0
    'Village Ironsmith': 'Ironfang',
    'Village Messenger': 'Moonrise Intruder',
    'Villagers of Estwald': 'Howlpack of Estwald',
    'Voldaren Pariah': 'Abolisher of Bloodlines',
    'Westvale Abbey': 'Ormendahl, Profane Prince',
    'Wolfbitten Captive': 'Krallenhorde Killer',
    'Gisela, the Broken Blade': 'Brisela, Voice of Nightmares Top',
    'Bruna, the Fading Light': 'Brisela, Voice of Nightmares Bottom',
    'Hanweir Battlements': 'Hanweir, the Writhing Township Top',
    'Hanweir Garrison': 'Hanweir, the Writhing Township Bottom',
    'Graf Rats': 'Chittering Host Top',
    'Midnight Scavengers': 'Chittering Host Bottom'
}

transforms = dict(
    (to_searchable(x), to_searchable(y)) for x, y in transforms.items())


def search_card_face(cardname, drive_order, grouping=0):
    # Search for a card, given its name and the drives to search
    # Return a tuple of dictionaries (returning as a dict rather than Card for Javascript access purposes)
    results = SearchQuerySet().filter(content=cardname)
    # Retrieve Card objects from search results while filtering out cardbacks
    card_objs = [
        x.object for x in results if "_cardback" not in x.object.source
    ]
    cards_found = []
    for source in drive_order:
        cards_this_source = [x for x in card_objs if source in x.source]
        if cards_this_source:
            # Sort cards from this source by priority and convert them all to dicts
Esempio n. 10
0
def parse_csv(csv_bytes):
    # TODO: I'm sure this can be cleaned up a lot, the logic here is confusing and unintuitive
    cards_dict = OrderDict()
    curr_slot = 0

    # support for different types of encoding - detect the encoding type then decode the given bytes according to that
    csv_format = chardet.detect(csv_bytes)
    csv_string_split = csv_bytes.decode(csv_format['encoding']).splitlines()

    # handle case where csv doesn't have correct headers
    headers = 'Quantity,Front,Back'
    if csv_string_split[0] != headers:
        # this CSV doesn't appear to have the correct column headers, so we'll attach them here
        csv_string_split = [headers] + csv_string_split
    csv_dictreader = csv.DictReader(csv_string_split)

    for line in csv_dictreader:
        qty = line['Quantity']
        if qty:
            # try to parse qty as int
            try:
                qty = int(qty)
            except ValueError:
                # invalid qty
                continue
        else:
            # for empty quantities, assume qty=1
            qty = 1

        # only care about lines with a front specified
        if line['Front']:
            # the slots for this line in the CSV
            curr_slots = list(range(curr_slot, curr_slot + qty))

            query_faces = [line['Front'], line['Back']]
            req_type_front = "normal"
            req_type_back = "normal"

            # process the front face as a token if necessary
            if query_faces[0][0:2].lower() == "t:":
                query_faces[0] = query_faces[0][2:]
                req_type_front = "token"

            if not line['Back']:
                # back face not specified
                # potentially doing transform things, because a back wasn't specified
                # first, determine if this card is a DFC by virtue of it having its two faces separated by an ampersand
                if '&' in query_faces[0]:
                    query_split = [
                        to_searchable(x) for x in query.split(" & ")
                    ]
                    if query_split[0] in transforms.keys(
                    ) and query_split[1] in transforms.values():
                        query_faces = query_split
                else:
                    # gotta check if query is the front of a DFC here as well
                    query_faces[0] = to_searchable(query_faces[0])
                    if query_faces[0] in transforms.keys():
                        query_faces = [
                            query_faces[0], transforms[query_faces[0]]
                        ]

            else:
                # both sides specified - process the back face as a token if necessary
                if query_faces[1][0:2].lower() == "t:":
                    query_faces[1] = query_faces[1][2:]
                    req_type_back = "token"

            # ensure everything has been converted to searchable
            query_faces = [to_searchable(x) for x in query_faces]

            # stick the front face into the dictionary
            cards_dict.insert(query_faces[0], curr_slots, "front",
                              req_type_front, "")

            if query_faces[1]:
                # is a DFC, gotta add the back face to the correct slots
                cards_dict.insert(query_faces[1], curr_slots, "back",
                                  req_type_back, "")

            else:
                # is not a DFC, so add this card's slots onto the common cardback's slots
                cards_dict.insert_back(curr_slots)

            curr_slot += qty

    # TODO: Read in chunks if big?
    return cards_dict.order, curr_slot
Esempio n. 11
0
def add_card(folderDict, parentDict, folder, item):
    try:
        # file is valid when it's not trashed and filesize does not exceed 30 MB
        valid = not item['trashed'] and int(item['size']) < 30000000
        if not valid:
            print("Can't index this card: {}".format(item))
    except KeyError:
        valid = True

    if valid:
        # strip the extension off of the item name to retrieve the card name
        try:
            [cardname, extension] = item['name'].rsplit('.', 1)
        except ValueError:
            print("nani {}".format(item['name']))
            return

        img_type = "normal"
        folderName = folderDict[item['parents'][0]]
        parentName = parentDict[item['parents'][0]]

        owner = item['owners'][0]['displayName']

        scryfall = False
        priority = 2
        if "Retro Cube" in parentName:
            priority = 0
        if ")" in cardname:
            priority = 1
        source = "Unknown"
        if folder['name'] == "Chilli_Axe's MPC Proxies":
            source = "Chilli_Axe"
            if folderName == "12. Cardbacks":
                if "Black Lotus" in item['name']:
                    priority += 10
                img_type = "cardback"
                priority += 5

        elif folder['name'] == "nofacej MPC Card Backs":
            img_type = "cardback"
            source = "nofacej"

        # this elif and the next one were the other way around - swap them back if shit breaks
        elif folder['name'] == "MPC Scryfall Scans":
            source = "berndt_toast83/" + folderName
            scryfall = True

        elif owner in OWNERS:
            source = OWNERS[owner]

        if "basic" in folderName.lower():
            priority += 5

        elif "token" in folderName.lower():
            img_type = "token"

        elif "cardbacks" in folderName.lower(
        ) or "card backs" in folderName.lower():
            img_type = "cardback"

        # Store the image's static URL
        static_url = "https://drive.google.com/thumbnail?sz=w400-h400&id=" + item[
            'id']

        # Calculate source image DPI, rounded to tens
        dpi = 10 * round(
            int(item['imageMediaMetadata']['height']) * DPI_HEIGHT_RATIO / 10)

        # Return card info so we can insert into database, in the correct list
        card_info = (item['id'], cardname, priority, source, dpi,
                     to_searchable(cardname), extension, item['createdTime'])

        # Skip card if its source couldn't be determined
        if source == "Unknown":
            return

        if img_type == "cardback":
            q_cardbacks.append(card_info)
            if scryfall:
                SOURCES["berndt_toast83"]["qty_cardbacks"] += 1
            else:
                SOURCES[source]["qty_cardbacks"] += 1
        elif img_type == "token":
            q_tokens.append(card_info)
            if scryfall:
                SOURCES["berndt_toast83"]["qty_tokens"] += 1
            else:
                SOURCES[source]["qty_tokens"] += 1
        else:
            q_cards.append(card_info)
            if scryfall:
                SOURCES["berndt_toast83"]["qty_cards"] += 1
            else:
                SOURCES[source]["qty_cards"] += 1
Esempio n. 12
0
def add_card(folder, source, item, q_cards, q_cardbacks, q_tokens):
    try:
        # file is valid when it's not trashed and filesize does not exceed 30 MB
        valid = not item["trashed"] and int(item["size"]) < 30000000
        if not valid:
            print(
                "Can't index this card: <{}> {}, size: {} bytes".format(
                    item["id"], item["name"], item["size"]
                )
            )
    except KeyError:
        valid = True

    if valid:
        # strip the extension off of the item name to retrieve the card name
        try:
            [cardname, extension] = item["name"].rsplit(".", 1)
        except ValueError:
            print("Issue with parsing image: {}".format(item["name"]))
            return

        source_verbose = "Unknown"

        img_type = "card"
        folder_name = item["folder_name"]
        parent_name = item["parent_name"]

        # Skip this file if it doesn't have a name after splitting name & extension
        if not cardname:
            return

        scryfall = False
        priority = 2

        if ")" in cardname:
            priority = 1

        if folder["name"] == "Chilli_Axe's MPC Proxies":
            source_verbose = "Chilli_Axe"

            if "Retro Cube" in parent_name:
                priority = 0
                source_verbose = "Chilli_Axe Retro Cube"

            elif folder_name == "12. Cardbacks":
                if "Black Lotus" in item["name"]:
                    priority += 10
                img_type = "cardback"
                priority += 5

        elif folder["name"] == "nofacej MPC Card Backs":
            img_type = "cardback"
            source_verbose = source.id

        elif folder["name"] == "MPC Scryfall Scans":
            source_verbose = "berndt_toast83/" + folder_name
            scryfall = True

        else:
            source_verbose = source.id

        if "basic" in folder_name.lower():
            priority += 5
            source_verbose = source_verbose + " Basics"

        elif "token" in folder_name.lower():
            img_type = "token"
            if not scryfall:
                source_verbose = source_verbose + " Tokens"

        elif "cardbacks" in folder_name.lower() or "card backs" in folder_name.lower():
            img_type = "cardback"
            source_verbose = source_verbose + " Cardbacks"

        # Calculate source image DPI, rounded to tens
        dpi = 10 * round(
            int(item["imageMediaMetadata"]["height"]) * DPI_HEIGHT_RATIO / 10
        )

        # Skip card if its source couldn't be determined
        if source == "Unknown":
            return

        # Use a dictionary to map the img type to the queue and class of card we need to append/create
        queue_object_map = {
            "cardback": (q_cardbacks, Cardback),
            "token": (q_tokens, Token),
            "card": (q_cards, Card),
        }

        queue_object_map[img_type][0].append(
            queue_object_map[img_type][1](
                id=item["id"],
                name=cardname,
                priority=priority,
                source=source,
                source_verbose=source_verbose,
                dpi=dpi,
                searchq=to_searchable(cardname),
                thumbpath=extension,
                date=item["createdTime"],
            )
        )
Esempio n. 13
0
def add_card(folderDict, parentDict, folder, db_datetime, item):
    if not item['trashed']:
        folderName = folderDict[item['parents'][0]]
        parentName = parentDict[item['parents'][0]]

        owner = item['owners'][0]['displayName']

        folders_sources = {
            "Jake Rowe": "nofacej_cardbacks",
            "Bazuki Alters": "Bazukii",
            "Karlin Courtney": "hathwellcrisping",
            "Digital Red": "Proxycommander",
            "Alastair Jack": "male_MPC",
            "i Derp": "iDerp69",
            "Trey Kapfer": "MrChow1917",
            "Tristan DELMAS": "Celid_of_the_wind"
        }

        scryfall = False
        priority = 2
        if "Retro Cube" in parentName:
            priority = 0
        if ")" in item['name']:
            priority = 1
        source = "Unknown"
        if folder['name'] == "Chilli_Axe's MPC Proxies":
            source = "Chilli_Axe"
            if folderName == "12. Cardbacks":
                if "Black Lotus" in item['name']:
                    priority += 10
                source += "_cardbacks"
                priority += 5

        elif owner in folders_sources.keys():
            source = folders_sources[owner]

        elif folder['name'] == "MPC Scryfall Scans":
            source = "berndt_toast83/" + folderName
            scryfall = True

        if "Basic" in folderName:
            priority += 5

        if scryfall:
            SOURCES["berndt_toast83"]["quantity"] += 1
        else:
            SOURCES[source]["quantity"] += 1
        folder_path = "./../staticroot/cardpicker/" + source
        # folder_path = "cardpicker/static/cardpicker/" + source

        folder_path = os.path.abspath(folder_path)

        # Download card thumbnail if necessary
        file_datetime = datetime.datetime.strptime(item["modifiedTime"],
                                                   "%Y-%m-%dT%H:%M:%S.%fZ")

        thumbnail_path = folder_path + "/" + item['id'] + ".png"
        try:
            if not os.path.exists(folder_path):
                os.makedirs(folder_path)
        except FileExistsError:
            pass

        # Calculate source image DPI, rounded to tens
        dpi = 10 * round(
            int(item['imageMediaMetadata']['height']) * DPI_HEIGHT_RATIO / 10)

        if not os.path.isfile(thumbnail_path) or file_datetime > db_datetime:
            # three tries at downloading the file
            counter = 0
            while counter < 3:
                try:
                    # Read thumbnail
                    thumbnail = imageio.imread(
                        "https://drive.google.com/thumbnail?sz=w400-h400&id=" +
                        item['id'])

                    # Trim off 13 pixels around the edges, which should remove the print bleed edge,
                    # assuming the image is 293 x 400 in resolution, before writing to disk
                    imageio.imwrite(thumbnail_path, thumbnail[13:-13,
                                                              13:-13, :])
                    break
                except:  # TODO: Not bare except
                    counter += 1
            if counter >= 3:
                print("Failed to download thumbnail for: {}".format(
                    item['name']))

        # Remove the file extension from card name
        cardname = '.'.join(item['name'].split(".")[0:-1])

        # Store the image's static URL as well
        static_url = "cardpicker/" + source + "/" + item['id'] + ".png"

        # Return card info so we can insert into database
        return item['id'], cardname, priority, source, dpi, to_searchable(
            cardname), static_url
Esempio n. 14
0
              'Sea Gate Restoration': 'Sea Gate, Reborn',
              'Sejiri Shelter': 'Sejiri Glacier',
              'Shatterskull Smashing': 'Shatterskull, the Hammer Pass',
              'Silundi Vision': 'Silundi Isle',
              'Skyclave Cleric': 'Skyclave Basilica',
              'Song-Mad Treachery': 'Song-Mad Ruins',
              'Spikefield Hazard': 'Spikefield Cave',
              'Tangled Florahedron': 'Tangled Vale',
              'Turntimber Symbiosis': 'Turntimber, Serpentine Wood',
              'Umara Wizard': 'Umara Skyfalls',
              'Valakut Awakening': 'Valakut Stoneforge',
              'Vastwood Fortification': 'Vastwood Thicket',
              'Zof Consumption': 'Zof Bloodbog',
}

transforms = dict((to_searchable(x), to_searchable(y)) for x, y in transforms.items())


def search_card_face(cardname, drive_order, grouping=0):
    # Search for a card, given its name and the drives to search
    # Return a tuple of dictionaries (returning as a dict rather than Card for Javascript access purposes)
    results = SearchQuerySet().filter(content=cardname).load_all()
    # Retrieve Card objects from search results while filtering out cardbacks
    card_objs = [x.object for x in results if "_cardback" not in x.object.source]
    cards_found = []
    for source in drive_order:
        cards_this_source = [x for x in card_objs if source in x.source]
        if cards_this_source:
            # Sort cards from this source by priority and convert them all to dicts
            priorities = [int(x.priority) for x in cards_this_source]
            cards_this_source = [x.to_dict() for _, x in sorted(zip(priorities, cards_this_source),