Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch import_from_scryfall_api_search Excluding Merge-Ins
This is equivalent to a diff from 8d9398b3cf to 0a24e6d490
2024-08-29
| ||
17:51 | Merge with trunk, Round 1. Merged in decimal document settings support. check-in: 5be445c009 user: thomas tags: WIP | |
2024-08-10
| ||
17:24 | Implement downloading Scryfall searches as CSV from the Scryfall API. Integrate into the deck list import wizard, adding a text field for Scryfall searches that can be directly imported as deck lists. Implements [d6085a9f5c42470a]. check-in: 3ac672ea94 user: thomas tags: trunk | |
17:23 | ScryfallCSVDownloader: Enable downloading "extras", like tokens, planes, etc. Closed-Leaf check-in: 0a24e6d490 user: thomas tags: import_from_scryfall_api_search | |
17:20 | Add changelog entry. Limit the Scryfall search query length to 900 characters. The API has a hard limit of 1000 characters, so stay below that. check-in: c53374c308 user: thomas tags: import_from_scryfall_api_search | |
15:00 | Add test case verifying that the sample data for the tappedout CSV parser is consistent with the actual live API results. check-in: 91311da813 user: thomas tags: import_from_scryfall_api_search | |
2024-08-08
| ||
08:06 | Merge with trunk. check-in: 17b2a898a0 user: thomas tags: l10n | |
2024-08-07
| ||
17:55 | units_and_sizes: Create the unit registry via a setup method. Register a conversion context for length ⇔ pixel conversions. The latter allows conversion without the need of an external function. check-in: 285cd70c39 user: thomas tags: embrace_pint | |
17:48 | Allow floating point values for numerical document settings. Also clamp these values to the [0, 10000] range. Implements [25c5698daf89fc54] and [34f7de80cb8b803e] check-in: 8d9398b3cf user: thomas tags: trunk | |
17:46 | DocumentSettings: Clamp all values to the [0, 10000] range, both when reading from the settings file and from loaded documents. This handles range violations more gracefully than resetting to defaults (settings file) or refusing to load (saved documents). Closed-Leaf check-in: bf1a830afd user: thomas tags: decimal_spacings | |
2024-07-23
| ||
07:34 | Ui string fixes. check-in: 2050ad6dff user: thomas tags: trunk | |
Changes to doc/changelog.md.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | + + + + + | # Changelog # Next version (in development) ## New features - The deck import wizard can now directly download Scryfall search queries as deck lists - Added a text field to enter a Scryfall card search query, a button to show the result on the Scryfall website, and a button that downloads the search result as a deck list. - Add option to fully automatically remove basic lands from all imported deck lists. - When enabled in the settings, basic lands are automatically stripped from deck lists, otherwise the previous behavior is retained. - The option honors the settings regarding inclusion of Wastes or Snow-Covered basic lands. - Add new card filter to hide Art Series cards, which can be enabled in the application settings. - When updating from previous versions, the filter becomes functional after the next card data update. ## Changed features - The deck list import wizard now supports downloading links to the Scryfall API card search at [https://api.scryfall.com/cards/search](https://scryfall.com/docs/api/cards/search) - Support decimal values in document settings, like margins, image spacings and the card bleed width. - As a safety measure against DoS-attacks via loading malicious documents, limit numerical document settings to 10000mm. Limiting the paper size to 10m (~394in) in each direction prevents the creation of indefinitely large drawing areas that could consume all system main memory until either the application or the system crashes. - Improved the related card search: The search now finds tokens created by Dungeons. Right-clicking a card with "Venture" or "Initiative" now also suggests the tokens created by the dungeon rooms. |
︙ |
Changes to mtg_proxy_printer/decklist_downloader.py.
︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | + | This module is responsible for downloading deck lists from a known list of deckbuilder websites. """ import abc import collections import csv import html.parser import io import urllib.parse from io import StringIO import platform import re import typing import ijson from PyQt5.QtGui import QValidator |
︙ | |||
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | + + + + + - - + + - + + - - + + + + + + + + + + + + | class DecklistDownloader(DownloaderBase): DECKLIST_PATH_RE = re.compile(r"") PARSER_CLASS: typing.Type[ParserBase] = None APPLICABLE_WEBSITES: str = "" def download(self, decklist_url: str) -> str: """ Fetches the decklist from the given URL. The base class handles the download including transparent decompression, and performs post-processing steps: Replacing Windows-style line endings \r\n with plain \n newlines, and decoding bytes assuming utf-8 input """ logger.info(f"About to fetch deck list from {decklist_url}") download_url = self.map_to_download_url(decklist_url) logger.debug(f"Obtained download URL: {download_url}") data, monitor = self.read_from_url(download_url, "Downloading deck list:") with data, monitor: raw_data = data.read() deck_list = self.post_process(raw_data) line_count = deck_list.count('\n') logger.debug(f"Obtained deck list containing {line_count} lines.") return deck_list @staticmethod def post_process(data: bytes) -> str: """Takes the raw, downloaded data and post-processes them into a user-presentable string.""" deck_list = data.replace(b"\r\n", b"\n") deck_list = deck_list.decode("utf-8") return deck_list @abc.abstractmethod def map_to_download_url(self, decklist_url: str) -> str: |
︙ |
Changes to mtg_proxy_printer/decklist_parser/csv_parsers.py.
︙ | |||
103 104 105 106 107 108 109 | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | - + - + - - - - - - - - - + + + + + + + + + + + + + + + + + | doublequote = True skipinitialspace = False lineterminator = "\n" quoting = csv.QUOTE_MINIMAL DIALECT_NAME = "scryfall_com" USED_COLUMNS = { |
︙ |
Changes to mtg_proxy_printer/http_file.py.
︙ | |||
217 218 219 220 221 222 223 224 225 226 227 228 229 230 | 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | + + + + | if first_byte > 0: headers["range"] = f"bytes={first_byte}-{self.content_length-1}" request = urllib.request.Request(self.url, headers=headers) last_error = None for retry in range(outer_retries, self.retry_limit or 1): try: response: http.client.HTTPResponse = urllib.request.urlopen(request) except urllib.error.HTTPError as e: if e.code in {400, 403, 404}: # Do not re-try bad requests, permission denied or not-found URLs raise e except urllib.error.URLError as e: # URLError is most likely caused by being offline, # so wait a bit to not immediately burn all remaining retries if self.closed: # Do not sleep, if this instance was closed externally. Just break in that case. break time.sleep(5) |
︙ |
Changes to mtg_proxy_printer/resources/ui/deck_import_wizard/load_list_page.ui.
1 2 3 4 5 6 7 8 9 10 11 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>LoadListPage</class> <widget class="QWizardPage" name="LoadListPage"> <property name="enabled"> <bool>true</bool> </property> <property name="geometry"> <rect> <x>0</x> <y>0</y> |
︙ |
Changes to mtg_proxy_printer/ui/deck_import_wizard.py.
︙ | |||
15 16 17 18 19 20 21 22 23 | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | + - - + + | import configparser import itertools import math import pathlib import re import typing import urllib.error import urllib.parse from PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal, pyqtProperty as Property, QStringListModel, Qt, \ |
︙ | |||
96 97 98 99 100 101 102 | 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | - - + + + + + + + + - - + + - - - - - - + + + + + + - - + + | class LoadListPage(QWizardPage): LARGE_FILE_THRESHOLD_BYTES = 200*2**10 deck_list_downloader_changed = Signal(str) def __init__(self, language_model: QStringListModel, *args, **kwargs): super().__init__(*args, **kwargs) |
︙ | |||
191 192 193 194 195 196 197 198 199 | 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | + + + + + + + - - - + + + - - + - - - + + + + + + + + + + + + + + | f'{name} (*.{" *.".join(extensions)})' for name, extensions in individual_file_types) \ + f";;{everything}" return result @Slot() def on_deck_list_download_button_clicked(self): url = self.ui.deck_list_download_url_line_edit.text() bad_request_msg="Verify that the URL is valid, reachable, and that the deck list is set to public.\n" \ "This program cannot download private deck lists. Please note, that setting deck lists to\n" \ "public may take a minute or two to apply." self._populate_deck_list_from_url(url, bad_request_msg) def _populate_deck_list_from_url(self, url: str, bad_request_msg: str): if not self.ui.deck_list.toPlainText() \ or QMessageBox.question( |
︙ |
Changes to tests/decklist_parser/test_scryfall_csv_parser.py.
︙ | |||
17 18 19 20 21 22 23 24 | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | + - + - - + + + + + + + - - + + + + + + + + + + + + + + - + + + + + + + + + + | import unittest.mock import pytest from hamcrest import * from mtg_proxy_printer.model.carddb import CardDatabase, Card, CardIdentificationData from mtg_proxy_printer.decklist_parser.csv_parsers import ScryfallCSVParser from mtg_proxy_printer.decklist_downloader import DecklistDownloader |
︙ | |||
91 92 93 94 95 96 97 | 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | + - + + + + + + + + + + | return card_list[0] def generate_test_cases_for_test_card_identification_works_in_simple_cases(): yield ( ["english_basic_Forest", "english_basic_Forest_2"], append_to_header( DECK_LIST_CSV_HEADER, |
︙ | |||
126 127 128 129 130 131 132 | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | + - - + + | @pytest.mark.parametrize( "cards_to_import, deck_list", [ ( ["english_basic_Forest"], append_to_header( DECK_LIST_CSV_HEADER, |
︙ |
Changes to tests/decklist_parser/test_tappedout_csv_parser.py.
︙ | |||
17 18 19 20 21 22 23 24 | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | + - + - + + + + + + + + + + + + + + + - + | import unittest.mock import pytest from hamcrest import * from mtg_proxy_printer.model.carddb import CardDatabase, Card, CardIdentificationData from mtg_proxy_printer.decklist_parser.csv_parsers import TappedOutCSVParser from mtg_proxy_printer.decklist_downloader import DecklistDownloader |
︙ | |||
86 87 88 89 90 91 92 | 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | - + | ) return card_list[0] def generate_test_cases_for_test_card_identification_works_in_simple_cases(): yield ( ["english_basic_Forest", "english_basic_Forest_2"], |
︙ | |||
115 116 117 118 119 120 121 | 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | - + | ) @pytest.mark.parametrize( "cards_to_import, deck_list", [ ( ["english_basic_Forest"], |
︙ |
Changes to tests/test_decklist_downloader.py.
︙ | |||
43 44 45 46 47 48 49 | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | - + + + + | yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/deck/download/5077398?output=mtggoldfish&type=online" yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/deck/download/5077398?output=dek&type=online" # Deck archetype links yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/archetype/legacy-led-dredge#paper" yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/archetype/legacy-led-dredge#arena" yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/archetype/legacy-led-dredge#online" |
︙ | |||
161 162 163 164 165 166 167 168 169 170 171 172 173 174 | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | + + | # Scryfall yield ScryfallDownloader, "https://scryfall.com/@user/8c02b4b2-50e2-4431-83e8-bfdea0951ce3/" # missing /deck # Invalid/missing UUIDS yield ScryfallDownloader, "https://scryfall.com/@user/decks/8c02b4b2-50e2-4431-83e-8-bfdea0951ce3/?with=eur" yield ScryfallDownloader, "https://scryfall.com/@user/decks/8c02b4b2-50e2-xyza-83e8-bfdea0951ce3/?with=tix" yield ScryfallDownloader, "https://scryfall.com/@user/decks/?with=arena" # API search without query yield ScryfallDownloader, "https://api.scryfall.com/cards/search?format=csv" # mtg.wtf yield MTGWTFDownloader, "https://mtg.wtf/deck/c21" yield MTGWTFDownloader, "https://mtg.wtf/deck/c21/" # MTG Arena Zone (mtgazone.com) yield MTGAZoneDownloader, "https://mtgazone.com/deck" |
︙ | |||
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | + + + | -> typing.Generator[typing.Tuple[typing.Type[DecklistDownloader], str, str], None, None]: """ Yields tuples with Parser class, deck list url and a snippet of the deck list content. It does not include the full deck list, because reported printings or price information may change over time, causing test failures. The tests should pass as long as the website returns some plausible data. """ yield MTGWTFDownloader, "https://mtg.wtf/deck/c21/prismari-performance/", "1 Jaya Ballard" # Deck list yield ScryfallDownloader, "https://scryfall.com/@luziferius/decks/e1a9af19-cfff-48c4-ae74-ed2dd78cb736", "Island" # API search yield ScryfallDownloader, "https://api.scryfall.com/cards/search?format=csv&q=e%3Arex+cn%E2%89%A527+cn%E2%89%A445", "f197b176-8fa0-451b-a981-a7a942890296" yield MTGAZoneDownloader, "https://mtgazone.com/deck/orzhov-phyrexians-march-of-the-machine-theorycraft/", "3 Cut Down" yield MTGTop8Downloader, "http://mtgtop8.com/event?e=9011&d=251345&f=BL", "4 [KTK] Abzan Charm" yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/deck/5136573", "1 Ancestral Recall" yield MTGGoldfishDownloader, "https://www.mtggoldfish.com/archetype/legacy-led-dredge", "4 Lion's Eye Diamond" yield TappedOutDownloader, "https://tappedout.net/mtg-decks/mtgproxyprinter-test-deck/", "Island" yield MoxfieldDownloader, "https://www.moxfield.com/decks/g1i2wHXC3kW0lanwY4Llkw", '"Zamriel, Seraph of Steel"' yield DeckstatsDownloader, "https://deckstats.net/decks/44867/576160-br-control-kld", "2 Blighted Fen" |
︙ |