From 90f94cbf3bf24492b878c367b6575f4914654a94 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Tue, 27 Jan 2026 14:43:54 -0800 Subject: [PATCH 01/76] DTC PDF generation working --- .gitignore | 4 + .python-version | 2 +- .../assets/icc/Adobe-Color-Profile-EULA.pdf | Bin 0 -> 64689 bytes desktop-tool/assets/icc/USWebCoatedSWOP.icc | Bin 0 -> 557168 bytes desktop-tool/autofill.py | 139 +++++++++++++- desktop-tool/src/constants.py | 32 ++++ desktop-tool/src/driver.py | 178 ++++++++++++++++++ desktop-tool/src/io.py | 15 +- desktop-tool/src/order.py | 78 +++++++- desktop-tool/src/pdf_maker.py | 159 +++++++++++++++- desktop-tool/src/processing.py | 128 ++++++++++++- desktop-tool/src/webdrivers.py | 45 ++++- desktop-tool/tests/test_desktop_tool.py | 99 +++++++++- 13 files changed, 845 insertions(+), 34 deletions(-) create mode 100644 desktop-tool/assets/icc/Adobe-Color-Profile-EULA.pdf create mode 100644 desktop-tool/assets/icc/USWebCoatedSWOP.icc diff --git a/.gitignore b/.gitignore index df862b5eb..8073a1c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ coverage.xml .pytest_cache/ desktop-tool/export/ desktop-tool/tests/export/ +desktop-tool/tests/cards/ cover/ @@ -178,3 +179,6 @@ MPCAutofill/drives.csv frontend/public/mockServiceWorker.js /image-cdn/node_modules/* /image-cdn/.wrangler/* + +# Plan files +*.plan.md diff --git a/.python-version b/.python-version index 24ee5b1be..2c45fe3a7 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13 +3.13.11 diff --git a/desktop-tool/assets/icc/Adobe-Color-Profile-EULA.pdf b/desktop-tool/assets/icc/Adobe-Color-Profile-EULA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..be14c29e75f420027fed88c7d50db1797a3bc913 GIT binary patch literal 64689 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 z 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
hF1k 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
+ 1 + (S30) Standard Smooth + false +
+ + + {tmp_path / "front_a.png"} + {SourceType.LOCAL_FILE} + 0 + front_a.png + + + {tmp_path / "front_b.png"} + {SourceType.LOCAL_FILE} + 1 + front_b.png + + + + {tmp_path / "back.png"} + + """ + ) + ), + allowed_to_exceed_project_max_size=True, + ) + order.name = "test_local.xml" + + generated_files = PdfExporter(order=order, export_mode="drive_thru_cards").execute( + post_processing_config=ImagePostProcessingConfig( + max_dpi=300, + downscale_alg=constants.ImageResizeMethods.LANCZOS, + output_format="JPEG", + convert_to_cmyk=False, + ) + ) + + assert generated_files == ["export/test_local/1.pdf"] + assert os.path.exists("export/test_local/1.pdf") + assert count_pdf_pages("export/test_local/1.pdf") == 4 + + # endregion # region test driver.py From 7e098147c40ad214d7e20e27a7a6a2fcfae0a0bc Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 13 Mar 2026 01:55:35 -0700 Subject: [PATCH 34/76] Write PDF/X output atomically --- desktop-tool/src/pdf_maker.py | 34 ++++++++++++----- desktop-tool/tests/test_desktop_tool.py | 50 ++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 75550c812..0816fd17d 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -64,6 +64,15 @@ def convert_pdf_to_pdfx( logger.warning("Ghostscript was not found. Skipping PDF/X-1a conversion.") return False + output_directory = os.path.dirname(output_path) or "." + os.makedirs(output_directory, exist_ok=True) + fd, temporary_output_path = tempfile.mkstemp( + dir=output_directory, + prefix=f".{os.path.splitext(os.path.basename(output_path))[0]}-", + suffix=".pdf", + ) + os.close(fd) + cmd = [ gs_path, "-dBATCH", @@ -78,22 +87,27 @@ def convert_pdf_to_pdfx( "-dDownsampleMonoImages=false", "-sProcessColorModel=DeviceCMYK", "-sColorConversionStrategy=CMYK", - f"-sOutputFile={output_path}", + f"-sOutputFile={temporary_output_path}", ] if config.icc_profile_path: cmd.append(f"-sOutputICCProfile={config.icc_profile_path}") cmd.append(source_path) logger.debug(f"Ghostscript command: {' '.join(cmd)}") - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - logger.warning( - "Ghostscript failed to convert PDF/X-1a.\n" - f"stdout: {result.stdout}\n" - f"stderr: {result.stderr}" - ) - return False - return True + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + logger.warning( + "Ghostscript failed to convert PDF/X-1a.\n" + f"stdout: {result.stdout}\n" + f"stderr: {result.stderr}" + ) + return False + os.replace(temporary_output_path, output_path) + return True + finally: + if os.path.exists(temporary_output_path): + os.unlink(temporary_output_path) @attr.s diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 35cfae608..707cb7b09 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -5,6 +5,7 @@ from concurrent.futures import ThreadPoolExecutor from contextlib import nullcontext from itertools import groupby +from pathlib import Path from queue import Queue from types import SimpleNamespace from typing import Callable, Generator @@ -34,7 +35,7 @@ aggregate_and_split_orders, ) import autofill as autofill_cli -from src.pdf_maker import PdfExporter, get_ghostscript_version +from src.pdf_maker import PdfExporter, convert_pdf_to_pdfx, get_ghostscript_version from src.processing import ImagePostProcessingConfig DEFAULT_POST_PROCESSING = ImagePostProcessingConfig(max_dpi=800, downscale_alg=constants.ImageResizeMethods.LANCZOS) @@ -570,6 +571,53 @@ def test_wiki_addendum_includes_cli_usage_updates() -> None: # region PDF/X conversion +def test_convert_pdf_to_pdfx_writes_output_atomically(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + source_path = tmp_path / "source.pdf" + output_path = tmp_path / "output.pdf" + source_path.write_bytes(b"source") + + def fake_run(cmd, capture_output=True, text=True): + output_arg = next(arg for arg in cmd if arg.startswith("-sOutputFile=")) + Path(output_arg.split("=", 1)[1]).write_bytes(b"pdfx") + return SimpleNamespace(returncode=0, stdout="", stderr="") + + monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") + monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) + + assert convert_pdf_to_pdfx( + str(source_path), + str(output_path), + autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + ) + assert output_path.read_bytes() == b"pdfx" + assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] + + +def test_convert_pdf_to_pdfx_does_not_leave_partial_output_on_failure( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + source_path = tmp_path / "source.pdf" + output_path = tmp_path / "output.pdf" + source_path.write_bytes(b"source") + output_path.write_bytes(b"previous") + + def fake_run(cmd, capture_output=True, text=True): + output_arg = next(arg for arg in cmd if arg.startswith("-sOutputFile=")) + Path(output_arg.split("=", 1)[1]).write_bytes(b"partial") + return SimpleNamespace(returncode=1, stdout="bad", stderr="worse") + + monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") + monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) + + assert not convert_pdf_to_pdfx( + str(source_path), + str(output_path), + autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + ) + assert output_path.read_bytes() == b"previous" + assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] + + def test_pdf_exporter_appends_pdfx_on_success(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: def do_nothing(_): return None From e090e9a48dc0e578a2b78979ff8480d1d56cc0b6 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 13 Mar 2026 01:55:43 -0700 Subject: [PATCH 35/76] Retry DriveThruCards driver startup --- desktop-tool/src/driver.py | 68 +++++++++++++------ .../tests/test_drivethrucards_driver.py | 56 +++++++++++++++ 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 687d05b0c..ccf315e1d 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -71,31 +71,59 @@ class AutofillDriver: # region initialisation - def initialise_driver(self) -> None: + @staticmethod + def _quit_driver_quietly(driver: Optional[WebDriver]) -> None: + if driver is None: + return try: - driver = self.browser.value( - headless=self.headless, - binary_location=self.binary_location, - user_data_dir=self.browser_profile_path, - profile_directory=self.browser_profile_name, - apply_custom_stealth=self.apply_custom_stealth, - ) # type: ignore # TODO - driver.set_window_size(1200, 900) - driver.implicitly_wait(5) - driver.get(self.starting_url) - WebDriverWait(driver, 10).until(visibility_of_element_located((By.TAG_NAME, "body"))) - logger.info( - f"Successfully initialised {bold(self.browser.name)} driver " - f"targeting {bold(self.target_site.name)}.\n" - ) - except (AttributeError, ValueError, sl_exc.WebDriverException) as e: + driver.quit() + except Exception: + pass + + def initialise_driver(self) -> None: + last_exception: Optional[Exception] = None + max_attempts = 3 + for attempt in range(1, max_attempts + 1): + driver = None + try: + driver = self.browser.value( + headless=self.headless, + binary_location=self.binary_location, + user_data_dir=self.browser_profile_path, + profile_directory=self.browser_profile_name, + apply_custom_stealth=self.apply_custom_stealth, + ) # type: ignore # TODO + driver.set_window_size(1200, 900) + driver.implicitly_wait(5) + driver.get(self.starting_url) + WebDriverWait(driver, 10).until(visibility_of_element_located((By.TAG_NAME, "body"))) + logger.info( + f"Successfully initialised {bold(self.browser.name)} driver " + f"targeting {bold(self.target_site.name)}.\n" + ) + self.driver = driver + return + except (AttributeError, ValueError, sl_exc.WebDriverException) as e: + last_exception = e + self._quit_driver_quietly(driver) + if attempt < max_attempts: + logger.warning( + f"Webdriver initialisation attempt {attempt}/{max_attempts} failed; retrying.\n" + f"{bold(e)}" + ) + time.sleep(1) + continue + raise Exception( + f"An error occurred while attempting to configure the webdriver for your specified browser. " + f"Please make sure you have installed the browser & that it is up to date:\n\n{bold(e)}" + ) + + if last_exception is not None: raise Exception( f"An error occurred while attempting to configure the webdriver for your specified browser. " - f"Please make sure you have installed the browser & that it is up to date:\n\n{bold(e)}" + f"Please make sure you have installed the browser & that it is up to date:\n\n{bold(last_exception)}" ) - self.driver = driver - def initialise_bars(self) -> None: # set the total for upload/download bars to 0 here, then change the total according to each order # as they're processed diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index e6706883e..9033f3e00 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -2,6 +2,7 @@ from types import SimpleNamespace import pytest +from selenium.common import exceptions as sl_exc from selenium.webdriver.common.by import By from src.constants import States, TargetSites @@ -109,6 +110,61 @@ def test_execute_drive_thru_cards_order_wraps_step_failures_with_context(dtc_dri dtc_driver.execute_drive_thru_cards_order(order=SimpleNamespace(name="x"), pdf_path="/tmp/x.pdf") +def test_initialise_driver_retries_when_initial_window_is_already_closed( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + created = [] + + class FailingDriver: + def __init__(self) -> None: + self.quit_called = False + + def set_window_size(self, *_args, **_kwargs) -> None: + raise sl_exc.NoSuchWindowException("target window already closed") + + def quit(self) -> None: + self.quit_called = True + + class WorkingDriver: + def __init__(self) -> None: + self.calls = [] + + def set_window_size(self, width: int, height: int) -> None: + self.calls.append(("set_window_size", width, height)) + + def implicitly_wait(self, seconds: int) -> None: + self.calls.append(("implicitly_wait", seconds)) + + def get(self, url: str) -> None: + self.calls.append(("get", url)) + + def quit(self) -> None: + self.calls.append(("quit",)) + + failing_driver = FailingDriver() + working_driver = WorkingDriver() + drivers = [failing_driver, working_driver] + + def fake_browser_factory(**_kwargs): + driver = drivers.pop(0) + created.append(driver) + return driver + + monkeypatch.setattr(dtc_driver, "browser", SimpleNamespace(value=fake_browser_factory, name="chrome")) + dtc_driver.starting_url = TargetSites.DriveThruCards.value.starting_url + monkeypatch.setattr("src.driver.WebDriverWait", lambda *_args, **_kwargs: SimpleNamespace(until=lambda _cond: True)) + monkeypatch.setattr("src.driver.time.sleep", lambda _seconds: None) + + dtc_driver.initialise_driver() + + assert created == [failing_driver, working_driver] + assert failing_driver.quit_called is True + assert dtc_driver.driver is working_driver + assert ("set_window_size", 1200, 900) in working_driver.calls + assert ("implicitly_wait", 5) in working_driver.calls + assert ("get", TargetSites.DriveThruCards.value.starting_url) in working_driver.calls + + def test_navigate_to_dtc_product_setup_uses_fast_polling_and_no_direct_fallback( monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver ) -> None: From b3644d54847ba958a4b3c3fb0cad32496bf00183 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 13 Mar 2026 01:55:54 -0700 Subject: [PATCH 36/76] Keep browser open for user handoff --- desktop-tool/autofill.py | 11 +++-- desktop-tool/tests/test_desktop_tool.py | 63 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 4ee807dca..b9aabdab0 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -521,6 +521,7 @@ def main( icc_source = "bundled default" if using_default_icc else "user-provided" logger.info(f"DriveThruCards ICC profile ({icc_source}): {bold(resolved_icc_profile)}") orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) + dtc_driver: Optional[AutofillDriver] = None for i, order in enumerate(orders, start=1): pdf_paths = get_dtc_pdf_paths_for_order( order=order, @@ -539,7 +540,7 @@ def main( "Please fix the Ghostscript conversion issue and try again." ) continue - AutofillDriver( + dtc_driver = AutofillDriver( browser=Browsers[browser], target_site=target_site, binary_location=binary_location, @@ -547,7 +548,8 @@ def main( browser_profile_name=browser_profile_name if browser_profile_path else None, apply_custom_stealth=dtc_custom_stealth, starting_url=target_site.value.starting_url, - ).execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) + ) + dtc_driver.execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") if not exportpdf: @@ -572,14 +574,15 @@ def main( combine_orders=combine_orders, ) web_server = WebServer() - AutofillDriver( + autofill_driver = AutofillDriver( browser=Browsers[browser], target_site=target_site, binary_location=binary_location, browser_profile_path=browser_profile_path, browser_profile_name=browser_profile_name if browser_profile_path else None, starting_url=web_server.server_url(), - ).execute_orders( + ) + autofill_driver.execute_orders( orders=card_orders, auto_save_threshold=auto_save_threshold if auto_save else None, post_processing_config=post_processing_config, diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 707cb7b09..bc7b74eb6 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -1,3 +1,4 @@ +import gc import os import re import textwrap @@ -511,6 +512,68 @@ def __init__(self, *args, **kwargs) -> None: assert calls["driver"] == 0 +def test_main_drive_thru_cards_keeps_driver_alive_until_user_handoff( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + icc_path = tmp_path / "test.icc" + icc_path.write_bytes(b"icc") + + state = {"finalized": 0, "executed": 0, "wait_seen": None} + + monkeypatch.setattr(autofill_cli, "configure_loggers", lambda **_kwargs: None) + monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/opt/homebrew/bin/gs") + monkeypatch.setattr(autofill_cli, "ensure_ghostscript_available", lambda **_kwargs: "/opt/homebrew/bin/gs") + monkeypatch.setattr(autofill_cli, "get_default_dtc_icc_profile", lambda: str(icc_path)) + monkeypatch.setattr( + autofill_cli.CardOrder, + "from_xmls_in_folder", + lambda working_directory: [SimpleNamespace(name="test_local")], + ) + monkeypatch.setattr( + autofill_cli, + "get_dtc_pdf_paths_for_order", + lambda **_kwargs: ["export/test_local/1.pdf", "export/test_local/1_pdfx.pdf"], + ) + + class FakeDriver: + def __init__(self, *args, **kwargs) -> None: + pass + + def execute_drive_thru_cards_order(self, order, pdf_path) -> None: + state["executed"] += 1 + + def __del__(self) -> None: + state["finalized"] += 1 + + monkeypatch.setattr(autofill_cli, "AutofillDriver", FakeDriver) + + def fake_wait_for_user_to_complete_order() -> None: + gc.collect() + state["wait_seen"] = state["finalized"] + + monkeypatch.setattr(autofill_cli, "wait_for_user_to_complete_order", fake_wait_for_user_to_complete_order) + + result = CliRunner().invoke( + autofill_cli.main, + [ + "--directory", + str(tmp_path), + "--site", + constants.TargetSites.DriveThruCards.name, + "--browser", + constants.Browsers.chrome.name, + ], + ) + + gc.collect() + + assert result.exit_code == 0 + assert state["executed"] == 1 + assert state["wait_seen"] == 0 + assert state["finalized"] == 1 + + def test_download_images_for_orders_downloads_fronts_and_backs() -> None: calls = {"fronts": 0, "backs": 0} From 0effb038da7fa89b08e4c458be8aa264312f0c4e Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Mon, 8 Jun 2026 13:07:33 -0700 Subject: [PATCH 37/76] Fix DriveThruCards login modal navigation --- desktop-tool/src/constants.py | 6 ++++-- desktop-tool/src/driver.py | 6 ++++-- desktop-tool/tests/test_drivethrucards_driver.py | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/desktop-tool/src/constants.py b/desktop-tool/src/constants.py index c50007b7b..76d6c3bfa 100644 --- a/desktop-tool/src/constants.py +++ b/desktop-tool/src/constants.py @@ -162,8 +162,10 @@ class DriveThruCardsSelectors: continue_selector: str = attr.ib(default="") # Login selectors - two step process: click login button, then click "Go to Log in" link login_button_selector: str = attr.ib(default="button[data-cy='login']") - # Target the login link in the modal, not the logout link (both can have href='/en/') - go_to_login_selector: str = attr.ib(default=".modal a[href='/en/'], .modal-content a[href='/en/']") + # Target the auth modal login button/link, not the logout link elsewhere on the page. + go_to_login_selector: str = attr.ib( + default="[data-cy='authModalBody'] button.btn-clean.anchor, .modal a[href='/en/'], .modal-content a[href='/en/']" + ) # Publisher Tools link only appears when logged in as a publisher logged_in_indicator_selector: str = attr.ib(default="a[href*='pub_tools.php']") diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index ccf315e1d..c1de75378 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -613,9 +613,11 @@ def authenticate_dtc(self) -> bool: "Please click the login button manually." ) - # Click "Go to Log in" link - poll aggressively + # Click "Go to Log in" in the auth modal - DriveThruCards has used both link and button markup. go_to_login_clicked = self.click_element_polling( - By.XPATH, "//a[contains(normalize-space(), 'Go to Log in')]", timeout=15 + By.XPATH, + "//*[@data-cy='authModalBody']//*[self::a or self::button][contains(normalize-space(), 'Go to Log in')]", + timeout=15, ) if not go_to_login_clicked: # Fallback to CSS selector diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index 9033f3e00..623d1d82f 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -73,9 +73,12 @@ def fake_click_element_polling(by: By, selector: str, timeout: int = 30) -> bool assert dtc_driver.authenticate_dtc() is True assert polling_calls[0][0] == By.XPATH + assert "authModalBody" in polling_calls[0][1] + assert "self::button" in polling_calls[0][1] assert "Go to Log in" in polling_calls[0][1] assert polling_calls[1][0] == By.CSS_SELECTOR assert polling_calls[1][1] == TargetSites.DriveThruCards.value.selectors.go_to_login_selector + assert "[data-cy='authModalBody'] button.btn-clean.anchor" in polling_calls[1][1] def test_authenticate_dtc_returns_false_on_timeout(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: From a2b4ad2f90dff466548d3f8d80187fce5fbb6e91 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 17 Jul 2026 11:51:34 -0700 Subject: [PATCH 38/76] Automate DriveThruCards publisher onboarding --- desktop-tool/src/constants.py | 12 +- desktop-tool/src/driver.py | 322 +++++++++++------- .../tests/test_drivethrucards_driver.py | 137 ++++++-- 3 files changed, 307 insertions(+), 164 deletions(-) diff --git a/desktop-tool/src/constants.py b/desktop-tool/src/constants.py index 76d6c3bfa..7b964f6d0 100644 --- a/desktop-tool/src/constants.py +++ b/desktop-tool/src/constants.py @@ -160,14 +160,13 @@ class DriveThruCardsSelectors: quantity_selector: str = attr.ib(default="") add_to_cart_selector: str = attr.ib(default="") continue_selector: str = attr.ib(default="") - # Login selectors - two step process: click login button, then click "Go to Log in" link + # Opening the login modal lets the user choose sign-in or account creation. login_button_selector: str = attr.ib(default="button[data-cy='login']") - # Target the auth modal login button/link, not the logout link elsewhere on the page. - go_to_login_selector: str = attr.ib( - default="[data-cy='authModalBody'] button.btn-clean.anchor, .modal a[href='/en/'], .modal-content a[href='/en/']" + authenticated_indicator_selector: str = attr.ib( + default="[data-cy='accountMenu'], [aria-label='Log Out'], " "a[href*='logoff'], a[href*='logout']" ) - # Publisher Tools link only appears when logged in as a publisher - logged_in_indicator_selector: str = attr.ib(default="a[href*='pub_tools.php']") + # This Publish link only appears after publisher access is enabled. + publisher_ready_selector: str = attr.ib(default="a[href*='pub_tools.php']") @attr.s @@ -259,3 +258,4 @@ class TargetSites(Enum): THREADS = 5 # shared between CardImageCollections POST_LAUNCH_HTML_FILENAME = "post-launch.html" +DTC_POST_LAUNCH_HTML_FILENAME = "dtc-post-launch.html" diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index c1de75378..a7ea8b011 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -18,8 +18,8 @@ from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support.expected_conditions import ( element_to_be_clickable, - presence_of_element_located, invisibility_of_element, + presence_of_element_located, text_to_be_present_in_element, visibility_of_element_located, ) @@ -108,8 +108,7 @@ def initialise_driver(self) -> None: self._quit_driver_quietly(driver) if attempt < max_attempts: logger.warning( - f"Webdriver initialisation attempt {attempt}/{max_attempts} failed; retrying.\n" - f"{bold(e)}" + f"Webdriver initialisation attempt {attempt}/{max_attempts} failed; retrying.\n" f"{bold(e)}" ) time.sleep(1) continue @@ -463,10 +462,11 @@ def _is_site_loaded(self) -> bool: # Temporarily disable implicit wait for fast polling self.driver.implicitly_wait(0) try: - # Check for login button or logged-in indicator + # Check for logged-out, basic-account, or publisher navigation. login_btns = self.driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) - logged_in = self.driver.find_elements(By.CSS_SELECTOR, selectors.logged_in_indicator_selector) - return bool(login_btns or logged_in) + authenticated = self.driver.find_elements(By.CSS_SELECTOR, selectors.authenticated_indicator_selector) + publisher_ready = self.driver.find_elements(By.CSS_SELECTOR, selectors.publisher_ready_selector) + return bool(login_btns or authenticated or publisher_ready) finally: # Restore implicit wait self.driver.implicitly_wait(5) @@ -498,10 +498,7 @@ def wait_for_cloudflare_challenge(self, timeout_seconds: int = 300) -> None: if self._is_cloudflare_challenge_active(): if not challenge_detected: challenge_detected = True - logger.info( - "Cloudflare challenge detected. Attempting auto-solve...\n" - "If this doesn't work, please complete the captcha manually." - ) + logger.info("Cloudflare challenge detected. Attempting automatic handling...") # Periodically try to click the Turnstile checkbox current_time = time.time() @@ -513,34 +510,53 @@ def wait_for_cloudflare_challenge(self, timeout_seconds: int = 300) -> None: time.sleep(poll_interval) # Timeout reached - logger.warning( - f"Timeout after {timeout_seconds}s waiting for site to load. " - "Attempting to continue anyway." - ) + raise TimeoutError(f"DriveThruCards did not finish loading after {timeout_seconds} seconds.") + + def is_dtc_publisher_ready(self) -> bool: + """Return whether the publisher-only Publish navigation link is visible.""" + selectors = self.target_site.value.selectors + try: + self.driver.implicitly_wait(0) + try: + return any( + element.is_displayed() + for element in self.driver.find_elements(By.CSS_SELECTOR, selectors.publisher_ready_selector) + ) + finally: + self.driver.implicitly_wait(5) + except Exception as exc: + logger.debug(f"Error checking publisher status: {exc}") + return False def is_dtc_user_authenticated(self) -> bool: - """Check if the user is logged in to DriveThruCards.""" + """Check for a basic signed-in account independently of publisher access.""" selectors = self.target_site.value.selectors try: - # Temporarily disable implicit wait for fast check self.driver.implicitly_wait(0) try: - # Look for logout button as indicator of being logged in - # This needs to be specific - only match elements that appear when logged in - logged_in_elements = self.driver.find_elements( - By.CSS_SELECTOR, selectors.logged_in_indicator_selector + authenticated_elements = self.driver.find_elements( + By.CSS_SELECTOR, selectors.authenticated_indicator_selector + ) + if any(element.is_displayed() for element in authenticated_elements): + return True + + logout_elements = self.driver.find_elements( + By.XPATH, + "//*[self::a or self::button][normalize-space()='Log Off' or normalize-space()='Log Out']", + ) + if any(element.is_displayed() for element in logout_elements): + return True + + # The Publish link also proves the user is signed in, even if the + # newer account-navigation selectors change. + return any( + element.is_displayed() + for element in self.driver.find_elements(By.CSS_SELECTOR, selectors.publisher_ready_selector) ) - # Filter to only visible elements - visible_elements = [el for el in logged_in_elements if el.is_displayed()] - if visible_elements: - logger.debug(f"Found {len(visible_elements)} visible logged-in indicator(s)") - for el in visible_elements[:3]: - logger.debug(f" Element: tag={el.tag_name}, text='{el.text}', aria-label='{el.get_attribute('aria-label')}'") - return len(visible_elements) > 0 finally: self.driver.implicitly_wait(5) - except Exception as e: - logger.debug(f"Error checking auth status: {e}") + except Exception as exc: + logger.debug(f"Error checking auth status: {exc}") return False def click_element_with_retry(self, element: Any) -> bool: @@ -596,8 +612,6 @@ def authenticate_dtc(self) -> bool: """ Handle DriveThruCards login flow. """ - selectors = self.target_site.value.selectors - if self.is_dtc_user_authenticated(): logger.info("Already logged in to DriveThruCards.") return True @@ -606,37 +620,20 @@ def authenticate_dtc(self) -> bool: logger.info("Please log in to your DriveThruCards account.") - # Now attempt actual login flow if not self._click_dtc_login_button(): logger.info( - "Could not find or click login button automatically.\n" - "Please click the login button manually." + "Could not find or click login button automatically.\n" "Please click the login button manually." ) - # Click "Go to Log in" in the auth modal - DriveThruCards has used both link and button markup. - go_to_login_clicked = self.click_element_polling( - By.XPATH, - "//*[@data-cy='authModalBody']//*[self::a or self::button][contains(normalize-space(), 'Go to Log in')]", - timeout=15, + go_to_login_xpath = ( + "//*[@data-cy='authModalBody']" + "//*[self::a or self::button][contains(normalize-space(), 'Go to Log In') " + "or contains(normalize-space(), 'Go to Log in')]" ) - if not go_to_login_clicked: - # Fallback to CSS selector - go_to_login_clicked = self.click_element_polling( - By.CSS_SELECTOR, selectors.go_to_login_selector, timeout=5 - ) + if not self.click_element_polling(By.XPATH, go_to_login_xpath, timeout=15): + logger.info("Please click 'Go to Log In' in the browser.") - if go_to_login_clicked: - logger.info("Navigated to login page.") - else: - logger.info( - "Could not find 'Go to Log in' link automatically.\n" - "Please navigate to the login page manually if needed." - ) - - logger.info( - "Please complete the login process in the browser window.\n" - "The tool will automatically continue once you're logged in." - ) + logger.info("Complete sign-in or account creation in the browser. " "The tool will continue automatically.") # Wait for user to complete login (timeout after 5 minutes) timeout_seconds = 300 @@ -647,12 +644,82 @@ def authenticate_dtc(self) -> bool: logger.info("Successfully logged in to DriveThruCards!") return True - logger.warning( - f"Login timeout after {timeout_seconds}s. " - "Please ensure you're logged in before continuing." - ) + logger.warning(f"Login timeout after {timeout_seconds}s. " "Please ensure you're logged in before continuing.") return False + def open_dtc_starting_page(self) -> None: + """Wait for the local instruction-page button, or open DriveThruCards directly.""" + if self.starting_url.startswith("http://localhost:"): + self.set_state(States.defining_order, "Awaiting browser instructions") + try: + WebDriverWait(self.driver, 300, poll_frequency=0.2).until( + lambda driver: "drivethrucards.com" in driver.current_url + ) + except sl_exc.TimeoutException as exc: + raise TimeoutError("The DriveThruCards instruction page was not continued within 5 minutes.") from exc + return + + if "drivethrucards.com" not in self.driver.current_url: + self.driver.get(self.target_site.value.starting_url) + + def ensure_dtc_publisher_account(self) -> None: + """Create publisher permissions when the authoritative Publish link is absent.""" + if self.is_dtc_publisher_ready(): + logger.info("DriveThruCards publisher account is ready.") + return + + self.set_state(States.defining_order, "Setting up publisher account") + self.driver.get("https://www.drivethrucards.com/joinchoice.php") + + non_exclusive_xpath = ( + "//*[self::a or self::button or self::input]" + "[contains(normalize-space(.), 'Create My Non-Exclusive Publisher Account') " + "or contains(@value, 'Create My Non-Exclusive Publisher Account')]" + ) + if not self.click_element_polling(By.XPATH, non_exclusive_xpath, timeout=30): + raise Exception("Could not choose the non-exclusive publisher account option.") + + publisher_name_xpath = ( + "//input[(not(@type) or @type='text') and " + "(contains(translate(@name, 'PUBLISHER', 'publisher'), 'publisher') or " + "contains(translate(@id, 'PUBLISHER', 'publisher'), 'publisher'))] | " + "//label[contains(normalize-space(.), 'Publisher Account Name')]/following::input[1]" + ) + publisher_name_input = WebDriverWait(self.driver, 30).until( + presence_of_element_located((By.XPATH, publisher_name_xpath)) + ) + publisher_name_input.clear() + publisher_name_input.send_keys("MPC Autofill Publisher") + + agreement_xpath = ( + "//input[@type='checkbox' and " + "(contains(translate(@name, 'AGREE', 'agree'), 'agree') or " + "contains(translate(@id, 'AGREE', 'agree'), 'agree'))] | " + "//label[contains(normalize-space(.), 'I Agree')]//input[@type='checkbox']" + ) + agreement_checkbox = WebDriverWait(self.driver, 30).until(element_to_be_clickable((By.XPATH, agreement_xpath))) + if not agreement_checkbox.is_selected() and not self.click_element_with_retry(agreement_checkbox): + raise Exception("Could not accept the publisher agreement.") + + setup_xpath = ( + "//*[self::a or self::button or self::input]" + "[contains(normalize-space(.), 'Set up My Publisher Account') " + "or contains(@value, 'Set up My Publisher Account')]" + ) + if not self.click_element_polling(By.XPATH, setup_xpath, timeout=30): + raise Exception("Could not submit the publisher agreement.") + + save_xpath = "//*[self::button or self::input]" "[normalize-space(.)='Save' or @value='Save']" + if not self.click_element_polling(By.XPATH, save_xpath, timeout=30): + raise Exception("Could not save the publisher payment information.") + + try: + WebDriverWait(self.driver, 30, poll_frequency=0.5).until(lambda _driver: self.is_dtc_publisher_ready()) + except sl_exc.TimeoutException as exc: + raise Exception("Publisher setup finished, but the Publish tab did not appear.") from exc + + logger.info("DriveThruCards publisher account setup is complete.") + def navigate_to_dtc_product_setup(self) -> None: """ Navigate through DriveThruCards to the product setup page. @@ -662,9 +729,7 @@ def navigate_to_dtc_product_setup(self) -> None: selectors = self.target_site.value.selectors # Step 1: Try quick click first to avoid post-login pause; otherwise navigate directly. - publisher_clicked = self.click_element_polling( - By.CSS_SELECTOR, selectors.logged_in_indicator_selector, timeout=1 - ) + publisher_clicked = self.click_element_polling(By.CSS_SELECTOR, selectors.publisher_ready_selector, timeout=1) if publisher_clicked: logger.debug("Clicked 'Publisher Tools' link.") else: @@ -673,9 +738,7 @@ def navigate_to_dtc_product_setup(self) -> None: # Step 2: Try quick click for setup link; otherwise navigate directly. self.set_state(States.defining_order, "Navigating to product setup") - setup_clicked = self.click_element_polling( - By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]", timeout=2 - ) + setup_clicked = self.click_element_polling(By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]", timeout=2) if setup_clicked: logger.debug("Clicked 'Set up a new title' link.") else: @@ -687,28 +750,26 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: Fill out the DriveThruCards product setup form (first page). """ import os - + self.set_state(States.defining_order, "Filling product form") - + # Get the placeholder cover image path (bundled with assets) assets_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets") placeholder_cover_path = os.path.join(assets_dir, "placeholder_cover.png") - + # Generate title: order name + today's date today = dt.date.today().strftime("%Y-%m-%d") title = f"{order.name or 'Order'} {today}" - + # Fill in the title field try: - title_input = WebDriverWait(self.driver, 10).until( - presence_of_element_located((By.ID, "products_name")) - ) + title_input = WebDriverWait(self.driver, 10).until(presence_of_element_located((By.ID, "products_name"))) title_input.clear() title_input.send_keys(title) logger.debug(f"Set product title to: {title}") except Exception as e: logger.warning(f"Could not fill title field: {e}") - + # Fill in the special price field with "0" try: price_input = self.driver.find_element(By.ID, "options_values_total_price") @@ -717,7 +778,7 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: logger.debug("Set special price to: 0") except Exception as e: logger.warning(f"Could not fill price field: {e}") - + # Upload the placeholder cover image try: image_input = self.driver.find_element(By.NAME, "products_image") @@ -725,7 +786,7 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: logger.debug(f"Uploaded placeholder cover image: {placeholder_cover_path}") except Exception as e: logger.warning(f"Could not upload cover image: {e}") - + # Check the two filter checkboxes try: checkbox1 = self.driver.find_element(By.ID, "filter_44550") @@ -734,7 +795,7 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: logger.debug("Checked filter_44550") except Exception as e: logger.warning(f"Could not check filter_44550: {e}") - + try: checkbox2 = self.driver.find_element(By.ID, "filter_1000138") if not checkbox2.is_selected(): @@ -742,13 +803,11 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: logger.debug("Checked filter_1000138") except Exception as e: logger.warning(f"Could not check filter_1000138: {e}") - + # Click the first submit button (Save Title Data and Continue to Preview Description) self.set_state(States.defining_order, "Submitting product form") try: - submit_button = WebDriverWait(self.driver, 10).until( - element_to_be_clickable((By.ID, "submit_id")) - ) + submit_button = WebDriverWait(self.driver, 10).until(element_to_be_clickable((By.ID, "submit_id"))) logger.debug("Clicking submit button...") self.click_element_with_retry(submit_button) logger.debug("Product form submitted successfully.") @@ -789,9 +848,7 @@ def open_dtc_upload_page(self) -> None: upload_url = url_match.group(1) self.driver.execute_script("window.location.href = arguments[0];", upload_url) # Wait for the upload page to load - WebDriverWait(self.driver, 15).until( - presence_of_element_located((By.ID, "card_type_select")) - ) + WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "card_type_select"))) logger.debug(f"Navigated to upload page: {self.driver.current_url}") else: logger.warning(f"Could not extract URL from upload button onclick: {onclick}") @@ -804,26 +861,24 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: Waits for elements to be available instead of using fixed sleeps. """ import os - + self.set_state(States.inserting_fronts, "Selecting card type") - + # Wait for and select the Euro Poker card option from the dropdown try: # Wait for dropdown to be present and interactable - WebDriverWait(self.driver, 15).until( - presence_of_element_located((By.ID, "card_type_select")) - ) + WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "card_type_select"))) # Re-fetch the element to avoid stale reference after page/tab switch card_type_dropdown = self.driver.find_element(By.ID, "card_type_select") select = Select(card_type_dropdown) - + # Find option containing "Euro Poker" (case-insensitive search) euro_poker_option_text = None for option in select.options: if "euro poker" in option.text.lower(): euro_poker_option_text = option.text break - + if euro_poker_option_text: select.select_by_visible_text(euro_poker_option_text) logger.debug(f"Selected '{euro_poker_option_text}' from dropdown.") @@ -831,41 +886,39 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: logger.warning("Could not find Euro Poker option in dropdown.") except Exception as e: logger.warning(f"Could not select card type: {e}") - + # Convert PDF path to absolute if needed if not os.path.isabs(pdf_path): pdf_path = os.path.abspath(pdf_path) - + # Verify the file exists if not os.path.exists(pdf_path): logger.error(f"PDF file not found: {pdf_path}") return - + logger.debug(f"PDF file found: {pdf_path} ({os.path.getsize(pdf_path)} bytes)") - + # Wait for the dropzone to appear after card type selection self.set_state(States.inserting_fronts, "Uploading PDF") try: # Wait for the dropzone div to be present - dropzone_div = WebDriverWait(self.driver, 15).until( - presence_of_element_located((By.ID, "uploadfiles")) - ) + dropzone_div = WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "uploadfiles"))) logger.debug("Dropzone div found.") - + # Click the dropzone to initialize Dropzone's hidden input # This should create the .dz-hidden-input element logger.debug("Clicking dropzone to initialize hidden input...") self.driver.execute_script("arguments[0].click();", dropzone_div) - + # Brief wait for the file dialog to appear, then send Escape to close it time.sleep(0.5) ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() time.sleep(0.5) - + # Find the file input and send the file - do this in a single operation # to avoid stale element references logger.debug(f"Uploading PDF: {pdf_path}") - + def find_and_use_file_input() -> bool: """Find a usable file input and send the file path to it.""" # Strategy 1: Dropzone hidden input @@ -876,7 +929,7 @@ def find_and_use_file_input() -> bool: return True except (sl_exc.NoSuchElementException, sl_exc.StaleElementReferenceException): pass - + # Strategy 2: Any file input that's not the fallback try: file_inputs = self.driver.find_elements(By.CSS_SELECTOR, "input[type='file']") @@ -893,21 +946,19 @@ def find_and_use_file_input() -> bool: continue except Exception as e: logger.debug(f"Error with file inputs: {e}") - + # Strategy 3: Use the fallback input try: logger.debug("Using fallback file input...") - self.driver.execute_script( - "document.getElementById('dropzoneFallback').style.display = 'block';" - ) + self.driver.execute_script("document.getElementById('dropzoneFallback').style.display = 'block';") fi = self.driver.find_element(By.CSS_SELECTOR, "#dropzoneFallback input[type='file']") fi.send_keys(pdf_path) return True except Exception as e: logger.debug(f"Fallback input failed: {e}") - + return False - + # Try up to 3 times to handle any remaining stale element issues file_sent = False for attempt in range(3): @@ -916,19 +967,21 @@ def find_and_use_file_input() -> bool: break logger.debug(f"Attempt {attempt + 1} failed, retrying...") time.sleep(0.5) - + if not file_sent: logger.warning("Could not send file to any input element.") return - + # Trigger change event on all file inputs (one of them has our file) - self.driver.execute_script(""" + self.driver.execute_script( + """ document.querySelectorAll('input[type="file"]').forEach(function(input) { input.dispatchEvent(new Event('change', { bubbles: true })); }); - """) + """ + ) logger.debug("Dispatched change event on file inputs.") - + # Wait for the upload button to become enabled (Dropzone enables it when files are queued) try: WebDriverWait(self.driver, 10).until( @@ -937,9 +990,10 @@ def find_and_use_file_input() -> bool: logger.debug("Upload button is now enabled.") except sl_exc.TimeoutException: logger.debug("Button didn't become enabled automatically, forcing it.") - + # Click the upload button using JavaScript - upload_clicked = self.driver.execute_script(""" + upload_clicked = self.driver.execute_script( + """ var btn = document.getElementById('dropzoneButton'); if (btn) { btn.disabled = false; @@ -948,41 +1002,47 @@ def find_and_use_file_input() -> bool: return true; } return false; - """) + """ + ) if upload_clicked: logger.debug("Clicked 'Begin Card File Upload' button via JavaScript.") else: logger.warning("Could not find upload button.") - + # Also try to trigger Dropzone's processQueue as a backup try: - self.driver.execute_script(""" + self.driver.execute_script( + """ var dz = Dropzone.forElement('#uploadfiles'); if (dz && dz.files && dz.files.length > 0) { dz.processQueue(); } - """) + """ + ) logger.debug("Triggered Dropzone processQueue.") except Exception as e: logger.debug(f"Could not trigger processQueue: {e}") - + # Wait for upload to complete - look for success message try: WebDriverWait(self.driver, 120).until( - lambda d: "successfully uploaded" in ( + lambda d: "successfully uploaded" + in ( d.find_element(By.ID, "status_messages").text.lower() - if d.find_elements(By.ID, "status_messages") else "" + if d.find_elements(By.ID, "status_messages") + else "" ) ) logger.debug("PDF upload completed to DriveThruCards.") except sl_exc.TimeoutException: logger.warning("Could not confirm upload completion. Please verify manually.") - + # Wait for the continue button to become active. # The button starts with an onclick handler that shows an error and returns false. # After upload validation, the page JS replaces this handler to enable navigation. # We detect activation by checking that the onclick no longer contains "return false". try: + def continue_button_is_active(d: Any) -> Any: btn = d.find_element(By.ID, "continue_button") onclick = btn.get_attribute("onclick") or "" @@ -996,17 +1056,15 @@ def continue_button_is_active(d: Any) -> Any: logger.debug("Clicked 'Click here after uploading your files' button.") except sl_exc.TimeoutException: logger.warning("Continue button did not activate. Please click it manually.") - + # Click the "Complete Setup" button on the next page try: - complete_button = WebDriverWait(self.driver, 30).until( - element_to_be_clickable((By.ID, "submit_id")) - ) + complete_button = WebDriverWait(self.driver, 30).until(element_to_be_clickable((By.ID, "submit_id"))) self.driver.execute_script("arguments[0].click();", complete_button) logger.debug("Clicked 'Complete Setup' button.") except sl_exc.TimeoutException: logger.warning("Could not find 'Complete Setup' button. Please click it manually.") - + # Click the "buy now" link to start placing the order. # This link has target="_blank", so navigate directly to avoid new-tab issues. try: @@ -1028,7 +1086,6 @@ def continue_button_is_active(d: Any) -> Any: def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> None: t = time.time() self.set_state(States.defining_order, "Opening DriveThruCards") - self.driver.get(self.target_site.value.starting_url) def run_step(step_name: str, func: Any, *args: Any, **kwargs: Any) -> Any: try: @@ -1036,13 +1093,14 @@ def run_step(step_name: str, func: Any, *args: Any, **kwargs: Any) -> Any: except Exception as exc: raise Exception(f"DriveThruCards step '{step_name}' failed: {exc}") from exc + run_step("open_dtc_starting_page", self.open_dtc_starting_page) run_step("wait_for_cloudflare_challenge", self.wait_for_cloudflare_challenge) login_completed = run_step("authenticate_dtc", self.authenticate_dtc) if not login_completed: raise Exception( - "DriveThruCards login was not completed before timeout. " - "Please log in and re-run the command." + "DriveThruCards login was not completed before timeout. " "Please log in and re-run the command." ) + run_step("ensure_dtc_publisher_account", self.ensure_dtc_publisher_account) run_step("navigate_to_dtc_product_setup", self.navigate_to_dtc_product_setup) run_step("fill_dtc_product_form", self.fill_dtc_product_form, order) run_step("submit_dtc_description_page", self.submit_dtc_description_page) diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index 623d1d82f..e598ec9d9 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -5,7 +5,7 @@ from selenium.common import exceptions as sl_exc from selenium.webdriver.common.by import By -from src.constants import States, TargetSites +from src.constants import TargetSites from src.driver import AutofillDriver @@ -19,10 +19,12 @@ def dtc_driver(monkeypatch: pytest.MonkeyPatch) -> AutofillDriver: def test_execute_drive_thru_cards_order_runs_expected_sequence(dtc_driver: AutofillDriver) -> None: calls = [] - dtc_driver.driver = SimpleNamespace(get=lambda url: calls.append(("get", url))) + dtc_driver.driver = SimpleNamespace() + dtc_driver.open_dtc_starting_page = lambda: calls.append(("open_dtc_starting_page",)) dtc_driver.wait_for_cloudflare_challenge = lambda: calls.append(("wait_for_cloudflare_challenge",)) dtc_driver.authenticate_dtc = lambda: calls.append(("authenticate_dtc",)) or True + dtc_driver.ensure_dtc_publisher_account = lambda: calls.append(("ensure_dtc_publisher_account",)) dtc_driver.navigate_to_dtc_product_setup = lambda: calls.append(("navigate_to_dtc_product_setup",)) dtc_driver.fill_dtc_product_form = lambda order: calls.append(("fill_dtc_product_form", order.name)) dtc_driver.submit_dtc_description_page = lambda: calls.append(("submit_dtc_description_page",)) @@ -33,9 +35,10 @@ def test_execute_drive_thru_cards_order_runs_expected_sequence(dtc_driver: Autof dtc_driver.execute_drive_thru_cards_order(order=order, pdf_path="/tmp/order.pdf") assert calls == [ - ("get", TargetSites.DriveThruCards.value.starting_url), + ("open_dtc_starting_page",), ("wait_for_cloudflare_challenge",), ("authenticate_dtc",), + ("ensure_dtc_publisher_account",), ("navigate_to_dtc_product_setup",), ("fill_dtc_product_form", "My Order"), ("submit_dtc_description_page",), @@ -54,31 +57,29 @@ def test_authenticate_dtc_returns_immediately_when_already_logged_in(dtc_driver: assert dtc_driver.authenticate_dtc() is True -def test_authenticate_dtc_uses_xpath_then_css_fallback(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: +def test_authenticated_selector_matches_current_account_menu() -> None: + selector = TargetSites.DriveThruCards.value.selectors.authenticated_indicator_selector + + assert "[data-cy='accountMenu']" in selector + assert "[aria-label='Log Out']" in selector + + +def test_authenticate_dtc_opens_login_pane_then_waits( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: auth_checks = iter([False, False, True]) dtc_driver.is_dtc_user_authenticated = lambda: next(auth_checks) dtc_driver._click_dtc_login_button = lambda: True - polling_calls = [] - - def fake_click_element_polling(by: By, selector: str, timeout: int = 30) -> bool: - polling_calls.append((by, selector, timeout)) - if by == By.XPATH: - return False - return True - - dtc_driver.click_element_polling = fake_click_element_polling + dtc_driver.click_element_polling = ( + lambda by, selector, timeout=30: polling_calls.append((by, selector, timeout)) or True + ) monkeypatch.setattr(time, "sleep", lambda _seconds: None) assert dtc_driver.authenticate_dtc() is True - assert polling_calls[0][0] == By.XPATH - assert "authModalBody" in polling_calls[0][1] - assert "self::button" in polling_calls[0][1] - assert "Go to Log in" in polling_calls[0][1] - assert polling_calls[1][0] == By.CSS_SELECTOR - assert polling_calls[1][1] == TargetSites.DriveThruCards.value.selectors.go_to_login_selector - assert "[data-cy='authModalBody'] button.btn-clean.anchor" in polling_calls[1][1] + assert "Go to Log In" in polling_calls[0][1] + assert polling_calls[0][2] == 15 def test_authenticate_dtc_returns_false_on_timeout(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: @@ -94,7 +95,8 @@ def test_authenticate_dtc_returns_false_on_timeout(monkeypatch: pytest.MonkeyPat def test_execute_drive_thru_cards_order_raises_when_login_not_completed(dtc_driver: AutofillDriver) -> None: - dtc_driver.driver = SimpleNamespace(get=lambda _url: None) + dtc_driver.driver = SimpleNamespace() + dtc_driver.open_dtc_starting_page = lambda: None dtc_driver.wait_for_cloudflare_challenge = lambda: None dtc_driver.authenticate_dtc = lambda: False dtc_driver.navigate_to_dtc_product_setup = lambda: (_ for _ in ()).throw(AssertionError("should not continue")) @@ -104,9 +106,11 @@ def test_execute_drive_thru_cards_order_raises_when_login_not_completed(dtc_driv def test_execute_drive_thru_cards_order_wraps_step_failures_with_context(dtc_driver: AutofillDriver) -> None: - dtc_driver.driver = SimpleNamespace(get=lambda _url: None) + dtc_driver.driver = SimpleNamespace() + dtc_driver.open_dtc_starting_page = lambda: None dtc_driver.wait_for_cloudflare_challenge = lambda: None dtc_driver.authenticate_dtc = lambda: True + dtc_driver.ensure_dtc_publisher_account = lambda: None dtc_driver.navigate_to_dtc_product_setup = lambda: (_ for _ in ()).throw(RuntimeError("new UI mismatch")) with pytest.raises(Exception, match="step 'navigate_to_dtc_product_setup' failed"): @@ -187,7 +191,7 @@ def fake_poll(by, selector, timeout=30): dtc_driver.navigate_to_dtc_product_setup() assert poll_calls == [ - (By.CSS_SELECTOR, TargetSites.DriveThruCards.value.selectors.logged_in_indicator_selector, 1), + (By.CSS_SELECTOR, TargetSites.DriveThruCards.value.selectors.publisher_ready_selector, 1), (By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]", 2), ] assert "Clicked 'Publisher Tools' link." in debug_logs @@ -217,7 +221,83 @@ def test_wait_for_cloudflare_challenge_returns_when_site_loaded(dtc_driver: Auto dtc_driver.wait_for_cloudflare_challenge(timeout_seconds=1) -def test_open_dtc_upload_page_extracts_window_open_url(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: +def test_wait_for_cloudflare_challenge_raises_on_timeout( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + dtc_driver._is_site_loaded = lambda: False + dtc_driver._is_cloudflare_challenge_active = lambda: False + time_values = iter([0.0, 2.0]) + monkeypatch.setattr(time, "time", lambda: next(time_values)) + + with pytest.raises(TimeoutError, match="did not finish loading"): + dtc_driver.wait_for_cloudflare_challenge(timeout_seconds=1) + + +def test_ensure_dtc_publisher_account_automates_wizard( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + calls = [] + + class PublisherNameInput: + value = "" + + def clear(self) -> None: + self.value = "" + + def send_keys(self, value: str) -> None: + self.value = value + + class AgreementCheckbox: + clicked = False + + def is_selected(self) -> bool: + return False + + publisher_name = PublisherNameInput() + agreement = AgreementCheckbox() + ready_checks = iter([False, True]) + dtc_driver.is_dtc_publisher_ready = lambda: next(ready_checks) + dtc_driver.driver = SimpleNamespace(get=lambda url: calls.append(("get", url))) + + def fake_click(by: By, selector: str, timeout: int = 30) -> bool: + calls.append(("click", by, selector, timeout)) + return True + + dtc_driver.click_element_polling = fake_click + + def fake_click_with_retry(element) -> bool: + element.clicked = True + return True + + dtc_driver.click_element_with_retry = fake_click_with_retry + + class FakeWait: + count = 0 + + def __init__(self, *_args, **_kwargs) -> None: + pass + + def until(self, condition): + FakeWait.count += 1 + if FakeWait.count == 1: + return publisher_name + if FakeWait.count == 2: + return agreement + return condition(dtc_driver.driver) + + monkeypatch.setattr("src.driver.WebDriverWait", FakeWait) + + dtc_driver.ensure_dtc_publisher_account() + + assert calls[0] == ("get", "https://www.drivethrucards.com/joinchoice.php") + assert len([call for call in calls if call[0] == "click"]) == 3 + assert publisher_name.value == "MPC Autofill Publisher" + assert agreement.clicked is True + + +def test_open_dtc_upload_page_extracts_window_open_url( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: script_calls = [] class UploadButton: @@ -247,7 +327,10 @@ def until(self, _condition): dtc_driver.open_dtc_upload_page() assert script_calls == [ - ("window.location.href = arguments[0];", "https://tools.drivethrucards.com/pub_upload_podcard_files.php?products_id=123") + ( + "window.location.href = arguments[0];", + "https://tools.drivethrucards.com/pub_upload_podcard_files.php?products_id=123", + ) ] @@ -265,7 +348,9 @@ def implicitly_wait(self, value: int) -> None: def find_elements(self, by: By, selector: str): if by == By.CSS_SELECTOR and selector == selectors.login_button_selector: return [object()] - if by == By.CSS_SELECTOR and selector == selectors.logged_in_indicator_selector: + if by == By.CSS_SELECTOR and selector == selectors.authenticated_indicator_selector: + return [] + if by == By.CSS_SELECTOR and selector == selectors.publisher_ready_selector: return [] return [] From 6bc18b67d02d6dd466541027f92aad40dedfa027 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 17 Jul 2026 11:51:49 -0700 Subject: [PATCH 39/76] Add DriveThruCards sign-in guidance --- desktop-tool/autofill.py | 81 +++++++++++++++---------- desktop-tool/dtc-post-launch.html | 57 +++++++++++++++++ desktop-tool/src/web_server.py | 5 +- desktop-tool/tests/test_desktop_tool.py | 65 +++++++++++++------- 4 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 desktop-tool/dtc-post-launch.html diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index b9aabdab0..f8a283052 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -1,6 +1,7 @@ # nuitka-project: --mode=onefile # nuitka-project: --include-data-files=client_secrets.json=client_secrets.json # nuitka-project: --include-data-files=post-launch.html=post-launch.html +# nuitka-project: --include-data-files=dtc-post-launch.html=dtc-post-launch.html # nuitka-project: --include-data-dir=assets=assets # nuitka-project: --include-data-files=assets/icc/USWebCoatedSWOP.icc=assets/icc/USWebCoatedSWOP.icc # nuitka-project: --include-data-files=assets/icc/Adobe-Color-Profile-EULA.pdf=assets/icc/Adobe-Color-Profile-EULA.pdf @@ -33,14 +34,29 @@ import enlighten from wakepy import keepawake -from src.constants import THREADS, Browsers, ImageResizeMethods, TargetSites +from src.constants import ( + DTC_POST_LAUNCH_HTML_FILENAME, + THREADS, + Browsers, + ImageResizeMethods, + TargetSites, +) from src.driver import AutofillDriver from src.exc import ValidationException from src.formatting import bold -from src.io import DEFAULT_WORKING_DIRECTORY, create_image_directory_if_not_exists, get_image_directory +from src.io import ( + DEFAULT_WORKING_DIRECTORY, + create_image_directory_if_not_exists, + get_image_directory, +) from src.logging import configure_loggers, logger from src.order import CardOrder, aggregate_and_split_orders -from src.pdf_maker import PdfExporter, PdfXConversionConfig, get_ghostscript_path, get_ghostscript_version +from src.pdf_maker import ( + PdfExporter, + PdfXConversionConfig, + get_ghostscript_path, + get_ghostscript_version, +) from src.processing import ImagePostProcessingConfig from src.web_server import WebServer @@ -91,9 +107,7 @@ def ensure_ghostscript_available(ask_for_install_confirmation: bool = True) -> s logger.info(f"Ghostscript detected at {bold(gs_path)}") return gs_path - logger.info( - "DriveThruCards export requires Ghostscript for PDF/X-1a compliance." - ) + logger.info("DriveThruCards export requires Ghostscript for PDF/X-1a compliance.") should_install = True if ask_for_install_confirmation: @@ -123,9 +137,7 @@ def ensure_ghostscript_available(ask_for_install_confirmation: bool = True) -> s logger.warning("Ghostscript installation via winget failed.") else: if shutil.which("sudo") is None: - logger.info( - "sudo not found. Please install Ghostscript with your package manager manually." - ) + logger.info("sudo not found. Please install Ghostscript with your package manager manually.") elif shutil.which("apt") is not None: logger.info("Installing Ghostscript via apt...") result = subprocess.run(["sudo", "apt", "install", "-y", "ghostscript"], check=False) @@ -204,9 +216,7 @@ def maybe_reuse_existing_pdfs( return None if require_pdfx and not any(path.endswith("_pdfx.pdf") for path in existing_pdf_paths): - logger.info( - "Existing PDF files were found, but no PDF/X-1a output was found. Recreating PDF export." - ) + logger.info("Existing PDF files were found, but no PDF/X-1a output was found. Recreating PDF export.") return None if existing_pdfs_are_stale(existing_pdf_paths=existing_pdf_paths, cards_directory=cards_directory): @@ -225,10 +235,7 @@ def download_images_for_orders( orders: list[CardOrder], post_processing_config: Optional[ImagePostProcessingConfig], ) -> None: - total_images = sum( - len(order.fronts.cards_by_id) + len(order.backs.cards_by_id) - for order in orders - ) + total_images = sum(len(order.fronts.cards_by_id) + len(order.backs.cards_by_id) for order in orders) manager = enlighten.get_manager() download_bar = manager.counter(total=total_images, desc="Images Downloaded", position=1, autorefresh=True) with ThreadPoolExecutor(max_workers=THREADS) as pool: @@ -272,9 +279,7 @@ def get_dtc_pdf_paths_for_order( def get_site_picker_choices() -> list[str]: site_names = [site.name for site in TargetSites] # Keep DriveThruCards available but place it last in the picker list. - return [name for name in site_names if name != TargetSites.DriveThruCards.name] + [ - TargetSites.DriveThruCards.name - ] + return [name for name in site_names if name != TargetSites.DriveThruCards.name] + [TargetSites.DriveThruCards.name] @click.command(context_settings={"show_default": True}) @@ -319,6 +324,12 @@ def get_site_picker_choices() -> list[str]: ), is_flag=True, ) +@click.option( + "--skip-dtc-instructions", + default=False, + help="Open DriveThruCards immediately without showing the browser instruction page.", + is_flag=True, +) @click.option( "--site", prompt=prompt_if_no_arguments("Which site should the tool auto-fill your project into?"), @@ -442,6 +453,7 @@ def main( browser_profile_path: Optional[str], browser_profile_name: str, dtc_custom_stealth: bool, + skip_dtc_instructions: bool, site: str, exportpdf: bool, download_images_only: bool, @@ -522,6 +534,7 @@ def main( logger.info(f"DriveThruCards ICC profile ({icc_source}): {bold(resolved_icc_profile)}") orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) dtc_driver: Optional[AutofillDriver] = None + dtc_web_server: Optional[WebServer] = None for i, order in enumerate(orders, start=1): pdf_paths = get_dtc_pdf_paths_for_order( order=order, @@ -540,15 +553,23 @@ def main( "Please fix the Ghostscript conversion issue and try again." ) continue - dtc_driver = AutofillDriver( - browser=Browsers[browser], - target_site=target_site, - binary_location=binary_location, - browser_profile_path=browser_profile_path, - browser_profile_name=browser_profile_name if browser_profile_path else None, - apply_custom_stealth=dtc_custom_stealth, - starting_url=target_site.value.starting_url, - ) + if dtc_driver is None: + starting_url = target_site.value.starting_url + if not skip_dtc_instructions: + logger.info( + "DriveThruCards setup will open in your browser. " "Follow the instructions there." + ) + dtc_web_server = WebServer(DTC_POST_LAUNCH_HTML_FILENAME) + starting_url = dtc_web_server.server_url() + dtc_driver = AutofillDriver( + browser=Browsers[browser], + target_site=target_site, + binary_location=binary_location, + browser_profile_path=browser_profile_path, + browser_profile_name=browser_profile_name if browser_profile_path else None, + apply_custom_stealth=dtc_custom_stealth, + starting_url=starting_url, + ) dtc_driver.execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") @@ -564,9 +585,7 @@ def main( ) is None ): - PdfExporter(order=order).execute( - post_processing_config=post_processing_config - ) + PdfExporter(order=order).execute(post_processing_config=post_processing_config) else: card_orders = aggregate_and_split_orders( orders=CardOrder.from_xmls_in_folder(working_directory=working_directory), diff --git a/desktop-tool/dtc-post-launch.html b/desktop-tool/dtc-post-launch.html new file mode 100644 index 000000000..83a93a44d --- /dev/null +++ b/desktop-tool/dtc-post-launch.html @@ -0,0 +1,57 @@ + + + + + + MPC Autofill — DriveThruCards + + + +
+

Sign in to DriveThruCards

+

Before continuing: Turn off any ad blockers; they may disrupt the login process.

+

+ Next, sign in to DriveThruCards.
+ No account? Click Get Started Here! on the next page to create one or sign in with Google or Facebook. +

+

MPC Autofill will continue automatically when you've logged in.

+
Continue to DriveThruCards +

To skip this page next time, use --skip-dtc-instructions.

+
+ + diff --git a/desktop-tool/src/web_server.py b/desktop-tool/src/web_server.py index 83d20511d..b40e2b1b3 100644 --- a/desktop-tool/src/web_server.py +++ b/desktop-tool/src/web_server.py @@ -17,7 +17,7 @@ def do_GET(self) -> None: self.send_response(200) self.send_header("Content-Type", "text/html") self.end_headers() - self.wfile.write(Path(__file__).joinpath("../..").joinpath(POST_LAUNCH_HTML_FILENAME).resolve().read_bytes()) + self.wfile.write(getattr(self.server, "html_path").read_bytes()) def log_request(self, code: Union[int, str] = "-", size: Union[int, str] = "-") -> None: # Silence the request log. @@ -25,8 +25,9 @@ def log_request(self, code: Union[int, str] = "-", size: Union[int, str] = "-") class WebServer: - def __init__(self) -> None: + def __init__(self, html_filename: str = POST_LAUNCH_HTML_FILENAME) -> None: self._server = server.ThreadingHTTPServer(("", 0), _Handler) + self._server.html_path = Path(__file__).joinpath("../..").joinpath(html_filename).resolve() # type: ignore[attr-defined] self._thread = threading.Thread(target=self._server.serve_forever) self._thread.start() logger.info(f"Web server started on {self.server_url()}") diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index bc7b74eb6..54b1e853a 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -12,6 +12,7 @@ from typing import Callable, Generator from xml.etree import ElementTree +import autofill as autofill_cli import pytest from click.testing import CliRunner from enlighten import Counter @@ -35,7 +36,6 @@ Details, aggregate_and_split_orders, ) -import autofill as autofill_cli from src.pdf_maker import PdfExporter, convert_pdf_to_pdfx, get_ghostscript_version from src.processing import ImagePostProcessingConfig @@ -101,9 +101,7 @@ def fake_run(*_args, **_kwargs): assert get_ghostscript_version("gs") == "10.02.1" -def test_ensure_ghostscript_available_prompts_until_found( - monkeypatch: pytest.MonkeyPatch, input_enter -) -> None: +def test_ensure_ghostscript_available_prompts_until_found(monkeypatch: pytest.MonkeyPatch, input_enter) -> None: paths = [None, "/usr/local/bin/gs"] called = {"version": 0} @@ -148,9 +146,7 @@ def fake_run(cmd, check=False): resolved = autofill_cli.ensure_ghostscript_available() assert resolved == "C:\\Program Files\\gs\\gswin64c.exe" - assert install_calls == [ - ["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"] - ] + assert install_calls == [["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"]] def test_ensure_ghostscript_available_installs_with_apt_on_linux( @@ -449,6 +445,7 @@ def test_cli_help_documents_new_flags_and_stealth_guidance() -> None: assert "--browser-profile-path" in result.output assert "--browser-profile-name" in result.output assert "--dtc-custom-stealth" in result.output + assert "--skip-dtc-instructions" in result.output assert "last resort" in result.output assert "detailed Selenium step-by-step logs" in result.output @@ -512,13 +509,24 @@ def __init__(self, *args, **kwargs) -> None: assert calls["driver"] == 0 +@pytest.mark.parametrize( + ("skip_instructions", "expected_starting_url", "expected_server_count"), + [ + (False, "http://localhost:1234/", 1), + (True, constants.TargetSites.DriveThruCards.value.starting_url, 0), + ], +) def test_main_drive_thru_cards_keeps_driver_alive_until_user_handoff( - monkeypatch: pytest.MonkeyPatch, tmp_path + monkeypatch: pytest.MonkeyPatch, + tmp_path, + skip_instructions: bool, + expected_starting_url: str, + expected_server_count: int, ) -> None: icc_path = tmp_path / "test.icc" icc_path.write_bytes(b"icc") - state = {"finalized": 0, "executed": 0, "wait_seen": None} + state = {"finalized": 0, "executed": 0, "wait_seen": None, "servers": 0, "starting_url": None} monkeypatch.setattr(autofill_cli, "configure_loggers", lambda **_kwargs: None) monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) @@ -536,9 +544,19 @@ def test_main_drive_thru_cards_keeps_driver_alive_until_user_handoff( lambda **_kwargs: ["export/test_local/1.pdf", "export/test_local/1_pdfx.pdf"], ) + class FakeWebServer: + def __init__(self, html_filename: str) -> None: + assert html_filename == constants.DTC_POST_LAUNCH_HTML_FILENAME + state["servers"] += 1 + + def server_url(self) -> str: + return "http://localhost:1234/" + + monkeypatch.setattr(autofill_cli, "WebServer", FakeWebServer) + class FakeDriver: def __init__(self, *args, **kwargs) -> None: - pass + state["starting_url"] = kwargs["starting_url"] def execute_drive_thru_cards_order(self, order, pdf_path) -> None: state["executed"] += 1 @@ -554,17 +572,18 @@ def fake_wait_for_user_to_complete_order() -> None: monkeypatch.setattr(autofill_cli, "wait_for_user_to_complete_order", fake_wait_for_user_to_complete_order) - result = CliRunner().invoke( - autofill_cli.main, - [ - "--directory", - str(tmp_path), - "--site", - constants.TargetSites.DriveThruCards.name, - "--browser", - constants.Browsers.chrome.name, - ], - ) + args = [ + "--directory", + str(tmp_path), + "--site", + constants.TargetSites.DriveThruCards.name, + "--browser", + constants.Browsers.chrome.name, + ] + if skip_instructions: + args.append("--skip-dtc-instructions") + + result = CliRunner().invoke(autofill_cli.main, args) gc.collect() @@ -572,6 +591,8 @@ def fake_wait_for_user_to_complete_order() -> None: assert state["executed"] == 1 assert state["wait_seen"] == 0 assert state["finalized"] == 1 + assert state["servers"] == expected_server_count + assert state["starting_url"] == expected_starting_url def test_download_images_for_orders_downloads_fronts_and_backs() -> None: @@ -617,7 +638,7 @@ def test_wiki_addendum_includes_cli_usage_updates() -> None: with open(wiki_addendum_path, "r", encoding="utf-8") as f: wiki = f.read() assert "contains only the incremental wiki updates" in wiki - assert "Insert Under Existing \"Useful CLI Flags\" Section" in wiki + assert 'Insert Under Existing "Useful CLI Flags" Section' in wiki assert "--skip-pdf-if-exists" in wiki assert "--download-images-only" in wiki assert "--browser-profile-path" in wiki From bd97c9d88bc4faeebc861dfdd2c31574aeb723ae Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 17 Jul 2026 11:51:57 -0700 Subject: [PATCH 40/76] Document DriveThruCards publisher workflow --- desktop-tool/TODO.md | 117 +++++++ .../drivethrucards-publisher-workflow.html | 291 ++++++++++++++++++ 2 files changed, 408 insertions(+) create mode 100644 desktop-tool/TODO.md create mode 100644 desktop-tool/docs/drivethrucards-publisher-workflow.html diff --git a/desktop-tool/TODO.md b/desktop-tool/TODO.md new file mode 100644 index 000000000..a0d7906bc --- /dev/null +++ b/desktop-tool/TODO.md @@ -0,0 +1,117 @@ +# Desktop Tool - TODO + +## Session 1: Platform Validation + +- [x] Test Ghostscript install step on Windows +- [x] Test Ghostscript install step on Linux + +### Context +- **Key files**: `tests/test_desktop_tool.py`, `autofill.py` +- **Findings**: Added mocked platform tests for `ensure_ghostscript_available()` validating Windows `winget` and Linux `apt` install command paths, plus updated prompt-loop test stability. +- **Next steps**: Validate real installs manually on native Windows/Linux environments during release testing. + +## Session 2: Consistent PDF Creation + +- [x] (User written, not by AI) When creating the PDF again after changing the card images in the cards folder, sometimes the PDF does not update. Confirm if this is some issue regarding accessing the same images saved in memory. Updating the images and then recreating the PDF should reflect those changes. +- [x] (User written, not by AI) Add CLI command to skip the creation of a new PDF if one already exists. Add UI in the CLI to suggest recreating the PDF, even with this flag selected, if there are images in the cards folder newer than the creation date of the existing PDF. + +### Context +- **Key files**: `src/pdf_maker.py`, `tests/test_desktop_tool.py` +- **Findings**: `fpdf` caches raster images by identifier; using file path identifiers can reuse stale image data. `PdfExporter.add_image` now feeds raw bytes for standard exports so cache keys track file content. Added regression test `test_pdf_exporter_add_image_uses_image_bytes`. Added CLI flag `--skip-pdf-if-exists` and helper logic to reuse existing export PDFs, prompt for recreation when `cards/` files are newer, and force regeneration for DriveThruCards if `_pdfx.pdf` output is missing. +- **Next steps**: Session 2 is complete. Continue with Session 3 Selenium login/bot-detection tasks. + +## Session 3: Selenium Robustness + +### Context +- **Key files**: `src/driver.py`, `src/webdrivers.py`, `src/constants.py` +- **Findings**: + - Switched to `undetected-chromedriver` library for Cloudflare bypass (standard Selenium stealth measures were insufficient) + - Added aggressive polling (500ms intervals) for Cloudflare challenge detection + - Added auto-click attempt for Turnstile checkbox in iframe (fallback if challenge appears) + - Fixed login modal selector to target correct link (`.modal a[href='/en/']`) + - Removed `set_network_conditions` calls which were a potential detection vector + - `undetected-chromedriver` requires `setuptools` on Python 3.13+ (distutils removed) + - `undetected-chromedriver` auto-detection of Chrome version is broken; implemented `_detect_chrome_version()` to query browser directly + - Cloudflare bypass now works + - Added stealth JavaScript to `webdrivers.py` (`_apply_stealth_scripts`) to hide automation traces - this worked initially but login is now blocked again + - Fixed slow response after Cloudflare by disabling implicit wait (5s) during polling methods + - Removed unused visibility/focus workaround methods that caused jarring UX (minimize/restore window) + - Added support for launching Chromium browsers with an existing user data directory/profile (`--browser-profile-path`, `--browser-profile-name`) so saved cookies/password managers can be reused for DriveThruCards login + - Added tests to verify Chromium profile options are passed to `undetected-chromedriver` / Brave webdriver setup + - Made custom stealth JavaScript injection opt-in (`--dtc-custom-stealth`) instead of always-on to reduce bot-detection surface area when using `undetected-chromedriver` + - Added tests to verify custom stealth is disabled by default and only applied when explicitly enabled + - Refactored `execute_drive_thru_cards_order` into explicit guarded steps with contextual failures and removed dead legacy upload path code + - Added tests verifying DriveThruCards execution halts on login timeout and reports failing step names for easier debugging against UI changes + - Moved noisy DriveThruCards Selenium step logs from `info` to `debug`, so average users get cleaner output while detailed traces remain available via `--log-level DEBUG` + - Removed post-login pause by replacing longer `WebDriverWait` checks in publisher/setup navigation with fast polling and immediate direct-navigation fallback + - Added browser-first sign-in instructions with `--skip-dtc-instructions` for experienced users + - Separated ordinary account sign-in detection from publisher readiness detection + - Automated non-exclusive publisher setup, including agreement acceptance and payment-default save; the visible Publish tab is the authoritative success check +- **Validation**: A full CLI run with a new Google-linked account and `/Users/bradley/Documents/dtc-test` created publisher access, uploaded the card PDF, and reached DriveThruCards checkout without purchasing. +- **Next steps**: Continue live DriveThruCards validation runs to tune selectors/timeouts as the site evolves. + +### Testing Notes + +**Important:** Always run the desktop tool itself for testing—do not spin up a separate browser instance. The first step in the Selenium automation chain is bypassing a Cloudflare captcha, which requires `undetected-chromedriver` and cannot be easily done with fresh browser instances. + +**Test command:** +```bash +.venv/bin/python autofill.py --site drivethrucards --directory /Users/bradley/Documents/dtc-test +``` + +Always run this command yourself (via Bash tool) rather than prompting the user to run it. This allows you to capture output directly and debug issues more easily. + +- [x] Improve Cloudflare CAPTCHA solver reliability +- [x] Fix slow response after Cloudflare CAPTCHA (disabled implicit wait during polling) +- [x] Fix login bot detection - stealth JavaScript approach inconsistent, site shows "Unable to log in" +- [x] Investigate using existing Chrome profile for cached credentials/password managers +- [x] Potentially overhaul Selenium automation due to new DriveThruCards UI +- [x] Automate creating a DriveThruCards non-exclusive publisher account when the Publish tab is absent +- [x] Reduce verbose Selenium step-by-step CLI output by default; keep detailed messages behind global log-level controls +- [x] Remove post-login delay before navigating to DriveThruCards publisher tools/setup flow + +## Session 4: Test Coverage + +- [x] Add testing coverage for all DriveThruCards unique actions + +### Context +- **Key files**: `tests/test_drivethrucards_driver.py`, `src/driver.py` +- **Findings**: Added focused unit coverage for DriveThruCards-specific driver flow methods: execution sequencing, Cloudflare wait early-exit, login flow fallback behavior, ordinary-account detection, publisher setup, upload-page URL extraction, and site-load selector checks. +- **Next steps**: Expand integration-level Selenium tests against live DriveThruCards environments after bot-detection/login stability is further improved. + +## Session 5: Documentation & Polish + +- [x] Update README and GitHub Wiki for DriveThruCards CLI workflow and Ghostscript requirement +- [x] Consider adding CLI option to skip PDF recreation +- [x] Add CLI option to only download card images +- [x] Update bundler so Python executable incorporates assets under desktop-tool +- [x] Add CLI status updates during Ghostscript PDF processing (large orders appear to hang) +- [x] Update CLI UI to ask users to confirm Ghostscript installation as part of the DriveThruCards requirement flow +- [x] Add browser-first DriveThruCards sign-in instructions and a flag to suppress them + +### Context +- **Key files**: `autofill.py`, `tests/test_desktop_tool.py` +- **Findings**: Added `--download-images-only` CLI option with `download_images_for_orders()` path that downloads fronts/backs and exits without launching webdriver/PDF export. Added tests for CLI flag visibility and download helper behavior. Added explicit PDF/X conversion progress logging (`Converting PDF to PDF/X-1a (n/total)`) and regression test to verify logs are emitted during conversion. Updated Nuitka directives to include the entire `assets/` directory and added a regression test to guard this packaging behavior. Updated `desktop-tool/readme.md` with DriveThruCards Ghostscript requirements and CLI workflow/flag documentation. Added a DriveThruCards-specific Ghostscript installation confirmation prompt before installation attempts and tests for this prompt path. Added a browser instruction page before DriveThruCards opens, plus `--skip-dtc-instructions` to bypass it. +- **Next steps**: All listed tasks are complete; continue monitoring DriveThruCards UI changes and adjust selectors/tests as needed. + +## Session 6: CLI UX Improvements + +- [x] Improve the base desktop tool CLI for the first few multiple-choice questions by replacing free-form typed input with a cleaner selection UI (for example: a selectable list/dropdown-style prompt). + +### Context +- **Key files**: `autofill.py`, `tests/test_desktop_tool.py` +- **Findings**: Replaced no-args startup prompts for browser, site, auto-save, and image post-processing with InquirerPy `rawlist` prompts (number-jump + arrow-key navigation). Added `should_run_interactive_onboarding()` so this only runs for interactive no-argument launches, preserving existing flag-driven/non-interactive behavior. +- **Validation**: `.venv/bin/pytest -q tests/test_desktop_tool.py -k "interactive_onboarding or cli_site_choices_list_drivethrucards_last"` and `.venv/bin/pytest -q tests/test_desktop_tool.py -k "cli_help_documents_new_flags_and_stealth_guidance or cli_help_includes_global_log_level_option or cli_help_includes_download_images_only_option"` both passed. +- **Next steps**: Continue follow-up CLI-only UX work on branch `codex/cli-ui-overhaul`. + +## Session 7: CLI UI Overhaul + +- [x] Create dedicated branch for CLI-only UI/UX changes: `codex/cli-ui-overhaul` +- [ ] Separate interactive question prompts from background status/progress output so prompts are visually distinct. +- [ ] Rework progress bar lifecycle so completed bars do not obscure subsequent status messages. +- [ ] Standardize user-facing messaging for non-technical users (clear phase headers + concise action prompts). + +## PR-Wide Final Audit + +- [ ] Run a full post-PR code cleanup audit across all files changed in this DriveThruCards PR to remove redundancies, leftover scaffolding, and any loose/dead code from prior iterations. +- [ ] Prune low-value tests (especially documentation/help-text/existence-only assertions) and keep/add behavior-focused tests that validate real functionality. diff --git a/desktop-tool/docs/drivethrucards-publisher-workflow.html b/desktop-tool/docs/drivethrucards-publisher-workflow.html new file mode 100644 index 000000000..6ee595beb --- /dev/null +++ b/desktop-tool/docs/drivethrucards-publisher-workflow.html @@ -0,0 +1,291 @@ + + + + + + DriveThruCards Publisher Workflow + + + +
+
+

DriveThruCards publisher-account automation

+

Implemented browser-first experience for signing in, creating a basic account when necessary, enabling publisher permissions, and handing off to the existing card-order automation.

+
+ Desktop tool action + DriveThruCards page or user action + Required success state +
+
+ +
+

1. Explain sign-in before sending the user to DriveThruCards

+
+ + Browser-first sign-in and publisher readiness flow + The console announces that browser instructions are opening. A local instruction page explains Google, Facebook, and new-account choices before opening DriveThruCards. After base sign-in is detected separately, the Publish tab determines whether publisher setup is required. + + + + + + +
Console message“DriveThruCards setup will open in your browser. Follow the instructions there.”
+
+ + + + +
Open local instruction pageReuse the existing MakePlayingCards landing-page pattern. The user does not need to look back at the console.
+
+ + + + +
Continue to DriveThruCardsExisting users sign in. New users sign in with Google or Facebook or click Get Started Here!
+
+ + + + +
Handle CAPTCHA automaticallyUse the existing stealth and Turnstile techniques; never wait for the user.
+
+ + + + +
Base account signed inDetect ordinary sign-in separately from publisher permissions.
+
+ + + + +
Publish tab visible?
+
+ + + Yes + + +
Continue card order
+
+ + + No + + +
Run publisher setup
+
+
+
+

The browser page appears before DriveThruCards, so every user sees the sign-in choices without switching attention back to the console.

+ + +
+ +
+

2. Complete publisher setup automatically

+
+ + Legacy DriveThruCards publisher wizard + The tool opens the legacy setup, chooses a non-exclusive account, fills the agreement and checks I Agree automatically, keeps payment defaults, saves, and verifies the Publish tab. + + + + + + +
Open legacy setup/joinchoice.php
+
+ + + + +
Account TypeCreate a non-exclusive publisher account.
+
+ + + + +
AgreementSet publisher name, check “I Agree” automatically, and submit. No console confirmation.
+
+ + + + +
Payment InfoKeep the prefilled defaults and save.
+
+ + + + +
Publish tab visible?
+
+ + + Yes + + +
Continue card order
+
+ + + No + + +
Setup failedReport the last step and leave the browser open for inspection.
+
+
+
+

Confirmation copy, URLs, and the Publisher Hub link are informational only. The visible Publish tab is authoritative.

+
+ +
+

Authoritative publisher-permission check

+
+ Log Off + Account + Publish + My Library + Wishlists +
+

Success means: a visible top-navigation element matches a[href*='pub_tools.php'].

+
+ +
Last updated: July 16, 2026 — Source: captured DriveThruCards walkthrough, browser snapshots, and verified desktop-tool selectors.
+
+ + From 2d5b042d8165227f83a6d939532baf4a220fa150 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 17 Jul 2026 12:02:09 -0700 Subject: [PATCH 41/76] Stop tracking desktop tool TODO --- desktop-tool/TODO.md | 117 ------------------------------------------- 1 file changed, 117 deletions(-) delete mode 100644 desktop-tool/TODO.md diff --git a/desktop-tool/TODO.md b/desktop-tool/TODO.md deleted file mode 100644 index a0d7906bc..000000000 --- a/desktop-tool/TODO.md +++ /dev/null @@ -1,117 +0,0 @@ -# Desktop Tool - TODO - -## Session 1: Platform Validation - -- [x] Test Ghostscript install step on Windows -- [x] Test Ghostscript install step on Linux - -### Context -- **Key files**: `tests/test_desktop_tool.py`, `autofill.py` -- **Findings**: Added mocked platform tests for `ensure_ghostscript_available()` validating Windows `winget` and Linux `apt` install command paths, plus updated prompt-loop test stability. -- **Next steps**: Validate real installs manually on native Windows/Linux environments during release testing. - -## Session 2: Consistent PDF Creation - -- [x] (User written, not by AI) When creating the PDF again after changing the card images in the cards folder, sometimes the PDF does not update. Confirm if this is some issue regarding accessing the same images saved in memory. Updating the images and then recreating the PDF should reflect those changes. -- [x] (User written, not by AI) Add CLI command to skip the creation of a new PDF if one already exists. Add UI in the CLI to suggest recreating the PDF, even with this flag selected, if there are images in the cards folder newer than the creation date of the existing PDF. - -### Context -- **Key files**: `src/pdf_maker.py`, `tests/test_desktop_tool.py` -- **Findings**: `fpdf` caches raster images by identifier; using file path identifiers can reuse stale image data. `PdfExporter.add_image` now feeds raw bytes for standard exports so cache keys track file content. Added regression test `test_pdf_exporter_add_image_uses_image_bytes`. Added CLI flag `--skip-pdf-if-exists` and helper logic to reuse existing export PDFs, prompt for recreation when `cards/` files are newer, and force regeneration for DriveThruCards if `_pdfx.pdf` output is missing. -- **Next steps**: Session 2 is complete. Continue with Session 3 Selenium login/bot-detection tasks. - -## Session 3: Selenium Robustness - -### Context -- **Key files**: `src/driver.py`, `src/webdrivers.py`, `src/constants.py` -- **Findings**: - - Switched to `undetected-chromedriver` library for Cloudflare bypass (standard Selenium stealth measures were insufficient) - - Added aggressive polling (500ms intervals) for Cloudflare challenge detection - - Added auto-click attempt for Turnstile checkbox in iframe (fallback if challenge appears) - - Fixed login modal selector to target correct link (`.modal a[href='/en/']`) - - Removed `set_network_conditions` calls which were a potential detection vector - - `undetected-chromedriver` requires `setuptools` on Python 3.13+ (distutils removed) - - `undetected-chromedriver` auto-detection of Chrome version is broken; implemented `_detect_chrome_version()` to query browser directly - - Cloudflare bypass now works - - Added stealth JavaScript to `webdrivers.py` (`_apply_stealth_scripts`) to hide automation traces - this worked initially but login is now blocked again - - Fixed slow response after Cloudflare by disabling implicit wait (5s) during polling methods - - Removed unused visibility/focus workaround methods that caused jarring UX (minimize/restore window) - - Added support for launching Chromium browsers with an existing user data directory/profile (`--browser-profile-path`, `--browser-profile-name`) so saved cookies/password managers can be reused for DriveThruCards login - - Added tests to verify Chromium profile options are passed to `undetected-chromedriver` / Brave webdriver setup - - Made custom stealth JavaScript injection opt-in (`--dtc-custom-stealth`) instead of always-on to reduce bot-detection surface area when using `undetected-chromedriver` - - Added tests to verify custom stealth is disabled by default and only applied when explicitly enabled - - Refactored `execute_drive_thru_cards_order` into explicit guarded steps with contextual failures and removed dead legacy upload path code - - Added tests verifying DriveThruCards execution halts on login timeout and reports failing step names for easier debugging against UI changes - - Moved noisy DriveThruCards Selenium step logs from `info` to `debug`, so average users get cleaner output while detailed traces remain available via `--log-level DEBUG` - - Removed post-login pause by replacing longer `WebDriverWait` checks in publisher/setup navigation with fast polling and immediate direct-navigation fallback - - Added browser-first sign-in instructions with `--skip-dtc-instructions` for experienced users - - Separated ordinary account sign-in detection from publisher readiness detection - - Automated non-exclusive publisher setup, including agreement acceptance and payment-default save; the visible Publish tab is the authoritative success check -- **Validation**: A full CLI run with a new Google-linked account and `/Users/bradley/Documents/dtc-test` created publisher access, uploaded the card PDF, and reached DriveThruCards checkout without purchasing. -- **Next steps**: Continue live DriveThruCards validation runs to tune selectors/timeouts as the site evolves. - -### Testing Notes - -**Important:** Always run the desktop tool itself for testing—do not spin up a separate browser instance. The first step in the Selenium automation chain is bypassing a Cloudflare captcha, which requires `undetected-chromedriver` and cannot be easily done with fresh browser instances. - -**Test command:** -```bash -.venv/bin/python autofill.py --site drivethrucards --directory /Users/bradley/Documents/dtc-test -``` - -Always run this command yourself (via Bash tool) rather than prompting the user to run it. This allows you to capture output directly and debug issues more easily. - -- [x] Improve Cloudflare CAPTCHA solver reliability -- [x] Fix slow response after Cloudflare CAPTCHA (disabled implicit wait during polling) -- [x] Fix login bot detection - stealth JavaScript approach inconsistent, site shows "Unable to log in" -- [x] Investigate using existing Chrome profile for cached credentials/password managers -- [x] Potentially overhaul Selenium automation due to new DriveThruCards UI -- [x] Automate creating a DriveThruCards non-exclusive publisher account when the Publish tab is absent -- [x] Reduce verbose Selenium step-by-step CLI output by default; keep detailed messages behind global log-level controls -- [x] Remove post-login delay before navigating to DriveThruCards publisher tools/setup flow - -## Session 4: Test Coverage - -- [x] Add testing coverage for all DriveThruCards unique actions - -### Context -- **Key files**: `tests/test_drivethrucards_driver.py`, `src/driver.py` -- **Findings**: Added focused unit coverage for DriveThruCards-specific driver flow methods: execution sequencing, Cloudflare wait early-exit, login flow fallback behavior, ordinary-account detection, publisher setup, upload-page URL extraction, and site-load selector checks. -- **Next steps**: Expand integration-level Selenium tests against live DriveThruCards environments after bot-detection/login stability is further improved. - -## Session 5: Documentation & Polish - -- [x] Update README and GitHub Wiki for DriveThruCards CLI workflow and Ghostscript requirement -- [x] Consider adding CLI option to skip PDF recreation -- [x] Add CLI option to only download card images -- [x] Update bundler so Python executable incorporates assets under desktop-tool -- [x] Add CLI status updates during Ghostscript PDF processing (large orders appear to hang) -- [x] Update CLI UI to ask users to confirm Ghostscript installation as part of the DriveThruCards requirement flow -- [x] Add browser-first DriveThruCards sign-in instructions and a flag to suppress them - -### Context -- **Key files**: `autofill.py`, `tests/test_desktop_tool.py` -- **Findings**: Added `--download-images-only` CLI option with `download_images_for_orders()` path that downloads fronts/backs and exits without launching webdriver/PDF export. Added tests for CLI flag visibility and download helper behavior. Added explicit PDF/X conversion progress logging (`Converting PDF to PDF/X-1a (n/total)`) and regression test to verify logs are emitted during conversion. Updated Nuitka directives to include the entire `assets/` directory and added a regression test to guard this packaging behavior. Updated `desktop-tool/readme.md` with DriveThruCards Ghostscript requirements and CLI workflow/flag documentation. Added a DriveThruCards-specific Ghostscript installation confirmation prompt before installation attempts and tests for this prompt path. Added a browser instruction page before DriveThruCards opens, plus `--skip-dtc-instructions` to bypass it. -- **Next steps**: All listed tasks are complete; continue monitoring DriveThruCards UI changes and adjust selectors/tests as needed. - -## Session 6: CLI UX Improvements - -- [x] Improve the base desktop tool CLI for the first few multiple-choice questions by replacing free-form typed input with a cleaner selection UI (for example: a selectable list/dropdown-style prompt). - -### Context -- **Key files**: `autofill.py`, `tests/test_desktop_tool.py` -- **Findings**: Replaced no-args startup prompts for browser, site, auto-save, and image post-processing with InquirerPy `rawlist` prompts (number-jump + arrow-key navigation). Added `should_run_interactive_onboarding()` so this only runs for interactive no-argument launches, preserving existing flag-driven/non-interactive behavior. -- **Validation**: `.venv/bin/pytest -q tests/test_desktop_tool.py -k "interactive_onboarding or cli_site_choices_list_drivethrucards_last"` and `.venv/bin/pytest -q tests/test_desktop_tool.py -k "cli_help_documents_new_flags_and_stealth_guidance or cli_help_includes_global_log_level_option or cli_help_includes_download_images_only_option"` both passed. -- **Next steps**: Continue follow-up CLI-only UX work on branch `codex/cli-ui-overhaul`. - -## Session 7: CLI UI Overhaul - -- [x] Create dedicated branch for CLI-only UI/UX changes: `codex/cli-ui-overhaul` -- [ ] Separate interactive question prompts from background status/progress output so prompts are visually distinct. -- [ ] Rework progress bar lifecycle so completed bars do not obscure subsequent status messages. -- [ ] Standardize user-facing messaging for non-technical users (clear phase headers + concise action prompts). - -## PR-Wide Final Audit - -- [ ] Run a full post-PR code cleanup audit across all files changed in this DriveThruCards PR to remove redundancies, leftover scaffolding, and any loose/dead code from prior iterations. -- [ ] Prune low-value tests (especially documentation/help-text/existence-only assertions) and keep/add behavior-focused tests that validate real functionality. From db9bfc6dc45b11031202e3fa2bc575a61872a5fc Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 17 Jul 2026 12:32:10 -0700 Subject: [PATCH 42/76] Skip secret-backed desktop tests on fork PRs --- .github/workflows/test-desktop-tool.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-desktop-tool.yml b/.github/workflows/test-desktop-tool.yml index 52acbfadf..d49f19c1b 100644 --- a/.github/workflows/test-desktop-tool.yml +++ b/.github/workflows/test-desktop-tool.yml @@ -8,6 +8,8 @@ jobs: test: name: Desktop tool tests runs-on: ${{ matrix.os }} + env: + GOOGLE_DRIVE_API_KEY: ${{ secrets.GOOGLE_DRIVE_API_KEY }} strategy: matrix: include: @@ -16,6 +18,11 @@ jobs: # - os: ubuntu-latest # https://github.com/browser-actions/setup-edge/issues/516 steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/test-desktop-tool + - name: Run desktop tool tests + if: env.GOOGLE_DRIVE_API_KEY != '' + uses: ./.github/actions/test-desktop-tool with: - google-drive-api-key: ${{ secrets.GOOGLE_DRIVE_API_KEY }} + google-drive-api-key: ${{ env.GOOGLE_DRIVE_API_KEY }} + - name: Skip secret-backed tests for fork pull requests + if: env.GOOGLE_DRIVE_API_KEY == '' + run: echo "::notice::Desktop tool tests require GOOGLE_DRIVE_API_KEY, which is unavailable to fork pull requests." From e60984ef369bd8aa9b26947a2f1dab8ed27ef195 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 01:44:40 -0700 Subject: [PATCH 43/76] Scope undetected-chromedriver to DriveThruCards only Restore upstream Chrome/Brave/Edge/Firefox factories (detach, network throttling) for MakePlayingCards-family sites. DriveThruCards uses a new get_undetected_chrome_driver factory; profile options apply there only. Fixes a crash where selecting Firefox or Edge passed unsupported kwargs to their factories. Removes the opt-in custom stealth scripts and --dtc-custom-stealth flag (off by default, self-documented detection risk). --- desktop-tool/autofill.py | 16 +- .../docs/Desktop-Tool.wiki.addendum.md | 5 - desktop-tool/src/driver.py | 38 +-- desktop-tool/src/webdrivers.py | 243 ++++++------------ desktop-tool/tests/test_desktop_tool.py | 76 +----- .../tests/test_drivethrucards_driver.py | 45 +++- 6 files changed, 160 insertions(+), 263 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index f8a283052..764c51a95 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -305,8 +305,8 @@ def get_site_picker_choices() -> list[str]: "--browser-profile-path", default=None, help=( - "Optional Chromium user-data directory for reusing existing profiles, cookies, and password managers. " - "Example on macOS: ~/Library/Application Support/Google/Chrome" + "Optional Chromium user-data directory for reusing existing profiles, cookies, and password managers " + "when targeting DriveThruCards. Example on macOS: ~/Library/Application Support/Google/Chrome" ), ) @click.option( @@ -314,16 +314,6 @@ def get_site_picker_choices() -> list[str]: default="Default", help="Profile directory name inside --browser-profile-path (e.g. Default or 'Profile 1').", ) -@click.option( - "--dtc-custom-stealth", - default=False, - help=( - "Optional extra stealth JavaScript for DriveThruCards. " - "Use only as a last resort if default automation gets stuck around Cloudflare/login checks; " - "disabled by default because it can increase bot-detection risk on some runs." - ), - is_flag=True, -) @click.option( "--skip-dtc-instructions", default=False, @@ -452,7 +442,6 @@ def main( binary_location: Optional[str], browser_profile_path: Optional[str], browser_profile_name: str, - dtc_custom_stealth: bool, skip_dtc_instructions: bool, site: str, exportpdf: bool, @@ -567,7 +556,6 @@ def main( binary_location=binary_location, browser_profile_path=browser_profile_path, browser_profile_name=browser_profile_name if browser_profile_path else None, - apply_custom_stealth=dtc_custom_stealth, starting_url=starting_url, ) dtc_driver.execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) diff --git a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md index 8e9e64f8a..a2fe6c155 100644 --- a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md +++ b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md @@ -28,11 +28,6 @@ Target page: - `--browser-profile-name` - Select the profile directory inside the user data folder (default: `Default`). -- `--dtc-custom-stealth` - - Optional extra stealth JavaScript for DriveThruCards. - - Use only as a last resort if default automation gets stuck around Cloudflare/login checks. - - Disabled by default because it can increase bot-detection risk on some runs. - ### Logging Flag (add subsection if missing) - `--log-level` diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index a7ea8b011..1d4f88e6c 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -45,6 +45,7 @@ ignore_javascript_errors, log_hours_minutes_seconds_elapsed, ) +from src.webdrivers import get_default_brave_binary_location, get_undetected_chrome_driver @attr.s @@ -55,7 +56,6 @@ class AutofillDriver: binary_location: Optional[str] = attr.ib(default=None) # path to browser executable browser_profile_path: Optional[str] = attr.ib(default=None) # user data dir for Chromium browsers browser_profile_name: Optional[str] = attr.ib(default=None) # profile directory name, e.g. "Profile 1" - apply_custom_stealth: bool = attr.ib(default=False) target_site: TargetSites = attr.ib(default=TargetSites.MakePlayingCards) headless: bool = attr.ib(default=False) starting_url: str = attr.ib(default="data:") @@ -80,19 +80,32 @@ def _quit_driver_quietly(driver: Optional[WebDriver]) -> None: except Exception: pass + def create_driver(self) -> WebDriver: + if self.target_site == TargetSites.DriveThruCards: + # DriveThruCards' bot detection (Cloudflare) blocks standard Selenium, so it needs + # undetected-chromedriver. Other sites keep their standard drivers untouched. + binary_location = self.binary_location + if self.browser == Browsers.brave and binary_location is None: + binary_location = get_default_brave_binary_location() + elif self.browser not in (Browsers.chrome, Browsers.brave): + logger.info( + f"DriveThruCards automation requires a Chromium browser - " + f"using {bold('Chrome')} instead of {bold(self.browser.name)}." + ) + return get_undetected_chrome_driver( + headless=self.headless, + binary_location=binary_location, + user_data_dir=self.browser_profile_path, + profile_directory=self.browser_profile_name, + ) + return self.browser.value(headless=self.headless, binary_location=self.binary_location) + def initialise_driver(self) -> None: - last_exception: Optional[Exception] = None max_attempts = 3 for attempt in range(1, max_attempts + 1): driver = None try: - driver = self.browser.value( - headless=self.headless, - binary_location=self.binary_location, - user_data_dir=self.browser_profile_path, - profile_directory=self.browser_profile_name, - apply_custom_stealth=self.apply_custom_stealth, - ) # type: ignore # TODO + driver = self.create_driver() driver.set_window_size(1200, 900) driver.implicitly_wait(5) driver.get(self.starting_url) @@ -104,7 +117,6 @@ def initialise_driver(self) -> None: self.driver = driver return except (AttributeError, ValueError, sl_exc.WebDriverException) as e: - last_exception = e self._quit_driver_quietly(driver) if attempt < max_attempts: logger.warning( @@ -117,12 +129,6 @@ def initialise_driver(self) -> None: f"Please make sure you have installed the browser & that it is up to date:\n\n{bold(e)}" ) - if last_exception is not None: - raise Exception( - f"An error occurred while attempting to configure the webdriver for your specified browser. " - f"Please make sure you have installed the browser & that it is up to date:\n\n{bold(last_exception)}" - ) - def initialise_bars(self) -> None: # set the total for upload/download bars to 0 here, then change the total according to each order # as they're processed diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 1ac25f5cd..2bd754206 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -4,13 +4,87 @@ from typing import Optional import undetected_chromedriver as uc -from selenium.webdriver import Edge, Firefox +from selenium.webdriver import Chrome, Edge, Firefox +from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.chromium.options import ChromiumOptions from selenium.webdriver.chromium.webdriver import ChromiumDriver from selenium.webdriver.edge.options import Options as EdgeOptions from selenium.webdriver.firefox.options import Options as FirefoxOptions +def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: + options = ChromeOptions() + options.add_argument("--no-sandbox") + options.add_argument("--log-level=3") + options.add_argument("--disable-dev-shm-usage") + if headless: + options.add_argument("--headless=new") + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + options.add_experimental_option("detach", True) + if binary_location is not None: + options.binary_location = binary_location + driver = Chrome(options=options) + driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) + return driver + + +def get_brave_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: + options = ChromeOptions() + options.add_argument("--no-sandbox") + options.add_argument("--log-level=3") + options.add_argument("--disable-dev-shm-usage") + if headless: + options.add_argument("--headless=new") + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + options.add_experimental_option("detach", True) + + # the binary location for brave must be manually specified (otherwise chrome will open instead) + options.binary_location = binary_location or get_default_brave_binary_location() + + driver = Chrome(options=options) + driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) + return driver + + +def get_edge_driver(headless: bool = False, binary_location: Optional[str] = None) -> ChromiumDriver: + options: ChromiumOptions = EdgeOptions() + options.add_argument("--no-sandbox") + options.add_argument("--log-level=3") + options.add_argument("--disable-dev-shm-usage") + if headless: + options.add_argument("--headless=new") + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + options.add_experimental_option("detach", True) + if binary_location is not None: + options.binary_location = binary_location + driver: ChromiumDriver = Edge(options=options) # type: ignore + driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) + return driver + + +# note: firefox is not currently supported +def get_firefox_driver(headless: bool = False, binary_location: Optional[str] = None) -> Firefox: + options = FirefoxOptions() + options.add_argument("--log-level=3") + if headless: + options.add_argument("--headless") + if binary_location is not None: + options.binary_location = binary_location + driver = Firefox(options=options) + return driver + + +def get_default_brave_binary_location() -> str: + default_binary_locations = { + "linux": "/usr/bin/brave-browser", + "darwin": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", + "win32": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe", + } + if sys.platform not in default_binary_locations.keys(): + raise KeyError(f"Cannot determine the default Brave binary location for the operating system {sys.platform}!") + return default_binary_locations[sys.platform] + + def _detect_chrome_version() -> Optional[int]: """ Detect the installed Chrome version by querying the browser. @@ -18,7 +92,6 @@ def _detect_chrome_version() -> Optional[int]: """ try: if sys.platform == "darwin": - # macOS: Use the Chrome binary to get version result = subprocess.run( ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"], capture_output=True, @@ -26,7 +99,6 @@ def _detect_chrome_version() -> Optional[int]: timeout=5, ) elif sys.platform == "win32": - # Windows: Query registry or use wmic result = subprocess.run( ["reg", "query", r"HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon", "/v", "version"], capture_output=True, @@ -34,7 +106,6 @@ def _detect_chrome_version() -> Optional[int]: timeout=5, ) else: - # Linux: Use google-chrome binary result = subprocess.run( ["google-chrome", "--version"], capture_output=True, @@ -51,182 +122,30 @@ def _detect_chrome_version() -> Optional[int]: return None -def _apply_stealth_scripts(driver: uc.Chrome) -> None: - """ - Apply additional stealth JavaScript to hide automation traces. - These patches help bypass bot detection on sites like DriveThruCards. - """ - stealth_js = """ - // Hide webdriver property - Object.defineProperty(navigator, 'webdriver', { - get: () => undefined - }); - - // Fix chrome object - window.chrome = { - runtime: {}, - loadTimes: function() {}, - csi: function() {}, - app: {} - }; - - // Fix permissions query - const originalQuery = window.navigator.permissions.query; - window.navigator.permissions.query = (parameters) => ( - parameters.name === 'notifications' ? - Promise.resolve({ state: Notification.permission }) : - originalQuery(parameters) - ); - - // Fix plugins to look more realistic - Object.defineProperty(navigator, 'plugins', { - get: () => [ - { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' }, - { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' }, - { name: 'Native Client', filename: 'internal-nacl-plugin' } - ] - }); - - // Fix languages - Object.defineProperty(navigator, 'languages', { - get: () => ['en-US', 'en'] - }); - - // Remove automation-related properties from window - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; - """ - driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": stealth_js}) - - -def get_chrome_driver( - headless: bool = False, - binary_location: Optional[str] = None, - remote_debugging_port: Optional[int] = None, - user_data_dir: Optional[str] = None, - profile_directory: Optional[str] = None, - apply_custom_stealth: bool = False, -) -> uc.Chrome: - """ - Create a Chrome driver using undetected-chromedriver to bypass bot detection. - This automatically handles Cloudflare and similar anti-bot systems. - """ - options = uc.ChromeOptions() - options.add_argument("--no-sandbox") - options.add_argument("--log-level=3") - options.add_argument("--disable-dev-shm-usage") - options.add_argument("--disable-blink-features=AutomationControlled") - if headless: - options.add_argument("--headless=new") - if remote_debugging_port is not None: - options.add_argument(f"--remote-debugging-port={remote_debugging_port}") - if binary_location is not None: - options.binary_location = binary_location - if user_data_dir is not None: - options.add_argument(f"--user-data-dir={user_data_dir}") - if profile_directory is not None: - options.add_argument(f"--profile-directory={profile_directory}") - - # undetected-chromedriver handles stealth automatically - # Detect Chrome version since auto-detection can fail - version_main = _detect_chrome_version() - driver = uc.Chrome(options=options, version_main=version_main) - - # Keep custom stealth JS opt-in: these patches can become a detection vector on some sites. - if apply_custom_stealth: - _apply_stealth_scripts(driver) - - return driver - - -def get_brave_driver( +def get_undetected_chrome_driver( headless: bool = False, binary_location: Optional[str] = None, user_data_dir: Optional[str] = None, profile_directory: Optional[str] = None, - apply_custom_stealth: bool = False, ) -> uc.Chrome: """ - Create a Brave driver using undetected-chromedriver. + Create a Chrome driver using undetected-chromedriver, for sites (DriveThruCards) whose bot detection + blocks standard Selenium. Only used when targeting DriveThruCards. """ options = uc.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") options.add_argument("--disable-dev-shm-usage") - if headless: - options.add_argument("--headless=new") - if user_data_dir is not None: - options.add_argument(f"--user-data-dir={user_data_dir}") - if profile_directory is not None: - options.add_argument(f"--profile-directory={profile_directory}") - - # the binary location for brave must be manually specified (otherwise chrome will open instead) - if binary_location is not None: - options.binary_location = binary_location - else: - default_binary_locations = { - "linux": "/usr/bin/brave-browser", - "darwin": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", - "win32": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe", - } - if sys.platform not in default_binary_locations.keys(): - raise KeyError( - f"Cannot determine the default Brave binary location for the operating system {sys.platform}!" - ) - options.binary_location = default_binary_locations[sys.platform] - - driver = uc.Chrome(options=options) - if apply_custom_stealth: - _apply_stealth_scripts(driver) - return driver - - -def get_edge_driver( - headless: bool = False, - binary_location: Optional[str] = None, - user_data_dir: Optional[str] = None, - profile_directory: Optional[str] = None, -) -> ChromiumDriver: - """ - Create an Edge driver with stealth options. - Note: Edge doesn't have an undetected variant, so we use standard stealth measures. - """ - options: ChromiumOptions = EdgeOptions() - options.add_argument("--no-sandbox") - options.add_argument("--log-level=3") - options.add_argument("--disable-dev-shm-usage") options.add_argument("--disable-blink-features=AutomationControlled") if headless: options.add_argument("--headless=new") - options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"]) - options.add_experimental_option("useAutomationExtension", False) - options.add_experimental_option("detach", True) if binary_location is not None: options.binary_location = binary_location if user_data_dir is not None: options.add_argument(f"--user-data-dir={user_data_dir}") if profile_directory is not None: options.add_argument(f"--profile-directory={profile_directory}") - driver: ChromiumDriver = Edge(options=options) # type: ignore - # Apply CDP stealth for Edge - driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { - "source": """ - Object.defineProperty(navigator, 'webdriver', { - get: () => undefined - }); - """ - }) - return driver - -# note: firefox is not currently supported -def get_firefox_driver(headless: bool = False, binary_location: Optional[str] = None) -> Firefox: - options = FirefoxOptions() - options.add_argument("--log-level=3") - if headless: - options.add_argument("--headless") - if binary_location is not None: - options.binary_location = binary_location - driver = Firefox(options=options) - return driver + # undetected-chromedriver handles stealth automatically. + # Detect the Chrome version ourselves since auto-detection can fail. + return uc.Chrome(options=options, version_main=_detect_chrome_version()) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 54b1e853a..9ae36ca16 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -1,4 +1,5 @@ import gc +import inspect import os import re import textwrap @@ -346,22 +347,18 @@ def test_maybe_reuse_existing_pdfs_requires_pdfx_if_requested(tmp_path) -> None: os.chdir(cwd_before) -def test_get_chrome_driver_applies_user_profile_options(monkeypatch: pytest.MonkeyPatch) -> None: +def test_get_undetected_chrome_driver_applies_user_profile_options(monkeypatch: pytest.MonkeyPatch) -> None: captured = {"options": None, "version_main": None} - class DummyDriver: - def execute_cdp_cmd(self, _command, _payload): - return None - def fake_chrome(*, options, version_main): captured["options"] = options captured["version_main"] = version_main - return DummyDriver() + return object() monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) monkeypatch.setattr(webdrivers.uc, "Chrome", fake_chrome) - webdrivers.get_chrome_driver( + webdrivers.get_undetected_chrome_driver( user_data_dir="/tmp/chrome-data", profile_directory="Profile 7", ) @@ -371,58 +368,12 @@ def fake_chrome(*, options, version_main): assert captured["version_main"] == 120 -def test_get_chrome_driver_does_not_apply_custom_stealth_by_default(monkeypatch: pytest.MonkeyPatch) -> None: - stealth_called = {"value": False} - - class DummyDriver: - def execute_cdp_cmd(self, _command, _payload): - return None - - monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) - monkeypatch.setattr(webdrivers.uc, "Chrome", lambda **_kwargs: DummyDriver()) - monkeypatch.setattr(webdrivers, "_apply_stealth_scripts", lambda _driver: stealth_called.update(value=True)) - - webdrivers.get_chrome_driver() - - assert stealth_called["value"] is False - - -def test_get_chrome_driver_applies_custom_stealth_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None: - stealth_called = {"value": False} - - class DummyDriver: - def execute_cdp_cmd(self, _command, _payload): - return None - - monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) - monkeypatch.setattr(webdrivers.uc, "Chrome", lambda **_kwargs: DummyDriver()) - monkeypatch.setattr(webdrivers, "_apply_stealth_scripts", lambda _driver: stealth_called.update(value=True)) - - webdrivers.get_chrome_driver(apply_custom_stealth=True) - - assert stealth_called["value"] is True - - -def test_get_brave_driver_applies_user_profile_options(monkeypatch: pytest.MonkeyPatch) -> None: - captured = {"options": None} - - class DummyDriver: - pass - - def fake_chrome(*, options): - captured["options"] = options - return DummyDriver() - - monkeypatch.setattr(webdrivers.uc, "Chrome", fake_chrome) - - webdrivers.get_brave_driver( - binary_location="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", - user_data_dir="/tmp/brave-data", - profile_directory="Default", - ) - - assert "--user-data-dir=/tmp/brave-data" in captured["options"].arguments - assert "--profile-directory=Default" in captured["options"].arguments +@pytest.mark.parametrize("browser", constants.Browsers) +def test_standard_driver_factories_accept_only_upstream_kwargs(browser: constants.Browsers) -> None: + # Regression test: passing DTC-only kwargs (user_data_dir etc.) to the standard factories + # used for MakePlayingCards-family sites must fail loudly, proving they were never added there. + factory_parameters = inspect.signature(browser.value).parameters + assert set(factory_parameters.keys()) == {"headless", "binary_location"} def test_cli_help_includes_download_images_only_option() -> None: @@ -437,16 +388,14 @@ def test_cli_help_includes_global_log_level_option() -> None: assert "--log-level" in result.output -def test_cli_help_documents_new_flags_and_stealth_guidance() -> None: +def test_cli_help_documents_new_flags() -> None: result = CliRunner().invoke(autofill_cli.main, ["--help"]) assert result.exit_code == 0 assert "--skip-pdf-if-exists" in result.output assert "--download-images-only" in result.output assert "--browser-profile-path" in result.output assert "--browser-profile-name" in result.output - assert "--dtc-custom-stealth" in result.output assert "--skip-dtc-instructions" in result.output - assert "last resort" in result.output assert "detailed Selenium step-by-step logs" in result.output @@ -642,9 +591,6 @@ def test_wiki_addendum_includes_cli_usage_updates() -> None: assert "--skip-pdf-if-exists" in wiki assert "--download-images-only" in wiki assert "--browser-profile-path" in wiki - assert "--dtc-custom-stealth" in wiki - assert "last resort" in wiki - assert "Cloudflare/login checks" in wiki assert "--log-level" in wiki assert "Use `DEBUG` to show detailed Selenium step-by-step logs" in wiki assert "confirmation before attempting Ghostscript installation" in wiki diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index e598ec9d9..b260c0fe6 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -157,7 +157,7 @@ def fake_browser_factory(**_kwargs): created.append(driver) return driver - monkeypatch.setattr(dtc_driver, "browser", SimpleNamespace(value=fake_browser_factory, name="chrome")) + monkeypatch.setattr("src.driver.get_undetected_chrome_driver", fake_browser_factory) dtc_driver.starting_url = TargetSites.DriveThruCards.value.starting_url monkeypatch.setattr("src.driver.WebDriverWait", lambda *_args, **_kwargs: SimpleNamespace(until=lambda _cond: True)) monkeypatch.setattr("src.driver.time.sleep", lambda _seconds: None) @@ -359,3 +359,46 @@ def find_elements(self, by: By, selector: str): assert dtc_driver._is_site_loaded() is True assert fake_driver.wait_values == [0, 5] + + +def test_create_driver_uses_undetected_chrome_for_dtc( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + captured = {} + + def fake_undetected_chrome(**kwargs): + captured.update(kwargs) + return "uc-driver" + + monkeypatch.setattr("src.driver.get_undetected_chrome_driver", fake_undetected_chrome) + dtc_driver.browser_profile_path = "/tmp/profile" + dtc_driver.browser_profile_name = "Profile 7" + + assert dtc_driver.create_driver() == "uc-driver" + assert captured == { + "headless": False, + "binary_location": None, + "user_data_dir": "/tmp/profile", + "profile_directory": "Profile 7", + } + + +def test_create_driver_uses_standard_factory_for_other_sites(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(AutofillDriver, "__attrs_post_init__", lambda self: None) + monkeypatch.setattr( + "src.driver.get_undetected_chrome_driver", + lambda **_kwargs: (_ for _ in ()).throw(AssertionError("undetected-chromedriver must not be used here")), + ) + captured = {} + + def fake_factory(headless=False, binary_location=None): + captured.update(headless=headless, binary_location=binary_location) + return "standard-driver" + + autofill_driver = AutofillDriver( + target_site=TargetSites.MakePlayingCards, + browser=SimpleNamespace(value=fake_factory, name="chrome"), + ) + + assert autofill_driver.create_driver() == "standard-driver" + assert captured == {"headless": False, "binary_location": None} From 279afb196e4855a3aca7d541d68256620ad552b1 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 01:48:16 -0700 Subject: [PATCH 44/76] Fail loudly when required DriveThruCards order steps break fill_dtc_product_form, submit_dtc_description_page, open_dtc_upload_page and select_card_type_and_upload_pdf previously reduced missing required elements (title, price, cover, filters, submit, file input, upload confirmation, continue/complete/buy-now) to warnings and returned early, so 'order setup complete!' printed even when no PDF was uploaded. These failures now raise and abort the order with step context. Adds negative fake-DOM tests asserting each step aborts instead of reporting success. --- desktop-tool/src/driver.py | 470 ++++++++---------- .../tests/test_drivethrucards_driver.py | 100 ++++ 2 files changed, 306 insertions(+), 264 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 1d4f88e6c..75f15142d 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -767,58 +767,33 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: today = dt.date.today().strftime("%Y-%m-%d") title = f"{order.name or 'Order'} {today}" - # Fill in the title field - try: - title_input = WebDriverWait(self.driver, 10).until(presence_of_element_located((By.ID, "products_name"))) - title_input.clear() - title_input.send_keys(title) - logger.debug(f"Set product title to: {title}") - except Exception as e: - logger.warning(f"Could not fill title field: {e}") - - # Fill in the special price field with "0" - try: - price_input = self.driver.find_element(By.ID, "options_values_total_price") - price_input.clear() - price_input.send_keys("0") - logger.debug("Set special price to: 0") - except Exception as e: - logger.warning(f"Could not fill price field: {e}") - - # Upload the placeholder cover image - try: - image_input = self.driver.find_element(By.NAME, "products_image") - image_input.send_keys(placeholder_cover_path) - logger.debug(f"Uploaded placeholder cover image: {placeholder_cover_path}") - except Exception as e: - logger.warning(f"Could not upload cover image: {e}") - - # Check the two filter checkboxes - try: - checkbox1 = self.driver.find_element(By.ID, "filter_44550") - if not checkbox1.is_selected(): - self.click_element_with_retry(checkbox1) - logger.debug("Checked filter_44550") - except Exception as e: - logger.warning(f"Could not check filter_44550: {e}") - - try: - checkbox2 = self.driver.find_element(By.ID, "filter_1000138") - if not checkbox2.is_selected(): - self.click_element_with_retry(checkbox2) - logger.debug("Checked filter_1000138") - except Exception as e: - logger.warning(f"Could not check filter_1000138: {e}") + # All of these fields are required for the product to be set up correctly, + # so any missing element must abort the order rather than limp on. + title_input = WebDriverWait(self.driver, 10).until(presence_of_element_located((By.ID, "products_name"))) + title_input.clear() + title_input.send_keys(title) + logger.debug(f"Set product title to: {title}") + + price_input = self.driver.find_element(By.ID, "options_values_total_price") + price_input.clear() + price_input.send_keys("0") + logger.debug("Set special price to: 0") + + self.driver.find_element(By.NAME, "products_image").send_keys(placeholder_cover_path) + logger.debug(f"Uploaded placeholder cover image: {placeholder_cover_path}") + + for checkbox_id in ("filter_44550", "filter_1000138"): + checkbox = self.driver.find_element(By.ID, checkbox_id) + if not checkbox.is_selected() and not self.click_element_with_retry(checkbox): + raise Exception(f"Could not check the {checkbox_id} product filter checkbox.") + logger.debug(f"Checked {checkbox_id}") # Click the first submit button (Save Title Data and Continue to Preview Description) self.set_state(States.defining_order, "Submitting product form") - try: - submit_button = WebDriverWait(self.driver, 10).until(element_to_be_clickable((By.ID, "submit_id"))) - logger.debug("Clicking submit button...") - self.click_element_with_retry(submit_button) - logger.debug("Product form submitted successfully.") - except Exception as e: - logger.warning(f"Could not click submit button: {e}") + submit_button = WebDriverWait(self.driver, 10).until(element_to_be_clickable((By.ID, "submit_id"))) + if not self.click_element_with_retry(submit_button): + raise Exception("Could not click the product form submit button.") + logger.debug("Product form submitted successfully.") def submit_dtc_description_page(self) -> None: """ @@ -826,15 +801,10 @@ def submit_dtc_description_page(self) -> None: Waits for the button to be available (page loaded from previous step). """ self.set_state(States.defining_order, "Saving description") - try: - save_continue_button = WebDriverWait(self.driver, 15).until( - element_to_be_clickable((By.ID, "clicked_element")) - ) - logger.debug("Clicking 'Save and Continue' button...") - self.click_element_with_retry(save_continue_button) - logger.debug("Description page submitted.") - except Exception as e: - logger.warning(f"Could not click 'Save and Continue': {e}") + save_continue_button = WebDriverWait(self.driver, 15).until(element_to_be_clickable((By.ID, "clicked_element"))) + if not self.click_element_with_retry(save_continue_button): + raise Exception("Could not click the 'Save and Continue' button on the description page.") + logger.debug("Description page submitted.") def open_dtc_upload_page(self) -> None: """ @@ -843,23 +813,18 @@ def open_dtc_upload_page(self) -> None: """ self.set_state(States.defining_order, "Opening upload page") - try: - upload_button = WebDriverWait(self.driver, 15).until( - element_to_be_clickable((By.XPATH, "//button[contains(@onclick, 'pub_upload_podcard_files.php')]")) - ) - onclick = upload_button.get_attribute("onclick") or "" - # Extract URL from onclick like: window.open('https://...pub_upload_podcard_files.php?products_id=123'); - url_match = re.search(r"window\.open\(['\"]([^'\"]+)['\"]\)", onclick) - if url_match: - upload_url = url_match.group(1) - self.driver.execute_script("window.location.href = arguments[0];", upload_url) - # Wait for the upload page to load - WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "card_type_select"))) - logger.debug(f"Navigated to upload page: {self.driver.current_url}") - else: - logger.warning(f"Could not extract URL from upload button onclick: {onclick}") - except Exception as e: - logger.warning(f"Could not open upload page: {e}") + upload_button = WebDriverWait(self.driver, 15).until( + element_to_be_clickable((By.XPATH, "//button[contains(@onclick, 'pub_upload_podcard_files.php')]")) + ) + onclick = upload_button.get_attribute("onclick") or "" + # Extract URL from onclick like: window.open('https://...pub_upload_podcard_files.php?products_id=123'); + url_match = re.search(r"window\.open\(['\"]([^'\"]+)['\"]\)", onclick) + if not url_match: + raise Exception(f"Could not extract the upload page URL from the upload button (onclick: {onclick}).") + self.driver.execute_script("window.location.href = arguments[0];", url_match.group(1)) + # Wait for the upload page to load + WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "card_type_select"))) + logger.debug(f"Navigated to upload page: {self.driver.current_url}") def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: """ @@ -870,224 +835,201 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: self.set_state(States.inserting_fronts, "Selecting card type") - # Wait for and select the Euro Poker card option from the dropdown - try: - # Wait for dropdown to be present and interactable - WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "card_type_select"))) - # Re-fetch the element to avoid stale reference after page/tab switch - card_type_dropdown = self.driver.find_element(By.ID, "card_type_select") - select = Select(card_type_dropdown) - - # Find option containing "Euro Poker" (case-insensitive search) - euro_poker_option_text = None - for option in select.options: - if "euro poker" in option.text.lower(): - euro_poker_option_text = option.text - break - - if euro_poker_option_text: - select.select_by_visible_text(euro_poker_option_text) - logger.debug(f"Selected '{euro_poker_option_text}' from dropdown.") - else: - logger.warning("Could not find Euro Poker option in dropdown.") - except Exception as e: - logger.warning(f"Could not select card type: {e}") + # Wait for and select the Euro Poker card option from the dropdown. + # Any missing required element below aborts the order - a partial upload must not report success. + WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "card_type_select"))) + # Re-fetch the element to avoid stale reference after page/tab switch + select = Select(self.driver.find_element(By.ID, "card_type_select")) + + # Find option containing "Euro Poker" (case-insensitive search) + euro_poker_option_text = next( + (option.text for option in select.options if "euro poker" in option.text.lower()), None + ) + if euro_poker_option_text is None: + raise Exception("Could not find the Euro Poker option in the card type dropdown.") + select.select_by_visible_text(euro_poker_option_text) + logger.debug(f"Selected '{euro_poker_option_text}' from dropdown.") # Convert PDF path to absolute if needed if not os.path.isabs(pdf_path): pdf_path = os.path.abspath(pdf_path) - # Verify the file exists if not os.path.exists(pdf_path): - logger.error(f"PDF file not found: {pdf_path}") - return + raise Exception(f"PDF file not found: {pdf_path}") logger.debug(f"PDF file found: {pdf_path} ({os.path.getsize(pdf_path)} bytes)") # Wait for the dropzone to appear after card type selection self.set_state(States.inserting_fronts, "Uploading PDF") - try: - # Wait for the dropzone div to be present - dropzone_div = WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "uploadfiles"))) - logger.debug("Dropzone div found.") - - # Click the dropzone to initialize Dropzone's hidden input - # This should create the .dz-hidden-input element - logger.debug("Clicking dropzone to initialize hidden input...") - self.driver.execute_script("arguments[0].click();", dropzone_div) - - # Brief wait for the file dialog to appear, then send Escape to close it - time.sleep(0.5) - ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() - time.sleep(0.5) - - # Find the file input and send the file - do this in a single operation - # to avoid stale element references - logger.debug(f"Uploading PDF: {pdf_path}") - - def find_and_use_file_input() -> bool: - """Find a usable file input and send the file path to it.""" - # Strategy 1: Dropzone hidden input - try: - fi = self.driver.find_element(By.CSS_SELECTOR, ".dz-hidden-input") - logger.debug("Found Dropzone hidden input, sending file...") - fi.send_keys(pdf_path) - return True - except (sl_exc.NoSuchElementException, sl_exc.StaleElementReferenceException): - pass + dropzone_div = WebDriverWait(self.driver, 15).until(presence_of_element_located((By.ID, "uploadfiles"))) + logger.debug("Dropzone div found.") + + # Click the dropzone to initialize Dropzone's hidden input + # This should create the .dz-hidden-input element + logger.debug("Clicking dropzone to initialize hidden input...") + self.driver.execute_script("arguments[0].click();", dropzone_div) + + # Brief wait for the file dialog to appear, then send Escape to close it + time.sleep(0.5) + ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() + time.sleep(0.5) + + # Find the file input and send the file - do this in a single operation + # to avoid stale element references + logger.debug(f"Uploading PDF: {pdf_path}") + + def find_and_use_file_input() -> bool: + """Find a usable file input and send the file path to it.""" + # Strategy 1: Dropzone hidden input + try: + fi = self.driver.find_element(By.CSS_SELECTOR, ".dz-hidden-input") + logger.debug("Found Dropzone hidden input, sending file...") + fi.send_keys(pdf_path) + return True + except (sl_exc.NoSuchElementException, sl_exc.StaleElementReferenceException): + pass - # Strategy 2: Any file input that's not the fallback - try: - file_inputs = self.driver.find_elements(By.CSS_SELECTOR, "input[type='file']") - logger.debug(f"Found {len(file_inputs)} file input(s) on page.") - for fi in file_inputs: - try: - name = fi.get_attribute("name") - if name == "groups_csv": - continue - logger.debug(f"Trying file input: name={name}") - fi.send_keys(pdf_path) - return True - except sl_exc.StaleElementReferenceException: + # Strategy 2: Any file input that's not the fallback + try: + file_inputs = self.driver.find_elements(By.CSS_SELECTOR, "input[type='file']") + logger.debug(f"Found {len(file_inputs)} file input(s) on page.") + for fi in file_inputs: + try: + name = fi.get_attribute("name") + if name == "groups_csv": continue - except Exception as e: - logger.debug(f"Error with file inputs: {e}") + logger.debug(f"Trying file input: name={name}") + fi.send_keys(pdf_path) + return True + except sl_exc.StaleElementReferenceException: + continue + except Exception as e: + logger.debug(f"Error with file inputs: {e}") - # Strategy 3: Use the fallback input - try: - logger.debug("Using fallback file input...") - self.driver.execute_script("document.getElementById('dropzoneFallback').style.display = 'block';") - fi = self.driver.find_element(By.CSS_SELECTOR, "#dropzoneFallback input[type='file']") - fi.send_keys(pdf_path) - return True - except Exception as e: - logger.debug(f"Fallback input failed: {e}") + # Strategy 3: Use the fallback input + try: + logger.debug("Using fallback file input...") + self.driver.execute_script("document.getElementById('dropzoneFallback').style.display = 'block';") + fi = self.driver.find_element(By.CSS_SELECTOR, "#dropzoneFallback input[type='file']") + fi.send_keys(pdf_path) + return True + except Exception as e: + logger.debug(f"Fallback input failed: {e}") - return False + return False - # Try up to 3 times to handle any remaining stale element issues - file_sent = False - for attempt in range(3): - if find_and_use_file_input(): - file_sent = True - break - logger.debug(f"Attempt {attempt + 1} failed, retrying...") - time.sleep(0.5) + # Try up to 3 times to handle any remaining stale element issues + file_sent = False + for attempt in range(3): + if find_and_use_file_input(): + file_sent = True + break + logger.debug(f"Attempt {attempt + 1} failed, retrying...") + time.sleep(0.5) - if not file_sent: - logger.warning("Could not send file to any input element.") - return + if not file_sent: + raise Exception("Could not send the PDF to any file input element on the upload page.") - # Trigger change event on all file inputs (one of them has our file) - self.driver.execute_script( - """ - document.querySelectorAll('input[type="file"]').forEach(function(input) { - input.dispatchEvent(new Event('change', { bubbles: true })); - }); + # Trigger change event on all file inputs (one of them has our file) + self.driver.execute_script( """ + document.querySelectorAll('input[type="file"]').forEach(function(input) { + input.dispatchEvent(new Event('change', { bubbles: true })); + }); + """ + ) + logger.debug("Dispatched change event on file inputs.") + + # Wait for the upload button to become enabled (Dropzone enables it when files are queued) + try: + WebDriverWait(self.driver, 10).until( + lambda d: not d.find_element(By.ID, "dropzoneButton").get_attribute("disabled") ) - logger.debug("Dispatched change event on file inputs.") + logger.debug("Upload button is now enabled.") + except sl_exc.TimeoutException: + logger.debug("Button didn't become enabled automatically, forcing it.") - # Wait for the upload button to become enabled (Dropzone enables it when files are queued) - try: - WebDriverWait(self.driver, 10).until( - lambda d: not d.find_element(By.ID, "dropzoneButton").get_attribute("disabled") - ) - logger.debug("Upload button is now enabled.") - except sl_exc.TimeoutException: - logger.debug("Button didn't become enabled automatically, forcing it.") + # Click the upload button using JavaScript + upload_clicked = self.driver.execute_script( + """ + var btn = document.getElementById('dropzoneButton'); + if (btn) { + btn.disabled = false; + btn.style.display = 'block'; + btn.click(); + return true; + } + return false; + """ + ) + if not upload_clicked: + raise Exception("Could not find the 'Begin Card File Upload' button.") + logger.debug("Clicked 'Begin Card File Upload' button via JavaScript.") - # Click the upload button using JavaScript - upload_clicked = self.driver.execute_script( + # Also try to trigger Dropzone's processQueue as a backup + try: + self.driver.execute_script( """ - var btn = document.getElementById('dropzoneButton'); - if (btn) { - btn.disabled = false; - btn.style.display = 'block'; - btn.click(); - return true; + var dz = Dropzone.forElement('#uploadfiles'); + if (dz && dz.files && dz.files.length > 0) { + dz.processQueue(); } - return false; """ ) - if upload_clicked: - logger.debug("Clicked 'Begin Card File Upload' button via JavaScript.") - else: - logger.warning("Could not find upload button.") - - # Also try to trigger Dropzone's processQueue as a backup - try: - self.driver.execute_script( - """ - var dz = Dropzone.forElement('#uploadfiles'); - if (dz && dz.files && dz.files.length > 0) { - dz.processQueue(); - } - """ - ) - logger.debug("Triggered Dropzone processQueue.") - except Exception as e: - logger.debug(f"Could not trigger processQueue: {e}") + logger.debug("Triggered Dropzone processQueue.") + except Exception as e: + logger.debug(f"Could not trigger processQueue: {e}") - # Wait for upload to complete - look for success message - try: - WebDriverWait(self.driver, 120).until( - lambda d: "successfully uploaded" - in ( - d.find_element(By.ID, "status_messages").text.lower() - if d.find_elements(By.ID, "status_messages") - else "" - ) + # Wait for upload to complete - look for success message + try: + WebDriverWait(self.driver, 120).until( + lambda d: "successfully uploaded" + in ( + d.find_element(By.ID, "status_messages").text.lower() + if d.find_elements(By.ID, "status_messages") + else "" ) - logger.debug("PDF upload completed to DriveThruCards.") - except sl_exc.TimeoutException: - logger.warning("Could not confirm upload completion. Please verify manually.") - - # Wait for the continue button to become active. - # The button starts with an onclick handler that shows an error and returns false. - # After upload validation, the page JS replaces this handler to enable navigation. - # We detect activation by checking that the onclick no longer contains "return false". - try: + ) + except sl_exc.TimeoutException as exc: + raise Exception("The PDF upload was not confirmed by DriveThruCards within 120 seconds.") from exc + logger.debug("PDF upload completed to DriveThruCards.") + + # Wait for the continue button to become active. + # The button starts with an onclick handler that shows an error and returns false. + # After upload validation, the page JS replaces this handler to enable navigation. + # We detect activation by checking that the onclick no longer contains "return false". + def continue_button_is_active(d: Any) -> Any: + btn = d.find_element(By.ID, "continue_button") + onclick = btn.get_attribute("onclick") or "" + if "return false" in onclick: + return False + return btn - def continue_button_is_active(d: Any) -> Any: - btn = d.find_element(By.ID, "continue_button") - onclick = btn.get_attribute("onclick") or "" - if "return false" in onclick: - return False - return btn - - continue_button = WebDriverWait(self.driver, 60).until(continue_button_is_active) - logger.debug(f"Continue button activated. onclick: {continue_button.get_attribute('onclick')}") - self.driver.execute_script("arguments[0].click();", continue_button) - logger.debug("Clicked 'Click here after uploading your files' button.") - except sl_exc.TimeoutException: - logger.warning("Continue button did not activate. Please click it manually.") - - # Click the "Complete Setup" button on the next page - try: - complete_button = WebDriverWait(self.driver, 30).until(element_to_be_clickable((By.ID, "submit_id"))) - self.driver.execute_script("arguments[0].click();", complete_button) - logger.debug("Clicked 'Complete Setup' button.") - except sl_exc.TimeoutException: - logger.warning("Could not find 'Complete Setup' button. Please click it manually.") - - # Click the "buy now" link to start placing the order. - # This link has target="_blank", so navigate directly to avoid new-tab issues. - try: - buy_now_link = WebDriverWait(self.driver, 30).until( - element_to_be_clickable((By.CSS_SELECTOR, "a[href*='action=buy_now']")) - ) - buy_now_href = buy_now_link.get_attribute("href") - if buy_now_href: - self.driver.get(buy_now_href) - logger.debug("Navigated to 'buy now' page to start placing the order.") - else: - self.driver.execute_script("arguments[0].click();", buy_now_link) - logger.debug("Clicked 'buy now' link.") - except sl_exc.TimeoutException: - logger.warning("Could not find 'buy now' link. Please click it manually.") - except Exception as e: - logger.warning(f"Could not upload PDF: {e}") + try: + continue_button = WebDriverWait(self.driver, 60).until(continue_button_is_active) + except sl_exc.TimeoutException as exc: + raise Exception("The continue button did not activate after the PDF upload.") from exc + logger.debug(f"Continue button activated. onclick: {continue_button.get_attribute('onclick')}") + self.driver.execute_script("arguments[0].click();", continue_button) + logger.debug("Clicked 'Click here after uploading your files' button.") + + # Click the "Complete Setup" button on the next page + complete_button = WebDriverWait(self.driver, 30).until(element_to_be_clickable((By.ID, "submit_id"))) + self.driver.execute_script("arguments[0].click();", complete_button) + logger.debug("Clicked 'Complete Setup' button.") + + # Click the "buy now" link to start placing the order. + # This link has target="_blank", so navigate directly to avoid new-tab issues. + buy_now_link = WebDriverWait(self.driver, 30).until( + element_to_be_clickable((By.CSS_SELECTOR, "a[href*='action=buy_now']")) + ) + buy_now_href = buy_now_link.get_attribute("href") + if buy_now_href: + self.driver.get(buy_now_href) + logger.debug("Navigated to 'buy now' page to start placing the order.") + else: + self.driver.execute_script("arguments[0].click();", buy_now_link) + logger.debug("Clicked 'buy now' link.") def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> None: t = time.time() diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index b260c0fe6..fac2d7787 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -402,3 +402,103 @@ def fake_factory(headless=False, binary_location=None): assert autofill_driver.create_driver() == "standard-driver" assert captured == {"headless": False, "binary_location": None} + + +class _FakeElement: + def __init__(self, selected: bool = False) -> None: + self.sent: list = [] + self._selected = selected + + def clear(self) -> None: + pass + + def send_keys(self, value: str) -> None: + self.sent.append(value) + + def is_selected(self) -> bool: + return self._selected + + def get_attribute(self, _name: str) -> str: + return "" + + +def _timing_out_wait(*_args, **_kwargs) -> SimpleNamespace: + return SimpleNamespace(until=lambda _cond: (_ for _ in ()).throw(sl_exc.TimeoutException("no element"))) + + +def test_fill_dtc_product_form_raises_when_title_field_is_missing( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + dtc_driver.driver = SimpleNamespace() + monkeypatch.setattr("src.driver.WebDriverWait", _timing_out_wait) + + with pytest.raises(sl_exc.TimeoutException): + dtc_driver.fill_dtc_product_form(order=SimpleNamespace(name="x")) + + +def test_fill_dtc_product_form_raises_when_filter_checkbox_cannot_be_checked( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + dtc_driver.driver = SimpleNamespace(find_element=lambda _by, _value: _FakeElement(selected=False)) + monkeypatch.setattr( + "src.driver.WebDriverWait", lambda *_args, **_kwargs: SimpleNamespace(until=lambda _cond: _FakeElement()) + ) + dtc_driver.click_element_with_retry = lambda _element: False + + with pytest.raises(Exception, match="filter checkbox"): + dtc_driver.fill_dtc_product_form(order=SimpleNamespace(name="x")) + + +def test_submit_dtc_description_page_raises_when_button_is_missing( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + dtc_driver.driver = SimpleNamespace() + monkeypatch.setattr("src.driver.WebDriverWait", _timing_out_wait) + + with pytest.raises(sl_exc.TimeoutException): + dtc_driver.submit_dtc_description_page() + + +def test_open_dtc_upload_page_raises_when_upload_url_cannot_be_extracted( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + button_without_url = SimpleNamespace(get_attribute=lambda _name: "showError(); return false;") + dtc_driver.driver = SimpleNamespace() + monkeypatch.setattr( + "src.driver.WebDriverWait", lambda *_args, **_kwargs: SimpleNamespace(until=lambda _cond: button_without_url) + ) + + with pytest.raises(Exception, match="upload page URL"): + dtc_driver.open_dtc_upload_page() + + +def test_select_card_type_and_upload_pdf_raises_when_euro_poker_option_is_missing( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + dtc_driver.driver = SimpleNamespace(find_element=lambda _by, _value: _FakeElement()) + monkeypatch.setattr( + "src.driver.WebDriverWait", lambda *_args, **_kwargs: SimpleNamespace(until=lambda _cond: _FakeElement()) + ) + monkeypatch.setattr( + "src.driver.Select", lambda _element: SimpleNamespace(options=[SimpleNamespace(text="Jumbo Card(s)")]) + ) + + with pytest.raises(Exception, match="Euro Poker"): + dtc_driver.select_card_type_and_upload_pdf(pdf_path="/tmp/order.pdf") + + +def test_select_card_type_and_upload_pdf_raises_when_pdf_is_missing( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver, tmp_path +) -> None: + euro_poker_select = SimpleNamespace( + options=[SimpleNamespace(text="Premium Euro Poker Card(s)")], + select_by_visible_text=lambda _text: None, + ) + dtc_driver.driver = SimpleNamespace(find_element=lambda _by, _value: _FakeElement()) + monkeypatch.setattr( + "src.driver.WebDriverWait", lambda *_args, **_kwargs: SimpleNamespace(until=lambda _cond: _FakeElement()) + ) + monkeypatch.setattr("src.driver.Select", lambda _element: euro_poker_select) + + with pytest.raises(Exception, match="PDF file not found"): + dtc_driver.select_card_type_and_upload_pdf(pdf_path=str(tmp_path / "does-not-exist.pdf")) From 625bacc18c27dbe8fcdf21ff2fd51e6006548b27 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 01:52:50 -0700 Subject: [PATCH 45/76] Generate genuine PDF/X-1a output and verify it before reporting success The previous Ghostscript invocation used bare -dPDFX (PDF/X-3 semantics) with no definition file, so the output was a plain CMYK PDF 1.3 that was reported as PDF/X-1a. Now generates a PDF/X-1a definition file (per Ghostscript's lib/PDFX_def.ps) declaring GTS_PDFXVersion/Conformance, Trapped and a US Web Coated (SWOP) output intent with the ICC profile embedded when available, invokes gs with -dPDFX=1, and preflights the output for the PDF/X-1a markers before accepting it. Verified against Ghostscript 10.07.1 (real-gs test included; gs adds the required ArtBox automatically). --- desktop-tool/src/pdf_maker.py | 74 ++++++++++++++++++++++++- desktop-tool/tests/test_desktop_tool.py | 51 ++++++++++++++++- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 0816fd17d..946246ea5 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -3,6 +3,7 @@ import shutil import subprocess import tempfile +from pathlib import Path from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass from typing import Optional @@ -54,6 +55,59 @@ def get_ghostscript_version(gs_path: str) -> Optional[str]: return version[0] if version else None +def _postscript_string(value: str) -> str: + return "(" + value.replace("\\", r"\\").replace("(", r"\(").replace(")", r"\)") + ")" + + +def _generate_pdfx_definition(title: str, icc_profile_path: Optional[str]) -> str: + """ + Generate the PDF/X-1a definition PostScript (based on Ghostscript's lib/PDFX_def.ps) which + supplies the metadata and output intent that the PDF/X-1a standard requires. The output is + tagged for the US Web Coated (SWOP) printing condition which DriveThruCards expects; when an + ICC profile is available it is embedded as the output intent's destination profile. + """ + lines = [ + "%!", + "% PDF/X-1a definition file generated by the MPC Autofill desktop tool.", + "[ /GTS_PDFXVersion (PDF/X-1:2001)", + " /GTS_PDFXConformance (PDF/X-1a:2001)", + f" /Title {_postscript_string(title)}", + " /Trapped /False", + " /DOCINFO pdfmark", + "[/_objdef {OutputIntent_PDFX} /type /dict /OBJ pdfmark", + "[{OutputIntent_PDFX} <<", + " /Type /OutputIntent", + " /S /GTS_PDFX", + " /OutputCondition (US Web Coated \\(SWOP\\))", + " /Info (U.S. Web Coated \\(SWOP\\) v2)", + " /OutputConditionIdentifier (CGATS TR 001)", + " /RegistryName (http://www.color.org)", + ">> /PUT pdfmark", + ] + if icc_profile_path: + posix_icc_path = Path(icc_profile_path).as_posix() + lines[2:2] = [f"/ICCProfile {_postscript_string(posix_icc_path)} def"] + lines += [ + "[/_objdef {icc_PDFX} /type /stream /OBJ pdfmark", + "[{icc_PDFX} << /N 4 >> /PUT pdfmark", + "[{icc_PDFX} ICCProfile (r) file /PUT pdfmark", + "[{OutputIntent_PDFX} << /DestOutputProfile {icc_PDFX} >> /PUT pdfmark", + ] + lines += ["[{Catalog} << /OutputIntents [ {OutputIntent_PDFX} ] >> /PUT pdfmark", ""] + return "\n".join(lines) + + +def verify_pdfx_output(output_path: str) -> bool: + """ + Lightweight preflight of the converted file: Ghostscript writes the document info dictionary + and output intent uncompressed, so their absence proves the file is not the PDF/X-1a we asked + for (a zero exit code from Ghostscript alone does not). + """ + with open(output_path, "rb") as f: + contents = f.read() + return b"(PDF/X-1a:2001)" in contents and b"/OutputIntents" in contents + + def convert_pdf_to_pdfx( source_path: str, output_path: str, @@ -73,6 +127,14 @@ def convert_pdf_to_pdfx( ) os.close(fd) + with tempfile.NamedTemporaryFile("w", suffix=".ps", delete=False) as pdfx_definition_file: + pdfx_definition_file.write( + _generate_pdfx_definition( + title=os.path.splitext(os.path.basename(output_path))[0], icc_profile_path=config.icc_profile_path + ) + ) + pdfx_definition_path = pdfx_definition_file.name + cmd = [ gs_path, "-dBATCH", @@ -80,8 +142,7 @@ def convert_pdf_to_pdfx( "-dNOSAFER", # Allow file system access for ICC profile and output "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.3", - "-dPDFX", - "-dPDFXNoTrimBox", + "-dPDFX=1", # PDF/X-1; conformance level PDF/X-1a:2001 is declared in the definition file "-dDownsampleColorImages=false", "-dDownsampleGrayImages=false", "-dDownsampleMonoImages=false", @@ -91,7 +152,7 @@ def convert_pdf_to_pdfx( ] if config.icc_profile_path: cmd.append(f"-sOutputICCProfile={config.icc_profile_path}") - cmd.append(source_path) + cmd += [pdfx_definition_path, source_path] logger.debug(f"Ghostscript command: {' '.join(cmd)}") try: @@ -103,9 +164,16 @@ def convert_pdf_to_pdfx( f"stderr: {result.stderr}" ) return False + if not verify_pdfx_output(temporary_output_path): + logger.warning( + "Ghostscript exited successfully but the output failed PDF/X-1a verification " + "(missing PDF/X-1a version marker or output intent)." + ) + return False os.replace(temporary_output_path, output_path) return True finally: + os.unlink(pdfx_definition_path) if os.path.exists(temporary_output_path): os.unlink(temporary_output_path) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 9ae36ca16..c7709a930 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -608,7 +608,7 @@ def test_convert_pdf_to_pdfx_writes_output_atomically(monkeypatch: pytest.Monkey def fake_run(cmd, capture_output=True, text=True): output_arg = next(arg for arg in cmd if arg.startswith("-sOutputFile=")) - Path(output_arg.split("=", 1)[1]).write_bytes(b"pdfx") + Path(output_arg.split("=", 1)[1]).write_bytes(b"%PDF-1.3 (PDF/X-1a:2001) /OutputIntents") return SimpleNamespace(returncode=0, stdout="", stderr="") monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") @@ -619,7 +619,7 @@ def fake_run(cmd, capture_output=True, text=True): str(output_path), autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), ) - assert output_path.read_bytes() == b"pdfx" + assert output_path.read_bytes() == b"%PDF-1.3 (PDF/X-1a:2001) /OutputIntents" assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] @@ -648,6 +648,53 @@ def fake_run(cmd, capture_output=True, text=True): assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] +def test_convert_pdf_to_pdfx_rejects_output_missing_pdfx_markers( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + source_path = tmp_path / "source.pdf" + output_path = tmp_path / "output.pdf" + source_path.write_bytes(b"source") + output_path.write_bytes(b"previous") + + def fake_run(cmd, capture_output=True, text=True): + # Zero exit code, but the output is a plain PDF rather than PDF/X-1a. + output_arg = next(arg for arg in cmd if arg.startswith("-sOutputFile=")) + Path(output_arg.split("=", 1)[1]).write_bytes(b"%PDF-1.3 plain") + return SimpleNamespace(returncode=0, stdout="", stderr="") + + monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") + monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) + + assert not convert_pdf_to_pdfx( + str(source_path), + str(output_path), + autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + ) + assert output_path.read_bytes() == b"previous" + + +@pytest.mark.skipif(src.pdf_maker.get_ghostscript_path() is None, reason="Ghostscript is not installed") +def test_convert_pdf_to_pdfx_produces_verified_pdfx_with_real_ghostscript(tmp_path) -> None: + from fpdf import FPDF + + image_path = tmp_path / "card.jpg" + Image.new("RGB", (819, 1113), (200, 30, 40)).save(image_path, "JPEG") + pdf = FPDF("P", "in", (2.73, 3.71)) + pdf.add_page() + pdf.image(str(image_path), x=0, y=0, w=2.73, h=3.71) + source_path = tmp_path / "source.pdf" + pdf.output(str(source_path)) + output_path = tmp_path / "output_pdfx.pdf" + + assert convert_pdf_to_pdfx(str(source_path), str(output_path), autofill_cli.PdfXConversionConfig()) + + contents = output_path.read_bytes() + assert b"(PDF/X-1:2001)" in contents # GTS_PDFXVersion + assert b"(PDF/X-1a:2001)" in contents # GTS_PDFXConformance + assert b"/OutputIntents" in contents + assert b"CGATS TR 001" in contents + + def test_pdf_exporter_appends_pdfx_on_success(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: def do_nothing(_): return None From 71551e5c0ef8f827c40990e236c1da033fdb0c77 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 01:56:27 -0700 Subject: [PATCH 46/76] Locate or download the SWOP ICC profile instead of bundling Adobe's Adobe's Color Profile License permits embedding the profile in image files but forbids redistributing it bundled with application software, so remove USWebCoatedSWOP.icc and the EULA PDF from the repo. At runtime the tool now finds an installed copy (Adobe/ColorSync/system colour directories) or, with the user's consent, downloads Adobe's own end-user profile bundle (checksum-verified, byte-identical to the previously bundled profile) and caches it in ~/.mpc-autofill. Falls back to Ghostscript's default CMYK conversion with a warning. Also fixes the Nuitka onefile path bug: the old bundled-profile lookup resolved from sys.argv[0] where compiled builds extract data files beside __file__, so packaged builds could never find the profile. --- .../assets/icc/Adobe-Color-Profile-EULA.pdf | Bin 64689 -> 0 bytes desktop-tool/assets/icc/USWebCoatedSWOP.icc | Bin 557168 -> 0 bytes desktop-tool/autofill.py | 38 +++----- desktop-tool/src/icc.py | 91 ++++++++++++++++++ desktop-tool/tests/test_desktop_tool.py | 67 ++++++++++++- 5 files changed, 171 insertions(+), 25 deletions(-) delete mode 100644 desktop-tool/assets/icc/Adobe-Color-Profile-EULA.pdf delete mode 100644 desktop-tool/assets/icc/USWebCoatedSWOP.icc create mode 100644 desktop-tool/src/icc.py diff --git a/desktop-tool/assets/icc/Adobe-Color-Profile-EULA.pdf b/desktop-tool/assets/icc/Adobe-Color-Profile-EULA.pdf deleted file mode 100644 index be14c29e75f420027fed88c7d50db1797a3bc913..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64689 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 z 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

g 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%0AjX_uhU=1YP`)2{^$H~tbd+yH`x>;KMP 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⁡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 z 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 literal 0 HcmV?d00001 diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index bd82abc61..611d66f3a 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -1,6 +1,8 @@ # nuitka-project: --mode=onefile # nuitka-project: --include-data-files=client_secrets.json=client_secrets.json # nuitka-project: --include-data-files=post-launch.html=post-launch.html +# nuitka-project: --include-data-files=assets/icc/USWebCoatedSWOP.icc=assets/icc/USWebCoatedSWOP.icc +# nuitka-project: --include-data-files=assets/icc/Adobe-Color-Profile-EULA.pdf=assets/icc/Adobe-Color-Profile-EULA.pdf # nuitka-project: --noinclude-pytest-mode=nofollow # nuitka-project: --windows-icon-from-ico=favicon.ico # nuitka-project-if: {OS} == "Windows": @@ -17,8 +19,11 @@ import logging import os +import shutil +import subprocess import sys from contextlib import nullcontext +from pathlib import Path from typing import Optional, Union import click @@ -31,7 +36,7 @@ from src.io import DEFAULT_WORKING_DIRECTORY, create_image_directory_if_not_exists from src.logging import configure_loggers, logger from src.order import CardOrder, aggregate_and_split_orders -from src.pdf_maker import PdfExporter +from src.pdf_maker import PdfExporter, PdfXConversionConfig, get_ghostscript_path, get_ghostscript_version from src.processing import ImagePostProcessingConfig from src.web_server import WebServer @@ -47,6 +52,86 @@ def prompt_if_no_arguments(prompt: str) -> Union[str, bool]: return f"{prompt} (Press Enter if you're not sure.)" if len(sys.argv) == 1 else False +def get_default_dtc_icc_profile() -> Optional[str]: + if "__compiled__" in globals(): + candidate = Path(sys.argv[0]).resolve().parent / "assets/icc/USWebCoatedSWOP.icc" + else: + candidate = Path(__file__).resolve().parent / "assets/icc/USWebCoatedSWOP.icc" + return str(candidate) if candidate.is_file() else None + + +def ensure_ghostscript_available() -> str: + while True: + gs_path = get_ghostscript_path() + if gs_path: + version = get_ghostscript_version(gs_path) + if version: + logger.info(f"Ghostscript detected: {bold(version)} at {bold(gs_path)}") + else: + logger.info(f"Ghostscript detected at {bold(gs_path)}") + return gs_path + + logger.info( + "DriveThruCards export requires Ghostscript for PDF/X-1a compliance." + ) + + should_install = click.confirm("Install Ghostscript now?", default=True) + if should_install: + if sys.platform.startswith("darwin"): + if shutil.which("brew") is None: + logger.info("Homebrew not found. Please install Homebrew, then re-run.") + else: + logger.info("Installing Ghostscript via Homebrew...") + result = subprocess.run(["brew", "install", "ghostscript"], check=False) + if result.returncode != 0: + logger.warning("Ghostscript installation via Homebrew failed.") + elif sys.platform.startswith("win"): + if shutil.which("winget") is None: + logger.info( + "winget not found. Please install Ghostscript from " + "https://ghostscript.com/releases/gsdnld.html and ensure it's on PATH." + ) + else: + logger.info("Installing Ghostscript via winget...") + result = subprocess.run( + ["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"], + check=False, + ) + if result.returncode != 0: + logger.warning("Ghostscript installation via winget failed.") + else: + if shutil.which("sudo") is None: + logger.info( + "sudo not found. Please install Ghostscript with your package manager manually." + ) + elif shutil.which("apt") is not None: + logger.info("Installing Ghostscript via apt...") + result = subprocess.run(["sudo", "apt", "install", "-y", "ghostscript"], check=False) + if result.returncode != 0: + logger.warning("Ghostscript installation via apt failed.") + elif shutil.which("dnf") is not None: + logger.info("Installing Ghostscript via dnf...") + result = subprocess.run(["sudo", "dnf", "install", "-y", "ghostscript"], check=False) + if result.returncode != 0: + logger.warning("Ghostscript installation via dnf failed.") + elif shutil.which("yum") is not None: + logger.info("Installing Ghostscript via yum...") + result = subprocess.run(["sudo", "yum", "install", "-y", "ghostscript"], check=False) + if result.returncode != 0: + logger.warning("Ghostscript installation via yum failed.") + else: + logger.info("No supported package manager found. Please install Ghostscript manually.") + else: + logger.info( + "Please install Ghostscript, then return here to continue.\n" + "macOS: brew install ghostscript\n" + "Windows: https://ghostscript.com/releases/gsdnld.html\n" + "Linux: use your package manager (e.g., apt install ghostscript)." + ) + + input("Press Enter to re-check for Ghostscript, or Ctrl+C to exit.") + + @click.command(context_settings={"show_default": True}) @click.option("-d", "--directory", default=None, help="The directory to search for order XML files.") @click.option( @@ -127,6 +212,11 @@ def prompt_if_no_arguments(prompt: str) -> Union[str, bool]: "\nhttps://pillow.readthedocs.io/en/latest/handbook/concepts.html#filters-comparison-table" ), ) +@click.option( + "--dtc-icc-profile", + default=None, + help="Optional ICC profile path to embed in DriveThruCards exports.", +) @click.option( "--combine-orders/--no-combine-orders", default=True, @@ -175,6 +265,7 @@ def main( image_post_processing: bool, max_dpi: int, downscale_alg: str, + dtc_icc_profile: Optional[str], combine_orders: bool, log_level: str, write_debug_logs: bool, @@ -208,17 +299,59 @@ def main( logger.info("System sleep is being prevented during this execution.") if image_post_processing: logger.info("Images are being post-processed during this execution.") + target_site = TargetSites[site] post_processing_config = ( ImagePostProcessingConfig(max_dpi=max_dpi, downscale_alg=ImageResizeMethods[downscale_alg]) if image_post_processing else None ) - if exportpdf: + if target_site == TargetSites.DriveThruCards: + ensure_ghostscript_available() + using_default_icc = dtc_icc_profile is None + resolved_icc_profile = dtc_icc_profile or get_default_dtc_icc_profile() + if resolved_icc_profile is None: + raise Exception( + "Default DriveThruCards ICC profile was not found. " + "Ensure assets/icc/USWebCoatedSWOP.icc is available or pass --dtc-icc-profile." + ) + if resolved_icc_profile and not os.path.isfile(resolved_icc_profile): + raise Exception( + f"DriveThruCards ICC profile path does not exist or is not a file: {bold(resolved_icc_profile)}" + ) + icc_source = "bundled default" if using_default_icc else "user-provided" + logger.info(f"DriveThruCards ICC profile ({icc_source}): {bold(resolved_icc_profile)}") + # Keep images as RGB JPEG during PDF generation - fpdf can embed these directly + # with DCT compression. Ghostscript will handle CMYK conversion with the ICC + # profile during PDF/X-1a conversion, which produces better results than + # pre-converting to CMYK (which fpdf re-encodes with FlateDecode, bloating file size). + dtc_post_processing_config = ImagePostProcessingConfig( + max_dpi=300, + downscale_alg=ImageResizeMethods[downscale_alg], + output_format="JPEG", + convert_to_cmyk=False, # Let Ghostscript handle CMYK conversion + ) + orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) + for i, order in enumerate(orders, start=1): + exporter = PdfExporter( + order=order, + export_mode="drive_thru_cards", + pdfx_config=PdfXConversionConfig(icc_profile_path=resolved_icc_profile), + ) + pdf_paths = exporter.execute(post_processing_config=dtc_post_processing_config) + dtc_pdf_path = next((path for path in reversed(pdf_paths) if path.endswith("_pdfx.pdf")), pdf_paths[0]) + AutofillDriver( + browser=Browsers[browser], + target_site=target_site, + binary_location=binary_location, + starting_url=target_site.value.starting_url, + ).execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) + if i < len(orders): + input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") + elif exportpdf: PdfExporter(order=CardOrder.from_xmls_in_folder(working_directory=working_directory)[0]).execute( post_processing_config=post_processing_config ) else: - target_site = TargetSites[site] card_orders = aggregate_and_split_orders( orders=CardOrder.from_xmls_in_folder(working_directory=working_directory), target_site=target_site, diff --git a/desktop-tool/src/constants.py b/desktop-tool/src/constants.py index 663212770..d2f8ffe36 100644 --- a/desktop-tool/src/constants.py +++ b/desktop-tool/src/constants.py @@ -152,6 +152,32 @@ def accept_settings_url(self) -> str: return self.format_url(self.accept_settings_url_route) +@attr.s +class DriveThruCardsSelectors: + product_url: str = attr.ib() + pdf_upload_input_selector: str = attr.ib(default="input[type='file']") + pdf_upload_input_index: int = attr.ib(default=0) + quantity_selector: str = attr.ib(default="") + add_to_cart_selector: str = attr.ib(default="") + continue_selector: str = attr.ib(default="") + # Login selectors - two step process: click login button, then click "Go to Log in" link + login_button_selector: str = attr.ib(default="button[data-cy='login'], button[aria-label='Log In']") + go_to_login_selector: str = attr.ib(default="a[href='/en/']") + logged_in_indicator_selector: str = attr.ib(default="button[data-cy='logout'], button[aria-label='Log Out'], a[href*='logout'], a[href*='account'], .my-account") + + +@attr.s +class DriveThruCardsSite: + base_url: str = attr.ib(default="https://www.drivethrucards.com") + selectors: DriveThruCardsSelectors = attr.ib( + default=attr.Factory(lambda: DriveThruCardsSelectors(product_url="https://www.drivethrucards.com")) + ) + + @property + def starting_url(self) -> str: + return self.selectors.product_url + + class TargetSites(Enum): MakePlayingCards = TargetSite( base_url="https://www.makeplayingcards.com", starting_url_route="design/custom-blank-card.html" @@ -214,6 +240,12 @@ class TargetSites(Enum): Cardstocks.P10: "Plastique (100%)", }, ) + DriveThruCards = DriveThruCardsSite( + selectors=DriveThruCardsSelectors( + product_url="https://www.drivethrucards.com", + pdf_upload_input_selector="input[type='file']", + ) + ) DPI_HEIGHT_RATIO = 300 / 1110 # TODO: share this between desktop tool and backend diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index f29e26e03..7a87a6344 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -14,6 +14,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support.expected_conditions import ( + presence_of_element_located, invisibility_of_element, text_to_be_present_in_element, visibility_of_element_located, @@ -202,6 +203,11 @@ def wait(self) -> bool: logger.debug(e) return False + def wait_for_selector(self, selector: str, timeout_seconds: int = 30) -> None: + WebDriverWait(self.driver, timeout_seconds, poll_frequency=0.2).until( + presence_of_element_located((By.CSS_SELECTOR, selector)) + ) + def set_state(self, state: str, action: Optional[str] = None) -> None: self.state = state self.action = action @@ -323,6 +329,178 @@ def handle_alert(self) -> None: # endregion + # region DriveThruCards + + def wait_for_cloudflare_challenge(self, timeout_seconds: int = 300) -> None: + """ + Wait for the Cloudflare challenge to be completed by waiting for site content to appear. + Checks multiple indicators that the actual site has loaded. + """ + self.set_state(States.defining_order, "Waiting for site to load") + logger.info( + "Waiting for DriveThruCards to load...\n" + "If you see a Cloudflare captcha, please complete it." + ) + + start_time = time.time() + while time.time() - start_time < timeout_seconds: + try: + # Check page title - Cloudflare shows "Just a moment..." + title = self.driver.title.lower() + if "just a moment" in title or "attention required" in title: + time.sleep(1) + continue + + # Check if we're on the actual DriveThruCards site + # Look for any of these indicators that the real site has loaded + page_source = self.driver.page_source + + # DriveThruCards specific indicators + site_indicators = [ + "drivethrucards", + "Log In", # The login button text + "data-cy=\"login\"", # The login button attribute + "OneBookShelf", # Parent company + ] + + if any(indicator in page_source for indicator in site_indicators): + logger.info("Site loaded successfully!") + time.sleep(2) # Give page time to fully render + return + + except Exception as e: + logger.debug(f"Error checking page: {e}") + time.sleep(1) + + logger.warning(f"Timeout after {timeout_seconds}s waiting for site to load. Attempting to continue anyway.") + + def is_dtc_user_authenticated(self) -> bool: + """Check if the user is logged in to DriveThruCards.""" + selectors = self.target_site.value.selectors + try: + # Look for logout link or account link as indicator of being logged in + logged_in_elements = self.driver.find_elements( + By.CSS_SELECTOR, selectors.logged_in_indicator_selector + ) + return len(logged_in_elements) > 0 + except Exception: + return False + + def authenticate_dtc(self) -> None: + """ + Handle DriveThruCards login flow. + Two-step process: click login button, then click "Go to Log in" link. + Waits for user to complete authentication. + """ + selectors = self.target_site.value.selectors + + if self.is_dtc_user_authenticated(): + logger.info("Already logged in to DriveThruCards.") + return + + self.set_state(States.defining_order, "Awaiting DriveThruCards login") + logger.info("Please log in to your DriveThruCards account.") + + # Step 1: Try to find and click the login button + try: + login_button = WebDriverWait(self.driver, 10).until( + presence_of_element_located((By.CSS_SELECTOR, selectors.login_button_selector)) + ) + logger.info("Clicking the login button...") + login_button.click() + time.sleep(2) # Wait for modal/dialog to appear + except Exception as e: + logger.debug(f"Could not find/click login button: {e}") + logger.info( + "Could not find login button automatically.\n" + "Please click the login button manually." + ) + + # Step 2: Try to find and click the "Go to Log in" link + try: + go_to_login_link = WebDriverWait(self.driver, 10).until( + presence_of_element_located((By.CSS_SELECTOR, selectors.go_to_login_selector)) + ) + logger.info("Clicking 'Go to Log in' link...") + go_to_login_link.click() + time.sleep(2) # Wait for login page to load + except Exception as e: + logger.debug(f"Could not find/click 'Go to Log in' link: {e}") + logger.info( + "Could not find 'Go to Log in' link automatically.\n" + "Please navigate to the login page manually." + ) + + logger.info( + "Please complete the login process in the browser window.\n" + "The tool will automatically continue once you're logged in." + ) + + # Wait for user to complete login (timeout after 5 minutes) + timeout_seconds = 300 + start_time = time.time() + while time.time() - start_time < timeout_seconds: + time.sleep(2) + if self.is_dtc_user_authenticated(): + logger.info("Successfully logged in to DriveThruCards!") + time.sleep(1) # Give the page a moment to settle + return + + logger.warning( + f"Login timeout after {timeout_seconds}s. " + "Please ensure you're logged in before continuing." + ) + + def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> None: + t = time.time() + selectors = self.target_site.value.selectors + self.set_state(States.defining_order, "Opening DriveThruCards") + self.driver.get(self.target_site.value.starting_url) + + # Handle Cloudflare challenge if present + self.wait_for_cloudflare_challenge() + + # Handle login + self.authenticate_dtc() + + if selectors.quantity_selector: + try: + self.wait_for_selector(selectors.quantity_selector) + quantity_input = self.driver.find_element(By.CSS_SELECTOR, selectors.quantity_selector) + quantity_input.clear() + quantity_input.send_keys(str(order.details.quantity)) + except sl_exc.WebDriverException as exc: + logger.warning(f"Failed to set DriveThruCards quantity automatically: {exc}") + + self.set_state(States.inserting_fronts, "Uploading PDF") + self.wait_for_selector(selectors.pdf_upload_input_selector) + upload_inputs = self.driver.find_elements(By.CSS_SELECTOR, selectors.pdf_upload_input_selector) + if len(upload_inputs) <= selectors.pdf_upload_input_index: + raise Exception( + f"DriveThruCards PDF upload input not found at index {selectors.pdf_upload_input_index} " + f"using selector {selectors.pdf_upload_input_selector}." + ) + upload_inputs[selectors.pdf_upload_input_index].send_keys(pdf_path) + logger.info("DriveThruCards PDF uploaded. Please confirm the preview and continue checkout.") + + if selectors.continue_selector: + try: + self.wait_for_selector(selectors.continue_selector) + self.driver.find_element(By.CSS_SELECTOR, selectors.continue_selector).click() + except sl_exc.WebDriverException: + logger.warning("DriveThruCards continue button could not be clicked automatically.") + + if selectors.add_to_cart_selector: + try: + self.wait_for_selector(selectors.add_to_cart_selector) + self.driver.find_element(By.CSS_SELECTOR, selectors.add_to_cart_selector).click() + except sl_exc.WebDriverException: + logger.warning("DriveThruCards add-to-cart button could not be clicked automatically.") + + log_hours_minutes_seconds_elapsed(t) + + # endregion + # region uploading @exception_retry_skip_handler diff --git a/desktop-tool/src/io.py b/desktop-tool/src/io.py index a081a0c74..35767d104 100644 --- a/desktop-tool/src/io.py +++ b/desktop-tool/src/io.py @@ -14,7 +14,12 @@ import src.constants as constants from src.logging import logger -from src.processing import ImagePostProcessingConfig, post_process_image +from src.processing import ( + ImagePostProcessingConfig, + get_post_processed_path, + post_process_image, + save_processed_image, +) thread_local = threading.local() # Should only be called once per thread @@ -194,8 +199,12 @@ def download_google_drive_file( if post_processing_config is not None: logger.debug(f"Post-processing {drive_id}...") - processed_image = post_process_image(raw_image=file_bytes, config=post_processing_config) - processed_image.save(file_path) + output_path = get_post_processed_path(file_path=file_path, config=post_processing_config) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + processed_image, icc_profile_bytes = post_process_image(raw_image=file_bytes, config=post_processing_config) + save_processed_image( + processed_image, file_path=output_path, config=post_processing_config, icc_profile_bytes=icc_profile_bytes + ) else: # Save the bytes directly to disk - avoid reading in pillow in case any quality degradation occurs with open(file_path, "wb") as f: diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index ad03df03a..024a6a4aa 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -27,7 +27,12 @@ get_image_directory, ) from src.logging import logger -from src.processing import ImagePostProcessingConfig +from src.processing import ( + ImagePostProcessingConfig, + get_post_processed_path, + post_process_image, + save_processed_image, +) from src.utils import unpack_element @@ -179,16 +184,77 @@ def download_image( post_processing_config: Optional[ImagePostProcessingConfig], ) -> None: try: + def is_image_valid(file_path: str) -> bool: + try: + from PIL import Image + + with Image.open(file_path) as img: + img.verify() + return True + except Exception: + return False + + def remove_if_exists(file_path: str) -> None: + try: + if os.path.isfile(file_path): + os.remove(file_path) + except Exception: + pass + + source_path = self.file_path + if post_processing_config and self.file_path: + self.file_path = get_post_processed_path(self.file_path, post_processing_config) + os.makedirs(os.path.dirname(self.file_path), exist_ok=True) if self.source_type == SourceType.LOCAL_FILE: if self.file_exists() and not self.errored: - self.downloaded = True + if is_image_valid(cast(str, self.file_path)): + self.downloaded = True + else: + logger.info( + f"Local file '{bold(self.name)}' appears to be corrupted at path:\n" + f"{bold(self.file_path)}\n" + ) + self.errored = True + elif post_processing_config and source_path and file_exists(source_path) and not self.errored: + with open(source_path, "rb") as f: + raw_image = f.read() + processed_image, icc_profile_bytes = post_process_image( + raw_image=raw_image, config=post_processing_config + ) + save_processed_image( + processed_image, + file_path=cast(str, self.file_path), + config=post_processing_config, + icc_profile_bytes=icc_profile_bytes, + ) + if is_image_valid(cast(str, self.file_path)): + self.downloaded = True + else: + logger.info( + f"Processed local file '{bold(self.name)}' appears to be corrupted at path:\n" + f"{bold(self.file_path)}\n" + ) + self.errored = True else: logger.info(f"Local file '{bold(self.name)}' does not exist at path:\n{bold(self.drive_id)}\n") elif self.source_type == SourceType.GOOGLE_DRIVE: - if not self.file_exists() and not self.errored and self.file_path is not None: - self.errored = not download_google_drive_file( - drive_id=self.drive_id, file_path=self.file_path, post_processing_config=post_processing_config - ) + if self.file_path is not None and not self.errored: + for attempt in range(2): + if not self.file_exists(): + self.errored = not download_google_drive_file( + drive_id=self.drive_id, + file_path=self.file_path, + post_processing_config=post_processing_config, + ) + if self.file_exists() and not self.errored and is_image_valid(cast(str, self.file_path)): + break + if self.file_exists(): + remove_if_exists(cast(str, self.file_path)) + logger.info( + f"Downloaded image '{bold(self.name)}' appears corrupted. Retrying download..." + ) + if attempt == 1: + self.errored = True if self.file_exists() and not self.errored: self.downloaded = True diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 3c722748a..e05ec7989 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -1,5 +1,10 @@ +import io import os +import shutil +import subprocess +import tempfile from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass from typing import Optional import attr @@ -11,7 +16,81 @@ from src.formatting import bold from src.logging import logger from src.order import CardOrder -from src.processing import ImagePostProcessingConfig +from src.processing import ( + ImagePostProcessingConfig, + calculate_dtc_target_pixel_size, + post_process_image, + save_processed_image, +) + + +@dataclass +class PdfXConversionConfig: + icc_profile_path: Optional[str] = None + ghostscript_path: Optional[str] = None + + +def _resolve_ghostscript_path(explicit_path: Optional[str]) -> Optional[str]: + if explicit_path: + return explicit_path + for candidate in ["gs", "gswin64c", "gswin32c"]: + if resolved := shutil.which(candidate): + return resolved + return None + + +def get_ghostscript_path(explicit_path: Optional[str] = None) -> Optional[str]: + return _resolve_ghostscript_path(explicit_path) + + +def get_ghostscript_version(gs_path: str) -> Optional[str]: + try: + result = subprocess.run([gs_path, "-version"], capture_output=True, text=True, check=False) + except Exception: + return None + version = result.stdout.strip().splitlines() + return version[0] if version else None + + +def convert_pdf_to_pdfx( + source_path: str, + output_path: str, + config: PdfXConversionConfig, +) -> bool: + gs_path = _resolve_ghostscript_path(config.ghostscript_path) + if not gs_path: + logger.warning("Ghostscript was not found. Skipping PDF/X-1a conversion.") + return False + + cmd = [ + gs_path, + "-dBATCH", + "-dNOPAUSE", + "-dSAFER", + "-sDEVICE=pdfwrite", + "-dCompatibilityLevel=1.3", + "-dPDFX", + "-dPDFXNoTrimBox", + "-dDownsampleColorImages=false", + "-dDownsampleGrayImages=false", + "-dDownsampleMonoImages=false", + "-sProcessColorModel=DeviceCMYK", + "-sColorConversionStrategy=CMYK", + f"-sOutputFile={output_path}", + ] + if config.icc_profile_path: + cmd.append(f"-sOutputICCProfile={config.icc_profile_path}") + cmd.append(source_path) + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + logger.warning( + "Ghostscript failed to convert PDF/X-1a.\n" + f"stdout: {result.stdout}\n" + f"stderr: {result.stderr}" + ) + return False + return True @attr.s @@ -27,10 +106,14 @@ class PdfExporter: save_path: str = attr.ib(default="") separate_faces: bool = attr.ib(default=False) current_face: str = attr.ib(default="all") + export_mode: str = attr.ib(default="standard") + pdfx_config: Optional[PdfXConversionConfig] = attr.ib(default=None) + image_post_processing_config: Optional[ImagePostProcessingConfig] = attr.ib(default=None) manager: enlighten.Manager = attr.ib(init=False, default=attr.Factory(enlighten.get_manager)) status_bar: enlighten.StatusBar = attr.ib(init=False, default=False) download_bar: enlighten.Counter = attr.ib(init=False, default=None) processed_bar: enlighten.Counter = attr.ib(init=False, default=None) + saved_files: list[str] = attr.ib(init=False, factory=list) def configure_bars(self) -> None: num_images = len(self.order.fronts.cards_by_id) + len(self.order.backs.cards_by_id) @@ -52,7 +135,13 @@ def set_state(self, state: str) -> None: self.status_bar.refresh() def __attrs_post_init__(self) -> None: - self.ask_questions() + if self.export_mode == "drive_thru_cards": + self.card_width_in_inches = 63 / 25.4 + self.card_height_in_inches = 88 / 25.4 + self.separate_faces = False + self.number_of_cards_per_file = max(1, self.order.details.quantity) + else: + self.ask_questions() self.configure_bars() self.generate_file_path() @@ -104,18 +193,54 @@ def generate_pdf(self) -> None: def add_image(self, image_path: str) -> None: self.pdf.add_page() - self.pdf.image(image_path, x=0, y=0, w=self.card_width_in_inches, h=self.card_height_in_inches) + if self.export_mode == "drive_thru_cards" and self.image_post_processing_config: + with open(image_path, "rb") as f: + raw_image = f.read() + # post_process_image handles resizing to target_pixel_size (set in execute()) + # which ensures the correct DPI for the DTC card dimensions + processed_image, icc_profile_bytes = post_process_image( + raw_image=raw_image, config=self.image_post_processing_config + ) + # Save to a temporary file so fpdf embeds the JPEG data directly + # (passing BytesIO causes fpdf to re-encode with FlateDecode, bloating file size) + ext = ".jpg" if self.image_post_processing_config.output_format == "JPEG" else ".png" + with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as tmp: + tmp_path = tmp.name + try: + save_processed_image( + processed_image, + file_path=tmp_path, + config=self.image_post_processing_config, + icc_profile_bytes=icc_profile_bytes, + ) + self.pdf.image( + tmp_path, + x=0, + y=0, + w=self.card_width_in_inches, + h=self.card_height_in_inches, + ) + finally: + # Clean up temp file + if os.path.exists(tmp_path): + os.unlink(tmp_path) + else: + self.pdf.image(image_path, x=0, y=0, w=self.card_width_in_inches, h=self.card_height_in_inches) - def save_file(self) -> None: + def save_file(self) -> str: extra = "" if self.separate_faces: extra = f"{self.current_face}/" - self.pdf.output(f"{self.save_path}{extra}{self.file_num}.pdf") + file_path = f"{self.save_path}{extra}{self.file_num}.pdf" + self.pdf.output(file_path) + self.saved_files.append(file_path) + return file_path def download_and_collect_images(self, post_processing_config: Optional[ImagePostProcessingConfig]) -> None: + download_config = None if self.export_mode == "drive_thru_cards" else post_processing_config with ThreadPoolExecutor(max_workers=THREADS) as pool: - self.order.fronts.download_images(pool, self.download_bar, post_processing_config) - self.order.backs.download_images(pool, self.download_bar, post_processing_config) + self.order.fronts.download_images(pool, self.download_bar, download_config) + self.order.backs.download_images(pool, self.download_bar, download_config) backs_by_slots = {} for card in self.order.backs.cards_by_id.values(): @@ -132,7 +257,15 @@ def download_and_collect_images(self, post_processing_config: Optional[ImagePost paths_by_slot[slot] = (str(backs_by_slots.get(slot, backs_by_slots[0])), str(fronts_by_slots[slot])) self.paths_by_slot = paths_by_slot - def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) -> None: + def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) -> list[str]: + if self.export_mode == "drive_thru_cards" and post_processing_config is not None: + # Calculate exact pixel dimensions for the target DPI at DTC card size (63x88mm) + post_processing_config.target_pixel_size = calculate_dtc_target_pixel_size( + post_processing_config.max_dpi + ) + # Embed DPI metadata so PDF tools correctly interpret the image resolution + post_processing_config.embed_dpi_metadata = True + self.image_post_processing_config = post_processing_config self.download_and_collect_images(post_processing_config=post_processing_config) if self.separate_faces: self.number_of_cards_per_file = 1 @@ -140,7 +273,17 @@ def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) - else: self.export() + if self.pdfx_config: + for file_path in list(self.saved_files): + pdfx_path = f"{os.path.splitext(file_path)[0]}_pdfx.pdf" + if convert_pdf_to_pdfx(file_path, pdfx_path, self.pdfx_config): + self.saved_files.append(pdfx_path) + logger.info(f"PDF/X-1a conversion succeeded: {pdfx_path}") + else: + logger.info(f"PDF/X-1a conversion failed for {file_path}. Using original PDF.") + logger.info(f"Finished exporting files! They should be accessible at {self.save_path}.") + return self.saved_files def export(self) -> None: for slot in sorted(self.paths_by_slot.keys()): diff --git a/desktop-tool/src/processing.py b/desktop-tool/src/processing.py index e5439c81c..e6d89b36c 100644 --- a/desktop-tool/src/processing.py +++ b/desktop-tool/src/processing.py @@ -1,30 +1,140 @@ import io +import os from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple, Dict, Any from src.constants import DPI_HEIGHT_RATIO, ImageResizeMethods +from src.logging import logger if TYPE_CHECKING: from PIL import Image +# DriveThruCards physical card dimensions in mm +DTC_CARD_WIDTH_MM = 63 +DTC_CARD_HEIGHT_MM = 88 +MM_PER_INCH = 25.4 + + +def calculate_dtc_target_pixel_size(target_dpi: int) -> Tuple[int, int]: + """ + Calculate the target pixel dimensions for DriveThruCards at the specified DPI. + Card size is 63mm x 88mm. + """ + width = max(1, round((DTC_CARD_WIDTH_MM / MM_PER_INCH) * target_dpi)) + height = max(1, round((DTC_CARD_HEIGHT_MM / MM_PER_INCH) * target_dpi)) + return (width, height) + @dataclass class ImagePostProcessingConfig: max_dpi: int downscale_alg: ImageResizeMethods - # jpeg: bool + output_format: Optional[str] = None + output_extension: Optional[str] = None + convert_to_cmyk: bool = False + icc_profile_path: Optional[str] = None + output_directory: Optional[str] = None + jpeg_quality: int = 95 + target_pixel_size: Optional[Tuple[int, int]] = None + embed_dpi_metadata: bool = False + + +def get_post_processed_path(file_path: str, config: ImagePostProcessingConfig) -> str: + directory = config.output_directory or os.path.dirname(file_path) + base_name = os.path.splitext(os.path.basename(file_path))[0] + extension = config.output_extension or os.path.splitext(file_path)[1] + return os.path.join(directory, f"{base_name}{extension}") + + +def _apply_color_processing( + img: "Image", config: ImagePostProcessingConfig +) -> Tuple["Image", Optional[bytes]]: + icc_profile_bytes = None + if config.convert_to_cmyk: + if img.mode in ("RGBA", "LA"): + img = img.convert("RGB") + elif img.mode not in ("RGB", "CMYK"): + img = img.convert("RGB") + if config.icc_profile_path: + try: + from PIL import ImageCms + srgb = ImageCms.createProfile("sRGB") + cmyk_profile = ImageCms.getOpenProfile(config.icc_profile_path) + img = ImageCms.profileToProfile(img, srgb, cmyk_profile, outputMode="CMYK") + icc_profile_bytes = cmyk_profile.tobytes() + except Exception as exc: + logger.warning(f"Failed to apply ICC profile ({config.icc_profile_path}): {exc}") + img = img.convert("CMYK") + else: + img = img.convert("CMYK") + elif config.output_format and config.output_format.upper() == "JPEG": + if img.mode in ("RGBA", "LA"): + img = img.convert("RGB") + return img, icc_profile_bytes -def post_process_image(raw_image: bytes, config: ImagePostProcessingConfig) -> "Image": + +def post_process_image(raw_image: bytes, config: ImagePostProcessingConfig) -> Tuple["Image", Optional[bytes]]: from PIL import Image img = Image.open(io.BytesIO(raw_image)) # downscale the image to `max_dpi` - img_dpi = 10 * round(int(img.height) * DPI_HEIGHT_RATIO / 10) - if img_dpi > config.max_dpi: - new_height = round((config.max_dpi / img_dpi) * img.height) - new_width = round((config.max_dpi / img_dpi) * img.width) - img = img.resize((new_width, new_height), config.downscale_alg.value) + if config.target_pixel_size: + target_width, target_height = config.target_pixel_size + if img.width != target_width or img.height != target_height: + # For DTC, force exact pixel size to guarantee 300 DPI at 63x88mm. + img = img.resize((target_width, target_height), config.downscale_alg.value) + else: + img_dpi = 10 * round(int(img.height) * DPI_HEIGHT_RATIO / 10) + if img_dpi > config.max_dpi: + new_height = round((config.max_dpi / img_dpi) * img.height) + new_width = round((config.max_dpi / img_dpi) * img.width) + img = img.resize((new_width, new_height), config.downscale_alg.value) + + img, icc_profile_bytes = _apply_color_processing(img, config) + return img, icc_profile_bytes + + +def save_processed_image( + img: "Image", + file_path: str, + config: ImagePostProcessingConfig, + icc_profile_bytes: Optional[bytes] = None, +) -> None: + img.save(file_path, **_build_save_kwargs(config=config, icc_profile_bytes=icc_profile_bytes)) + + +def save_processed_image_to_bytes( + img: "Image", + config: ImagePostProcessingConfig, + icc_profile_bytes: Optional[bytes] = None, +) -> bytes: + output = io.BytesIO() + img.save(output, **_build_save_kwargs(config=config, icc_profile_bytes=icc_profile_bytes)) + output.seek(0) + return output.read() + - return img +def _build_save_kwargs( + config: ImagePostProcessingConfig, + icc_profile_bytes: Optional[bytes], +) -> Dict[str, Any]: + save_kwargs: Dict[str, Any] = {} + if config.output_format: + save_kwargs["format"] = config.output_format + if config.output_format and config.output_format.upper() == "JPEG": + save_kwargs["quality"] = config.jpeg_quality + save_kwargs["subsampling"] = 0 + save_kwargs["optimize"] = True + if icc_profile_bytes: + save_kwargs["icc_profile"] = icc_profile_bytes + # Embed DPI metadata to ensure PDF tools correctly interpret the image resolution. + # This is critical for DriveThruCards where the target DPI must be 300. + if config.embed_dpi_metadata and config.target_pixel_size: + # Calculate DPI from target pixel size and DTC card dimensions + target_width, target_height = config.target_pixel_size + dpi_x = round(target_width / (DTC_CARD_WIDTH_MM / MM_PER_INCH)) + dpi_y = round(target_height / (DTC_CARD_HEIGHT_MM / MM_PER_INCH)) + save_kwargs["dpi"] = (dpi_x, dpi_y) + return save_kwargs diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 179d11a9c..4bd2d7557 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -9,6 +9,33 @@ from selenium.webdriver.firefox.options import Options as FirefoxOptions +def _apply_stealth_options(options: ChromeOptions) -> None: + """ + Apply anti-bot-detection options to Chrome/Chromium browsers. + This helps bypass Cloudflare and similar bot detection systems. + """ + # Disable automation indicators + options.add_argument("--disable-blink-features=AutomationControlled") + # Exclude automation switches that reveal Selenium + options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"]) + # Disable the "Chrome is being controlled by automated software" infobar + options.add_experimental_option("useAutomationExtension", False) + + +def _apply_stealth_cdp(driver: Chrome) -> None: + """ + Use Chrome DevTools Protocol to further hide automation indicators. + """ + # Remove navigator.webdriver flag + driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { + "source": """ + Object.defineProperty(navigator, 'webdriver', { + get: () => undefined + }); + """ + }) + + def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: options = ChromeOptions() options.add_argument("--no-sandbox") @@ -16,11 +43,12 @@ def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = N options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") - options.add_experimental_option("excludeSwitches", ["enable-logging"]) + _apply_stealth_options(options) options.add_experimental_option("detach", True) if binary_location is not None: options.binary_location = binary_location driver = Chrome(options=options) + _apply_stealth_cdp(driver) driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) return driver @@ -32,7 +60,7 @@ def get_brave_driver(headless: bool = False, binary_location: Optional[str] = No options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") - options.add_experimental_option("excludeSwitches", ["enable-logging"]) + _apply_stealth_options(options) options.add_experimental_option("detach", True) # the binary location for brave must be manually specified (otherwise chrome will open instead) @@ -51,6 +79,7 @@ def get_brave_driver(headless: bool = False, binary_location: Optional[str] = No options.binary_location = default_binary_locations[sys.platform] driver = Chrome(options=options) + _apply_stealth_cdp(driver) driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) return driver @@ -60,13 +89,23 @@ def get_edge_driver(headless: bool = False, binary_location: Optional[str] = Non options.add_argument("--no-sandbox") options.add_argument("--log-level=3") options.add_argument("--disable-dev-shm-usage") + options.add_argument("--disable-blink-features=AutomationControlled") if headless: options.add_argument("--headless=new") - options.add_experimental_option("excludeSwitches", ["enable-logging"]) + options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"]) + options.add_experimental_option("useAutomationExtension", False) options.add_experimental_option("detach", True) if binary_location is not None: options.binary_location = binary_location driver: ChromiumDriver = Edge(options=options) # type: ignore + # Apply CDP stealth for Edge as well + driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { + "source": """ + Object.defineProperty(navigator, 'webdriver', { + get: () => undefined + }); + """ + }) driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) return driver diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 3e34f73e5..286930b5a 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -27,7 +27,8 @@ Details, aggregate_and_split_orders, ) -from src.pdf_maker import PdfExporter +import autofill as autofill_cli +from src.pdf_maker import PdfExporter, get_ghostscript_version from src.processing import ImagePostProcessingConfig DEFAULT_POST_PROCESSING = ImagePostProcessingConfig(max_dpi=800, downscale_alg=constants.ImageResizeMethods.LANCZOS) @@ -70,6 +71,102 @@ def assert_file_size(file_path: str, size: int) -> None: assert os.stat(file_path).st_size == size, f"File size {os.stat(file_path).st_size} does not match {size}" +# endregion + +# region Ghostscript + + +def test_get_ghostscript_version_reads_stdout(monkeypatch: pytest.MonkeyPatch) -> None: + class Result: + def __init__(self) -> None: + self.stdout = "10.02.1\n" + + def fake_run(*_args, **_kwargs): + return Result() + + monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) + assert get_ghostscript_version("gs") == "10.02.1" + + +def test_ensure_ghostscript_available_prompts_until_found( + monkeypatch: pytest.MonkeyPatch, input_enter +) -> None: + paths = [None, "/usr/local/bin/gs"] + called = {"version": 0} + + def fake_get_path(): + return paths.pop(0) + + def fake_get_version(_path: str) -> str: + called["version"] += 1 + return "10.0.0" + + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", fake_get_path) + monkeypatch.setattr(autofill_cli, "get_ghostscript_version", fake_get_version) + + assert autofill_cli.ensure_ghostscript_available() == "/usr/local/bin/gs" + assert called["version"] == 1 + + +# endregion + +# region PDF/X conversion + + +def test_pdf_exporter_appends_pdfx_on_success(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: + def do_nothing(_): + return None + + def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> bool: + with open(output_path, "wb") as f: + f.write(b"pdfx") + return True + + monkeypatch.setattr("src.pdf_maker.PdfExporter.ask_questions", do_nothing) + monkeypatch.setattr("src.pdf_maker.convert_pdf_to_pdfx", fake_convert_pdf_to_pdfx) + + card_order_valid.name = "test_order.xml" + pdf_exporter = PdfExporter( + order=card_order_valid, + number_of_cards_per_file=1, + pdfx_config=autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + ) + generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) + + expected_pdfx_files = [ + "export/test_order/1_pdfx.pdf", + "export/test_order/2_pdfx.pdf", + "export/test_order/3_pdfx.pdf", + ] + for file_path in expected_pdfx_files: + assert file_path in generated_files + assert os.path.exists(file_path) + + remove_files([path for path in generated_files if path.endswith(".pdf")]) + remove_directories(["export/test_order", "export"]) + + +def test_pdf_exporter_skips_pdfx_on_failure(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: + def do_nothing(_): + return None + + monkeypatch.setattr("src.pdf_maker.PdfExporter.ask_questions", do_nothing) + monkeypatch.setattr("src.pdf_maker.convert_pdf_to_pdfx", lambda *_args, **_kwargs: False) + + card_order_valid.name = "test_order.xml" + pdf_exporter = PdfExporter( + order=card_order_valid, + number_of_cards_per_file=1, + pdfx_config=autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + ) + generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) + + assert not any(path.endswith("_pdfx.pdf") for path in generated_files) + + remove_files([path for path in generated_files if path.endswith(".pdf")]) + remove_directories(["export/test_order", "export"]) + + # endregion # region constants From d0ceac0789c10afc4bd4adf02f7d314cff37ad55 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Tue, 27 Jan 2026 16:48:35 -0800 Subject: [PATCH 02/76] Add DriveThruCards order automation flow - Automate login, product setup, PDF upload, and checkout navigation - Fix card dimensions to 2.73" x 3.71" for Premium Euro Poker with bleed - Use Ghostscript -dNOSAFER flag for PDF/X-1a conversion - Add placeholder cover image for product form - Replace sleep timers with dynamic element waits --- desktop-tool/assets/placeholder_cover.png | Bin 0 -> 51796 bytes desktop-tool/autofill.py | 9 +- desktop-tool/src/constants.py | 5 +- desktop-tool/src/driver.py | 557 ++++++++++++++++++++-- desktop-tool/src/pdf_maker.py | 12 +- desktop-tool/src/processing.py | 19 +- 6 files changed, 540 insertions(+), 62 deletions(-) create mode 100644 desktop-tool/assets/placeholder_cover.png diff --git a/desktop-tool/assets/placeholder_cover.png b/desktop-tool/assets/placeholder_cover.png new file mode 100644 index 0000000000000000000000000000000000000000..72d708f4508bcd70a042596010ec16d48f597e02 GIT binary patch literal 51796 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 literal 0 HcmV?d00001 diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 611d66f3a..40213e7f4 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -338,7 +338,14 @@ def main( pdfx_config=PdfXConversionConfig(icc_profile_path=resolved_icc_profile), ) pdf_paths = exporter.execute(post_processing_config=dtc_post_processing_config) - dtc_pdf_path = next((path for path in reversed(pdf_paths) if path.endswith("_pdfx.pdf")), pdf_paths[0]) + # Only use the Ghostscript PDF/X-1a output - no fallback + dtc_pdf_path = next((path for path in reversed(pdf_paths) if path.endswith("_pdfx.pdf")), None) + if dtc_pdf_path is None: + logger.error( + "Ghostscript PDF/X-1a conversion failed. Cannot proceed with DriveThruCards upload.\n" + "Please fix the Ghostscript conversion issue and try again." + ) + continue AutofillDriver( browser=Browsers[browser], target_site=target_site, diff --git a/desktop-tool/src/constants.py b/desktop-tool/src/constants.py index d2f8ffe36..6c4b88fa9 100644 --- a/desktop-tool/src/constants.py +++ b/desktop-tool/src/constants.py @@ -161,9 +161,10 @@ class DriveThruCardsSelectors: add_to_cart_selector: str = attr.ib(default="") continue_selector: str = attr.ib(default="") # Login selectors - two step process: click login button, then click "Go to Log in" link - login_button_selector: str = attr.ib(default="button[data-cy='login'], button[aria-label='Log In']") + login_button_selector: str = attr.ib(default="button[data-cy='login']") go_to_login_selector: str = attr.ib(default="a[href='/en/']") - logged_in_indicator_selector: str = attr.ib(default="button[data-cy='logout'], button[aria-label='Log Out'], a[href*='logout'], a[href*='account'], .my-account") + # Publisher Tools link only appears when logged in as a publisher + logged_in_indicator_selector: str = attr.ib(default="a[href*='pub_tools.php']") @attr.s diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 7a87a6344..3f84e6cac 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -11,9 +11,12 @@ from InquirerPy import inquirer from selenium.common import exceptions as sl_exc from selenium.common.exceptions import NoAlertPresentException, NoSuchElementException +from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support.expected_conditions import ( + element_to_be_clickable, presence_of_element_located, invisibility_of_element, text_to_be_present_in_element, @@ -334,7 +337,7 @@ def handle_alert(self) -> None: def wait_for_cloudflare_challenge(self, timeout_seconds: int = 300) -> None: """ Wait for the Cloudflare challenge to be completed by waiting for site content to appear. - Checks multiple indicators that the actual site has loaded. + Waits for either the login button or Publisher Tools link (if already logged in). """ self.set_state(States.defining_order, "Waiting for site to load") logger.info( @@ -342,50 +345,74 @@ def wait_for_cloudflare_challenge(self, timeout_seconds: int = 300) -> None: "If you see a Cloudflare captcha, please complete it." ) - start_time = time.time() - while time.time() - start_time < timeout_seconds: + selectors = self.target_site.value.selectors + + def check_site_loaded(driver: WebDriver) -> bool: try: - # Check page title - Cloudflare shows "Just a moment..." - title = self.driver.title.lower() - if "just a moment" in title or "attention required" in title: - time.sleep(1) - continue - - # Check if we're on the actual DriveThruCards site - # Look for any of these indicators that the real site has loaded - page_source = self.driver.page_source - - # DriveThruCards specific indicators - site_indicators = [ - "drivethrucards", - "Log In", # The login button text - "data-cy=\"login\"", # The login button attribute - "OneBookShelf", # Parent company - ] - - if any(indicator in page_source for indicator in site_indicators): - logger.info("Site loaded successfully!") - time.sleep(2) # Give page time to fully render - return - + title = driver.title.lower() + # During Cloudflare, page title is "Just a moment..." + if "just a moment" in title: + return False + # Check for login button or logged-in indicator + login_btns = driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) + logged_in = driver.find_elements(By.CSS_SELECTOR, selectors.logged_in_indicator_selector) + if login_btns or logged_in: + return True + # Page loaded but elements not found yet - log for debugging + logger.debug(f"Page title: '{title}', login buttons: {len(login_btns)}, logged_in indicators: {len(logged_in)}") + return False except Exception as e: - logger.debug(f"Error checking page: {e}") - time.sleep(1) - - logger.warning(f"Timeout after {timeout_seconds}s waiting for site to load. Attempting to continue anyway.") + logger.debug(f"Error checking site loaded: {e}") + return False + + try: + WebDriverWait(self.driver, timeout_seconds).until(check_site_loaded) + logger.info("Site loaded successfully!") + except sl_exc.TimeoutException: + logger.warning(f"Timeout after {timeout_seconds}s waiting for site to load. Attempting to continue anyway.") def is_dtc_user_authenticated(self) -> bool: """Check if the user is logged in to DriveThruCards.""" selectors = self.target_site.value.selectors try: - # Look for logout link or account link as indicator of being logged in + # Look for logout button as indicator of being logged in + # This needs to be specific - only match elements that appear when logged in logged_in_elements = self.driver.find_elements( By.CSS_SELECTOR, selectors.logged_in_indicator_selector ) - return len(logged_in_elements) > 0 - except Exception: + # Filter to only visible elements + visible_elements = [el for el in logged_in_elements if el.is_displayed()] + if visible_elements: + logger.debug(f"Found {len(visible_elements)} visible logged-in indicator(s)") + for el in visible_elements[:3]: + logger.debug(f" Element: tag={el.tag_name}, text='{el.text}', aria-label='{el.get_attribute('aria-label')}'") + return len(visible_elements) > 0 + except Exception as e: + logger.debug(f"Error checking auth status: {e}") return False + def click_element_with_retry(self, element: Any) -> bool: + """ + Attempt to click an element using multiple strategies. + Returns True if click succeeded, False otherwise. + """ + # Strategy 1: Scroll into view and use native click + try: + self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element) + element.click() + return True + except Exception as e: + logger.debug(f"Native click failed: {e}") + + # Strategy 2: JavaScript click (bypasses overlays and visibility issues) + try: + self.driver.execute_script("arguments[0].click();", element) + return True + except Exception as e: + logger.debug(f"JavaScript click failed: {e}") + + return False + def authenticate_dtc(self) -> None: """ Handle DriveThruCards login flow. @@ -402,33 +429,76 @@ def authenticate_dtc(self) -> None: logger.info("Please log in to your DriveThruCards account.") # Step 1: Try to find and click the login button + login_clicked = False try: - login_button = WebDriverWait(self.driver, 10).until( - presence_of_element_located((By.CSS_SELECTOR, selectors.login_button_selector)) + # Wait for element to be clickable, not just present + logger.debug(f"Looking for login button with selector: {selectors.login_button_selector}") + login_button = WebDriverWait(self.driver, 15).until( + element_to_be_clickable((By.CSS_SELECTOR, selectors.login_button_selector)) ) - logger.info("Clicking the login button...") - login_button.click() - time.sleep(2) # Wait for modal/dialog to appear + # Log info about the found element for debugging + logger.debug( + f"Found element - tag: {login_button.tag_name}, " + f"text: '{login_button.text}', " + f"displayed: {login_button.is_displayed()}, " + f"enabled: {login_button.is_enabled()}" + ) + logger.info("Found login button, attempting to click...") + login_clicked = self.click_element_with_retry(login_button) + if login_clicked: + logger.info("Successfully clicked the login button.") + else: + logger.debug("All click strategies failed for login button.") + except sl_exc.TimeoutException: + logger.debug("Timeout waiting for login button to be clickable.") + # Try to find any buttons to help debug + try: + buttons = self.driver.find_elements(By.TAG_NAME, "button") + logger.debug(f"Found {len(buttons)} button(s) on page") + for btn in buttons[:5]: # Log first 5 buttons + logger.debug(f" Button: text='{btn.text}', aria-label='{btn.get_attribute('aria-label')}'") + except Exception: + pass except Exception as e: logger.debug(f"Could not find/click login button: {e}") + + if not login_clicked: logger.info( - "Could not find login button automatically.\n" + "Could not find or click login button automatically.\n" "Please click the login button manually." ) # Step 2: Try to find and click the "Go to Log in" link + # Use XPath to find by text content since href="/en/" is too generic + go_to_login_clicked = False try: + # Try XPath first (more specific - matches by text content) go_to_login_link = WebDriverWait(self.driver, 10).until( - presence_of_element_located((By.CSS_SELECTOR, selectors.go_to_login_selector)) + element_to_be_clickable((By.XPATH, "//a[contains(normalize-space(), 'Go to Log in')]")) ) - logger.info("Clicking 'Go to Log in' link...") - go_to_login_link.click() - time.sleep(2) # Wait for login page to load + logger.info("Found 'Go to Log in' link, attempting to click...") + go_to_login_clicked = self.click_element_with_retry(go_to_login_link) + if go_to_login_clicked: + logger.info("Successfully clicked 'Go to Log in' link.") + else: + logger.debug("All click strategies failed for 'Go to Log in' link.") + except sl_exc.TimeoutException: + logger.debug("Timeout waiting for 'Go to Log in' link to be clickable.") + # Fallback to CSS selector + try: + go_to_login_link = WebDriverWait(self.driver, 5).until( + element_to_be_clickable((By.CSS_SELECTOR, selectors.go_to_login_selector)) + ) + go_to_login_clicked = self.click_element_with_retry(go_to_login_link) + except Exception: + pass except Exception as e: logger.debug(f"Could not find/click 'Go to Log in' link: {e}") + + if not go_to_login_clicked: logger.info( "Could not find 'Go to Log in' link automatically.\n" - "Please navigate to the login page manually." + "Please navigate to the login page manually if needed." ) logger.info( @@ -440,10 +510,9 @@ def authenticate_dtc(self) -> None: timeout_seconds = 300 start_time = time.time() while time.time() - start_time < timeout_seconds: - time.sleep(2) + time.sleep(1) # Polling interval if self.is_dtc_user_authenticated(): logger.info("Successfully logged in to DriveThruCards!") - time.sleep(1) # Give the page a moment to settle return logger.warning( @@ -451,6 +520,379 @@ def authenticate_dtc(self) -> None: "Please ensure you're logged in before continuing." ) + def navigate_to_dtc_product_setup(self) -> None: + """ + Navigate through DriveThruCards to the product setup page. + Steps: Publisher Tools -> Set up a new title + """ + self.set_state(States.defining_order, "Navigating to Publisher Tools") + selectors = self.target_site.value.selectors + + # Step 1: Click "Publisher Tools" link (use same selector as login detection) + try: + publisher_tools_link = WebDriverWait(self.driver, 5).until( + element_to_be_clickable((By.CSS_SELECTOR, selectors.logged_in_indicator_selector)) + ) + logger.info("Found 'Publisher Tools' link, clicking...") + if self.click_element_with_retry(publisher_tools_link): + logger.info("Successfully clicked 'Publisher Tools'.") + else: + logger.warning("Could not click 'Publisher Tools' automatically. Please click it manually.") + except sl_exc.TimeoutException: + logger.warning("Could not find 'Publisher Tools' link. Trying direct navigation...") + self.driver.get("https://site.drivethrucards.com/pub_tools.php") + + # Step 2: Wait for and click "Set up a new title" link + self.set_state(States.defining_order, "Navigating to product setup") + try: + setup_link = WebDriverWait(self.driver, 10).until( + element_to_be_clickable((By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]")) + ) + logger.info("Found 'Set up a new title' link, clicking...") + if self.click_element_with_retry(setup_link): + logger.info("Successfully clicked 'Set up a new title'.") + else: + logger.warning("Could not click 'Set up a new title' automatically. Please click it manually.") + except sl_exc.TimeoutException: + logger.warning("Could not find 'Set up a new title' link. Trying direct navigation...") + self.driver.get("https://tools.drivethrucards.com/pub_enter_product.php") + + def fill_dtc_product_form(self, order: CardOrder) -> None: + """ + Fill out the DriveThruCards product setup form (first page). + """ + import os + + self.set_state(States.defining_order, "Filling product form") + + # Get the placeholder cover image path (bundled with assets) + assets_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets") + placeholder_cover_path = os.path.join(assets_dir, "placeholder_cover.png") + + # Generate title: order name + today's date + today = dt.date.today().strftime("%Y-%m-%d") + title = f"{order.name or 'Order'} {today}" + + # Fill in the title field + try: + title_input = WebDriverWait(self.driver, 10).until( + presence_of_element_located((By.ID, "products_name")) + ) + title_input.clear() + title_input.send_keys(title) + logger.info(f"Set product title to: {title}") + except Exception as e: + logger.warning(f"Could not fill title field: {e}") + + # Fill in the special price field with "0" + try: + price_input = self.driver.find_element(By.ID, "options_values_total_price") + price_input.clear() + price_input.send_keys("0") + logger.info("Set special price to: 0") + except Exception as e: + logger.warning(f"Could not fill price field: {e}") + + # Upload the placeholder cover image + try: + image_input = self.driver.find_element(By.NAME, "products_image") + image_input.send_keys(placeholder_cover_path) + logger.info(f"Uploaded placeholder cover image: {placeholder_cover_path}") + except Exception as e: + logger.warning(f"Could not upload cover image: {e}") + + # Check the two filter checkboxes + try: + checkbox1 = self.driver.find_element(By.ID, "filter_44550") + if not checkbox1.is_selected(): + self.click_element_with_retry(checkbox1) + logger.debug("Checked filter_44550") + except Exception as e: + logger.warning(f"Could not check filter_44550: {e}") + + try: + checkbox2 = self.driver.find_element(By.ID, "filter_1000138") + if not checkbox2.is_selected(): + self.click_element_with_retry(checkbox2) + logger.debug("Checked filter_1000138") + except Exception as e: + logger.warning(f"Could not check filter_1000138: {e}") + + # Click the first submit button (Save Title Data and Continue to Preview Description) + self.set_state(States.defining_order, "Submitting product form") + try: + submit_button = WebDriverWait(self.driver, 10).until( + element_to_be_clickable((By.ID, "submit_id")) + ) + logger.info("Clicking submit button...") + self.click_element_with_retry(submit_button) + logger.info("Product form submitted successfully.") + except Exception as e: + logger.warning(f"Could not click submit button: {e}") + + def submit_dtc_description_page(self) -> None: + """ + Click 'Save and Continue' on the description preview page. + Waits for the button to be available (page loaded from previous step). + """ + self.set_state(States.defining_order, "Saving description") + try: + save_continue_button = WebDriverWait(self.driver, 15).until( + element_to_be_clickable((By.ID, "clicked_element")) + ) + logger.info("Clicking 'Save and Continue' button...") + self.click_element_with_retry(save_continue_button) + logger.info("Description page submitted.") + except Exception as e: + logger.warning(f"Could not click 'Save and Continue': {e}") + + def open_dtc_upload_page(self) -> None: + """ + Click 'Upload print-ready file' button which opens a new tab, + then switch to that tab. + Waits for button to be available (page loaded from previous step). + """ + self.set_state(States.defining_order, "Opening upload page") + + # Store current window handles + original_windows = set(self.driver.window_handles) + + try: + # Wait for and click the upload button + upload_button = WebDriverWait(self.driver, 15).until( + element_to_be_clickable((By.XPATH, "//button[contains(@onclick, 'pub_upload_podcard_files.php')]")) + ) + logger.info("Clicking 'Upload print-ready file' button...") + self.click_element_with_retry(upload_button) + + # Wait for new window/tab to open (poll until a new window appears) + WebDriverWait(self.driver, 15).until( + lambda d: len(d.window_handles) > len(original_windows) + ) + + # Find the new window and switch to it + new_windows = set(self.driver.window_handles) - original_windows + if new_windows: + new_window = new_windows.pop() + self.driver.switch_to.window(new_window) + logger.info(f"Switched to upload tab: {self.driver.current_url}") + else: + logger.warning("No new tab detected after clicking upload button.") + except Exception as e: + logger.warning(f"Could not open upload page: {e}") + + def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: + """ + Select 'Premium Euro Poker Card(s)' from dropdown and upload the PDF. + Waits for elements to be available instead of using fixed sleeps. + """ + import os + + self.set_state(States.inserting_fronts, "Selecting card type") + + # Wait for and select the Euro Poker card option from the dropdown + try: + # Wait for dropdown to be present and interactable + WebDriverWait(self.driver, 15).until( + presence_of_element_located((By.ID, "card_type_select")) + ) + # Re-fetch the element to avoid stale reference after page/tab switch + card_type_dropdown = self.driver.find_element(By.ID, "card_type_select") + select = Select(card_type_dropdown) + + # Find option containing "Euro Poker" (case-insensitive search) + euro_poker_option_text = None + for option in select.options: + if "euro poker" in option.text.lower(): + euro_poker_option_text = option.text + break + + if euro_poker_option_text: + select.select_by_visible_text(euro_poker_option_text) + logger.info(f"Selected '{euro_poker_option_text}' from dropdown.") + else: + logger.warning("Could not find Euro Poker option in dropdown.") + except Exception as e: + logger.warning(f"Could not select card type: {e}") + + # Convert PDF path to absolute if needed + if not os.path.isabs(pdf_path): + pdf_path = os.path.abspath(pdf_path) + + # Verify the file exists + if not os.path.exists(pdf_path): + logger.error(f"PDF file not found: {pdf_path}") + return + + logger.info(f"PDF file found: {pdf_path} ({os.path.getsize(pdf_path)} bytes)") + + # Wait for the dropzone to appear after card type selection + self.set_state(States.inserting_fronts, "Uploading PDF") + try: + # Wait for the dropzone div to be present + dropzone_div = WebDriverWait(self.driver, 15).until( + presence_of_element_located((By.ID, "uploadfiles")) + ) + logger.debug("Dropzone div found.") + + # Click the dropzone to initialize Dropzone's hidden input + # This should create the .dz-hidden-input element + logger.debug("Clicking dropzone to initialize hidden input...") + self.driver.execute_script("arguments[0].click();", dropzone_div) + + # Brief wait for the file dialog to appear, then send Escape to close it + time.sleep(0.5) + ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() + time.sleep(0.5) + + # Find the file input and send the file - do this in a single operation + # to avoid stale element references + logger.info(f"Uploading PDF: {pdf_path}") + + def find_and_use_file_input() -> bool: + """Find a usable file input and send the file path to it.""" + # Strategy 1: Dropzone hidden input + try: + fi = self.driver.find_element(By.CSS_SELECTOR, ".dz-hidden-input") + logger.debug("Found Dropzone hidden input, sending file...") + fi.send_keys(pdf_path) + return True + except (sl_exc.NoSuchElementException, sl_exc.StaleElementReferenceException): + pass + + # Strategy 2: Any file input that's not the fallback + try: + file_inputs = self.driver.find_elements(By.CSS_SELECTOR, "input[type='file']") + logger.debug(f"Found {len(file_inputs)} file input(s) on page.") + for fi in file_inputs: + try: + name = fi.get_attribute("name") + if name == "groups_csv": + continue + logger.debug(f"Trying file input: name={name}") + fi.send_keys(pdf_path) + return True + except sl_exc.StaleElementReferenceException: + continue + except Exception as e: + logger.debug(f"Error with file inputs: {e}") + + # Strategy 3: Use the fallback input + try: + logger.debug("Using fallback file input...") + self.driver.execute_script( + "document.getElementById('dropzoneFallback').style.display = 'block';" + ) + fi = self.driver.find_element(By.CSS_SELECTOR, "#dropzoneFallback input[type='file']") + fi.send_keys(pdf_path) + return True + except Exception as e: + logger.debug(f"Fallback input failed: {e}") + + return False + + # Try up to 3 times to handle any remaining stale element issues + file_sent = False + for attempt in range(3): + if find_and_use_file_input(): + file_sent = True + break + logger.debug(f"Attempt {attempt + 1} failed, retrying...") + time.sleep(0.5) + + if not file_sent: + logger.warning("Could not send file to any input element.") + return + + # Trigger change event on all file inputs (one of them has our file) + self.driver.execute_script(""" + document.querySelectorAll('input[type="file"]').forEach(function(input) { + input.dispatchEvent(new Event('change', { bubbles: true })); + }); + """) + logger.debug("Dispatched change event on file inputs.") + + # Wait for the upload button to become enabled (Dropzone enables it when files are queued) + try: + WebDriverWait(self.driver, 10).until( + lambda d: not d.find_element(By.ID, "dropzoneButton").get_attribute("disabled") + ) + logger.debug("Upload button is now enabled.") + except sl_exc.TimeoutException: + logger.debug("Button didn't become enabled automatically, forcing it.") + + # Click the upload button using JavaScript + upload_clicked = self.driver.execute_script(""" + var btn = document.getElementById('dropzoneButton'); + if (btn) { + btn.disabled = false; + btn.style.display = 'block'; + btn.click(); + return true; + } + return false; + """) + if upload_clicked: + logger.info("Clicked 'Begin Card File Upload' button via JavaScript.") + else: + logger.warning("Could not find upload button.") + + # Also try to trigger Dropzone's processQueue as a backup + try: + self.driver.execute_script(""" + var dz = Dropzone.forElement('#uploadfiles'); + if (dz && dz.files && dz.files.length > 0) { + dz.processQueue(); + } + """) + logger.debug("Triggered Dropzone processQueue.") + except Exception as e: + logger.debug(f"Could not trigger processQueue: {e}") + + # Wait for upload to complete - look for success message + try: + WebDriverWait(self.driver, 120).until( + lambda d: "successfully uploaded" in ( + d.find_element(By.ID, "status_messages").text.lower() + if d.find_elements(By.ID, "status_messages") else "" + ) + ) + logger.info("PDF upload completed to DriveThruCards.") + except sl_exc.TimeoutException: + logger.warning("Could not confirm upload completion. Please verify manually.") + + # Click the continue button + try: + continue_button = WebDriverWait(self.driver, 10).until( + element_to_be_clickable((By.ID, "continue_button")) + ) + self.driver.execute_script("arguments[0].click();", continue_button) + logger.info("Clicked 'Click here after uploading your files' button.") + except sl_exc.TimeoutException: + logger.warning("Could not find continue button. Please click it manually.") + + # Click the "Complete Setup" button on the next page + try: + complete_button = WebDriverWait(self.driver, 30).until( + element_to_be_clickable((By.ID, "submit_id")) + ) + self.driver.execute_script("arguments[0].click();", complete_button) + logger.info("Clicked 'Complete Setup' button.") + except sl_exc.TimeoutException: + logger.warning("Could not find 'Complete Setup' button. Please click it manually.") + + # Click the "buy now" link on the next page + try: + buy_now_link = WebDriverWait(self.driver, 30).until( + element_to_be_clickable((By.CSS_SELECTOR, "a[href*='action=buy_now']")) + ) + self.driver.execute_script("arguments[0].click();", buy_now_link) + logger.info("Clicked 'buy now' link.") + except sl_exc.TimeoutException: + logger.warning("Could not find 'buy now' link. Please click it manually.") + except Exception as e: + logger.warning(f"Could not upload PDF: {e}") + def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> None: t = time.time() selectors = self.target_site.value.selectors @@ -463,6 +905,29 @@ def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> Non # Handle login self.authenticate_dtc() + # Navigate to product setup page + self.navigate_to_dtc_product_setup() + + # Fill out the product form (first page - title, price, cover image, checkboxes) + self.fill_dtc_product_form(order=order) + + # Submit the description preview page + self.submit_dtc_description_page() + + # Open the upload page (new tab) + self.open_dtc_upload_page() + + # Select card type and upload the PDF + self.select_card_type_and_upload_pdf(pdf_path=pdf_path) + + # DriveThruCards automation complete - user should finish checkout manually + log_hours_minutes_seconds_elapsed(t) + logger.info( + "DriveThruCards order setup complete!\n" + "You are now at the checkout page. Please review your order and complete the purchase manually." + ) + return + if selectors.quantity_selector: try: self.wait_for_selector(selectors.quantity_selector) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index e05ec7989..c14386b44 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -17,6 +17,8 @@ from src.logging import logger from src.order import CardOrder from src.processing import ( + DTC_CARD_HEIGHT_INCHES, + DTC_CARD_WIDTH_INCHES, ImagePostProcessingConfig, calculate_dtc_target_pixel_size, post_process_image, @@ -66,7 +68,7 @@ def convert_pdf_to_pdfx( gs_path, "-dBATCH", "-dNOPAUSE", - "-dSAFER", + "-dNOSAFER", # Allow file system access for ICC profile and output "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.3", "-dPDFX", @@ -82,6 +84,7 @@ def convert_pdf_to_pdfx( cmd.append(f"-sOutputICCProfile={config.icc_profile_path}") cmd.append(source_path) + logger.debug(f"Ghostscript command: {' '.join(cmd)}") result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: logger.warning( @@ -136,8 +139,9 @@ def set_state(self, state: str) -> None: def __attrs_post_init__(self) -> None: if self.export_mode == "drive_thru_cards": - self.card_width_in_inches = 63 / 25.4 - self.card_height_in_inches = 88 / 25.4 + # DriveThruCards Premium Euro Poker requires 2.73" x 3.71" with bleed + self.card_width_in_inches = DTC_CARD_WIDTH_INCHES + self.card_height_in_inches = DTC_CARD_HEIGHT_INCHES self.separate_faces = False self.number_of_cards_per_file = max(1, self.order.details.quantity) else: @@ -259,7 +263,7 @@ def download_and_collect_images(self, post_processing_config: Optional[ImagePost def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) -> list[str]: if self.export_mode == "drive_thru_cards" and post_processing_config is not None: - # Calculate exact pixel dimensions for the target DPI at DTC card size (63x88mm) + # Calculate exact pixel dimensions for the target DPI at DTC card size (2.73" x 3.71") post_processing_config.target_pixel_size = calculate_dtc_target_pixel_size( post_processing_config.max_dpi ) diff --git a/desktop-tool/src/processing.py b/desktop-tool/src/processing.py index e6d89b36c..22b703a66 100644 --- a/desktop-tool/src/processing.py +++ b/desktop-tool/src/processing.py @@ -9,19 +9,20 @@ if TYPE_CHECKING: from PIL import Image -# DriveThruCards physical card dimensions in mm -DTC_CARD_WIDTH_MM = 63 -DTC_CARD_HEIGHT_MM = 88 +# DriveThruCards physical card dimensions (Premium Euro Poker with bleed) +# DriveThruCards requires 2.73" x 3.71" which includes bleed area +DTC_CARD_WIDTH_INCHES = 2.73 +DTC_CARD_HEIGHT_INCHES = 3.71 MM_PER_INCH = 25.4 def calculate_dtc_target_pixel_size(target_dpi: int) -> Tuple[int, int]: """ Calculate the target pixel dimensions for DriveThruCards at the specified DPI. - Card size is 63mm x 88mm. + Card size is 2.73" x 3.71" (Premium Euro Poker with bleed). """ - width = max(1, round((DTC_CARD_WIDTH_MM / MM_PER_INCH) * target_dpi)) - height = max(1, round((DTC_CARD_HEIGHT_MM / MM_PER_INCH) * target_dpi)) + width = max(1, round(DTC_CARD_WIDTH_INCHES * target_dpi)) + height = max(1, round(DTC_CARD_HEIGHT_INCHES * target_dpi)) return (width, height) @@ -83,7 +84,7 @@ def post_process_image(raw_image: bytes, config: ImagePostProcessingConfig) -> T if config.target_pixel_size: target_width, target_height = config.target_pixel_size if img.width != target_width or img.height != target_height: - # For DTC, force exact pixel size to guarantee 300 DPI at 63x88mm. + # For DTC, force exact pixel size to guarantee 300 DPI at 2.73" x 3.71". img = img.resize((target_width, target_height), config.downscale_alg.value) else: img_dpi = 10 * round(int(img.height) * DPI_HEIGHT_RATIO / 10) @@ -134,7 +135,7 @@ def _build_save_kwargs( if config.embed_dpi_metadata and config.target_pixel_size: # Calculate DPI from target pixel size and DTC card dimensions target_width, target_height = config.target_pixel_size - dpi_x = round(target_width / (DTC_CARD_WIDTH_MM / MM_PER_INCH)) - dpi_y = round(target_height / (DTC_CARD_HEIGHT_MM / MM_PER_INCH)) + dpi_x = round(target_width / DTC_CARD_WIDTH_INCHES) + dpi_y = round(target_height / DTC_CARD_HEIGHT_INCHES) save_kwargs["dpi"] = (dpi_x, dpi_y) return save_kwargs From 316081e25cb8e78f9e1ec53ad2bfd86571b0163b Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 30 Jan 2026 02:10:37 -0800 Subject: [PATCH 03/76] Strip XMP metadata from images before saving JPEG has a 64KB limit for XMP data in a single APP1 segment. Some source images have XMP data exceeding this limit, causing "XMP data is too long" errors when saving. Since XMP is purely metadata and doesn't affect the visual content, we strip it before saving. --- desktop-tool/src/processing.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/desktop-tool/src/processing.py b/desktop-tool/src/processing.py index 22b703a66..86061c4ca 100644 --- a/desktop-tool/src/processing.py +++ b/desktop-tool/src/processing.py @@ -103,6 +103,11 @@ def save_processed_image( config: ImagePostProcessingConfig, icc_profile_bytes: Optional[bytes] = None, ) -> None: + # Remove XMP data if it's present in the image info to avoid "XMP data is too long" error. + # JPEG format has a 64KB limit for XMP metadata in a single APP1 segment. + if "xmp" in img.info: + img.info.pop("xmp") + img.save(file_path, **_build_save_kwargs(config=config, icc_profile_bytes=icc_profile_bytes)) @@ -111,6 +116,10 @@ def save_processed_image_to_bytes( config: ImagePostProcessingConfig, icc_profile_bytes: Optional[bytes] = None, ) -> bytes: + # Remove XMP data if it's present in the image info to avoid "XMP data is too long" error. + if "xmp" in img.info: + img.info.pop("xmp") + output = io.BytesIO() img.save(output, **_build_save_kwargs(config=config, icc_profile_bytes=icc_profile_bytes)) output.seek(0) From 99679503af32b975d3ad9081bf3ce46dfbe107b6 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 30 Jan 2026 02:11:46 -0800 Subject: [PATCH 04/76] Add claude.md to gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 8073a1c8b..111414245 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ coverage.xml desktop-tool/export/ desktop-tool/tests/export/ desktop-tool/tests/cards/ +desktop-tool/TODO.md cover/ @@ -148,6 +149,7 @@ dmypy.json cython_debug/ .idea +.cursor/ .DS_Store MPCAutofill/card_db.db MPCAutofill/build @@ -182,3 +184,6 @@ frontend/public/mockServiceWorker.js # Plan files *.plan.md + +# Claude Code +claude.md From 2716782d258bcc08d9dd5bf0b6c51cc39cd6d6f6 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 1 Feb 2026 23:50:18 -0800 Subject: [PATCH 05/76] Use undetected-chromedriver for Cloudflare bypass - Replace standard selenium Chrome driver with undetected-chromedriver - Add aggressive polling (500ms) for Cloudflare challenge detection - Add auto-click attempt for Turnstile checkbox in iframe - Fix login modal selector to target correct link - Remove set_network_conditions calls (potential detection vector) - Add setuptools dependency for Python 3.13 compatibility --- desktop-tool/requirements.txt | 1 + desktop-tool/src/constants.py | 3 +- desktop-tool/src/driver.py | 344 ++++++++++++++++++++++++--------- desktop-tool/src/webdrivers.py | 69 +++---- 4 files changed, 281 insertions(+), 136 deletions(-) diff --git a/desktop-tool/requirements.txt b/desktop-tool/requirements.txt index ab6075257..2eee0bcda 100644 --- a/desktop-tool/requirements.txt +++ b/desktop-tool/requirements.txt @@ -19,4 +19,5 @@ ratelimit~=2.2.1 requests~=2.32.5 sanitize-filename~=1.2.0 selenium~=4.35 +undetected-chromedriver~=3.5.5 wakepy==0.6.0 diff --git a/desktop-tool/src/constants.py b/desktop-tool/src/constants.py index 6c4b88fa9..c50007b7b 100644 --- a/desktop-tool/src/constants.py +++ b/desktop-tool/src/constants.py @@ -162,7 +162,8 @@ class DriveThruCardsSelectors: continue_selector: str = attr.ib(default="") # Login selectors - two step process: click login button, then click "Go to Log in" link login_button_selector: str = attr.ib(default="button[data-cy='login']") - go_to_login_selector: str = attr.ib(default="a[href='/en/']") + # Target the login link in the modal, not the logout link (both can have href='/en/') + go_to_login_selector: str = attr.ib(default=".modal a[href='/en/'], .modal-content a[href='/en/']") # Publisher Tools link only appears when logged in as a publisher logged_in_indicator_selector: str = attr.ib(default="a[href*='pub_tools.php']") diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 3f84e6cac..36be3d80e 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -334,42 +334,135 @@ def handle_alert(self) -> None: # region DriveThruCards + def _try_click_turnstile_checkbox(self) -> bool: + """ + Attempt to find and click the Cloudflare Turnstile checkbox. + Returns True if checkbox was found and clicked, False otherwise. + """ + try: + # Turnstile is rendered in an iframe - find it by common attributes + iframe_selectors = [ + "iframe[src*='challenges.cloudflare.com']", + "iframe[title*='cloudflare']", + "iframe[title*='Cloudflare']", + "iframe[id*='cf-']", + ] + + iframe = None + for selector in iframe_selectors: + iframes = self.driver.find_elements(By.CSS_SELECTOR, selector) + if iframes: + iframe = iframes[0] + break + + if not iframe: + return False + + # Switch to iframe context + self.driver.switch_to.frame(iframe) + + try: + # Look for the checkbox input or clickable verification element + checkbox_selectors = [ + "input[type='checkbox']", + ".ctp-checkbox-label", + "#challenge-stage", + "[data-testid='challenge-input']", + ] + + for selector in checkbox_selectors: + elements = self.driver.find_elements(By.CSS_SELECTOR, selector) + for element in elements: + if element.is_displayed(): + element.click() + logger.debug("Clicked Turnstile checkbox") + return True + finally: + # Always switch back to main content + self.driver.switch_to.default_content() + + return False + except Exception as e: + logger.debug(f"Error clicking Turnstile checkbox: {e}") + # Ensure we're back in main content even on error + try: + self.driver.switch_to.default_content() + except Exception: + pass + return False + + def _is_cloudflare_challenge_active(self) -> bool: + """Check if a Cloudflare challenge page is currently displayed.""" + try: + title = self.driver.title.lower() + if "just a moment" in title: + return True + # Also check for challenge body text + body_text = self.driver.find_element(By.TAG_NAME, "body").text.lower() + if "verifying you are human" in body_text or "checking your browser" in body_text: + return True + return False + except Exception: + return False + + def _is_site_loaded(self) -> bool: + """Check if the actual site content has loaded (past Cloudflare).""" + selectors = self.target_site.value.selectors + try: + title = self.driver.title.lower() + if "just a moment" in title: + return False + # Check for login button or logged-in indicator + login_btns = self.driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) + logged_in = self.driver.find_elements(By.CSS_SELECTOR, selectors.logged_in_indicator_selector) + return bool(login_btns or logged_in) + except Exception: + return False + def wait_for_cloudflare_challenge(self, timeout_seconds: int = 300) -> None: """ Wait for the Cloudflare challenge to be completed by waiting for site content to appear. + Uses aggressive polling and attempts to auto-click the Turnstile checkbox. Waits for either the login button or Publisher Tools link (if already logged in). """ self.set_state(States.defining_order, "Waiting for site to load") - logger.info( - "Waiting for DriveThruCards to load...\n" - "If you see a Cloudflare captcha, please complete it." - ) + logger.info("Waiting for DriveThruCards to load...") - selectors = self.target_site.value.selectors - - def check_site_loaded(driver: WebDriver) -> bool: - try: - title = driver.title.lower() - # During Cloudflare, page title is "Just a moment..." - if "just a moment" in title: - return False - # Check for login button or logged-in indicator - login_btns = driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) - logged_in = driver.find_elements(By.CSS_SELECTOR, selectors.logged_in_indicator_selector) - if login_btns or logged_in: - return True - # Page loaded but elements not found yet - log for debugging - logger.debug(f"Page title: '{title}', login buttons: {len(login_btns)}, logged_in indicators: {len(logged_in)}") - return False - except Exception as e: - logger.debug(f"Error checking site loaded: {e}") - return False - - try: - WebDriverWait(self.driver, timeout_seconds).until(check_site_loaded) - logger.info("Site loaded successfully!") - except sl_exc.TimeoutException: - logger.warning(f"Timeout after {timeout_seconds}s waiting for site to load. Attempting to continue anyway.") + poll_interval = 0.5 # Check every 500ms for responsive detection + turnstile_click_interval = 3.0 # Try clicking Turnstile every 3 seconds + last_turnstile_attempt = 0.0 + challenge_detected = False + start_time = time.time() + + while time.time() - start_time < timeout_seconds: + # Check if site has loaded successfully + if self._is_site_loaded(): + logger.info("Site loaded successfully!") + return + + # Check if we're on a Cloudflare challenge + if self._is_cloudflare_challenge_active(): + if not challenge_detected: + challenge_detected = True + logger.info( + "Cloudflare challenge detected. Attempting auto-solve...\n" + "If this doesn't work, please complete the captcha manually." + ) + + # Periodically try to click the Turnstile checkbox + current_time = time.time() + if current_time - last_turnstile_attempt >= turnstile_click_interval: + last_turnstile_attempt = current_time + if self._try_click_turnstile_checkbox(): + logger.debug("Turnstile click attempted, waiting for verification...") + + time.sleep(poll_interval) + + # Timeout reached + logger.warning( + f"Timeout after {timeout_seconds}s waiting for site to load. " + "Attempting to continue anyway." + ) def is_dtc_user_authenticated(self) -> bool: """Check if the user is logged in to DriveThruCards.""" @@ -413,11 +506,120 @@ def click_element_with_retry(self, element: Any) -> bool: return False + def click_element_polling(self, by: By, selector: str, timeout: int = 30) -> bool: + """ + Aggressively poll for an element and click it as soon as it's available. + No fixed waits - keeps trying until success or timeout. + """ + start = time.time() + while time.time() - start < timeout: + try: + elements = self.driver.find_elements(by, selector) + for el in elements: + if el.is_displayed() and self.click_element_with_retry(el): + return True + except Exception: + pass + return False + + def _click_dtc_login_button(self) -> bool: + """Click the DriveThruCards login button to open the login modal.""" + selectors = self.target_site.value.selectors + return self.click_element_polling(By.CSS_SELECTOR, selectors.login_button_selector, timeout=15) + + def _simulate_human_behavior(self) -> None: + """ + Simulate human-like behavior to help bypass bot detection. + Adds natural mouse movements and small delays. + """ + try: + # Move mouse to a neutral position and perform small movements + body = self.driver.find_element(By.TAG_NAME, "body") + actions = ActionChains(self.driver) + # Small random-ish movements to simulate human behavior + actions.move_to_element(body) + actions.move_by_offset(50, 30) + actions.pause(0.1) + actions.move_by_offset(-20, 15) + actions.pause(0.1) + actions.perform() + logger.debug("Simulated human mouse movements") + except Exception as e: + logger.debug(f"Could not simulate mouse movements: {e}") + + def _trigger_visibility_change_via_minimize(self) -> bool: + """ + Trigger visibility change by minimizing and restoring the window. + This causes real browser visibility events without the jarring tab switch. + """ + try: + # Store current window size/position to restore later + original_rect = self.driver.get_window_rect() + + # Minimize triggers visibilitychange (document.hidden = true) + self.driver.minimize_window() + time.sleep(0.15) + + # Restore triggers visibilitychange (document.hidden = false) and focus + self.driver.set_window_rect( + x=original_rect['x'], + y=original_rect['y'], + width=original_rect['width'], + height=original_rect['height'] + ) + time.sleep(0.1) + + logger.debug("Triggered visibility change via window minimize/restore") + return True + except Exception as e: + logger.debug(f"Window minimize/restore failed: {e}") + return False + + def _perform_tab_switch_workaround(self) -> bool: + """ + Fallback workaround: open a new tab, close it, return to original. + This triggers real browser visibility/focus events that reset bot detection state. + """ + try: + original_window = self.driver.current_window_handle + self.driver.switch_to.new_window('tab') + self.driver.close() + self.driver.switch_to.window(original_window) + logger.debug("Tab switch workaround completed") + return True + except Exception as e: + logger.debug(f"Tab switch workaround failed: {e}") + return False + + def _perform_login_focus_workaround(self) -> bool: + """ + Workaround for DriveThruCards bot detection. + The site shows "Unable to log in" error on automated first attempts. + + The bot detection appears to track whether the page has received natural + visibility/focus events. By triggering these events, we reset the detection state. + + Tries minimize/restore first (cleanest), falls back to tab switch if needed. + """ + logger.debug("Performing focus workaround to bypass bot detection...") + + # First, add some human-like behavior + self._simulate_human_behavior() + + # Try minimize/restore approach first (cleaner than tab switch) + if self._trigger_visibility_change_via_minimize(): + return True + + # Fallback to tab switch (more reliable but visible to user) + logger.debug("Minimize/restore failed, falling back to tab switch workaround") + return self._perform_tab_switch_workaround() + def authenticate_dtc(self) -> None: """ Handle DriveThruCards login flow. - Two-step process: click login button, then click "Go to Log in" link. - Waits for user to complete authentication. + + Note: DriveThruCards has bot detection that shows "Unable to log in" error. + Workaround: Click "log in with D20" then hit back before actual login. """ selectors = self.target_site.value.selectors @@ -426,76 +628,32 @@ def authenticate_dtc(self) -> None: return self.set_state(States.defining_order, "Awaiting DriveThruCards login") - logger.info("Please log in to your DriveThruCards account.") - # Step 1: Try to find and click the login button - login_clicked = False - try: - # Wait for element to be clickable, not just present - logger.debug(f"Looking for login button with selector: {selectors.login_button_selector}") - login_button = WebDriverWait(self.driver, 15).until( - element_to_be_clickable((By.CSS_SELECTOR, selectors.login_button_selector)) - ) - # Log info about the found element for debugging - logger.debug( - f"Found element - tag: {login_button.tag_name}, " - f"text: '{login_button.text}', " - f"displayed: {login_button.is_displayed()}, " - f"enabled: {login_button.is_enabled()}" - ) - logger.info("Found login button, attempting to click...") - login_clicked = self.click_element_with_retry(login_button) - if login_clicked: - logger.info("Successfully clicked the login button.") - else: - logger.debug("All click strategies failed for login button.") - except sl_exc.TimeoutException: - logger.debug("Timeout waiting for login button to be clickable.") - # Try to find any buttons to help debug - try: - buttons = self.driver.find_elements(By.TAG_NAME, "button") - logger.debug(f"Found {len(buttons)} button(s) on page") - for btn in buttons[:5]: # Log first 5 buttons - logger.debug(f" Button: text='{btn.text}', aria-label='{btn.get_attribute('aria-label')}'") - except Exception: - pass - except Exception as e: - logger.debug(f"Could not find/click login button: {e}") + # Perform workaround to bypass bot detection (focus change resets state) + self._perform_login_focus_workaround() + + logger.info("Please log in to your DriveThruCards account.") - if not login_clicked: + # Now attempt actual login flow + if not self._click_dtc_login_button(): logger.info( "Could not find or click login button automatically.\n" "Please click the login button manually." ) - # Step 2: Try to find and click the "Go to Log in" link - # Use XPath to find by text content since href="/en/" is too generic - go_to_login_clicked = False - try: - # Try XPath first (more specific - matches by text content) - go_to_login_link = WebDriverWait(self.driver, 10).until( - element_to_be_clickable((By.XPATH, "//a[contains(normalize-space(), 'Go to Log in')]")) - ) - logger.info("Found 'Go to Log in' link, attempting to click...") - go_to_login_clicked = self.click_element_with_retry(go_to_login_link) - if go_to_login_clicked: - logger.info("Successfully clicked 'Go to Log in' link.") - else: - logger.debug("All click strategies failed for 'Go to Log in' link.") - except sl_exc.TimeoutException: - logger.debug("Timeout waiting for 'Go to Log in' link to be clickable.") + # Click "Go to Log in" link - poll aggressively + go_to_login_clicked = self.click_element_polling( + By.XPATH, "//a[contains(normalize-space(), 'Go to Log in')]", timeout=15 + ) + if not go_to_login_clicked: # Fallback to CSS selector - try: - go_to_login_link = WebDriverWait(self.driver, 5).until( - element_to_be_clickable((By.CSS_SELECTOR, selectors.go_to_login_selector)) - ) - go_to_login_clicked = self.click_element_with_retry(go_to_login_link) - except Exception: - pass - except Exception as e: - logger.debug(f"Could not find/click 'Go to Log in' link: {e}") + go_to_login_clicked = self.click_element_polling( + By.CSS_SELECTOR, selectors.go_to_login_selector, timeout=5 + ) - if not go_to_login_clicked: + if go_to_login_clicked: + logger.info("Navigated to login page.") + else: logger.info( "Could not find 'Go to Log in' link automatically.\n" "Please navigate to the login page manually if needed." @@ -510,7 +668,7 @@ def authenticate_dtc(self) -> None: timeout_seconds = 300 start_time = time.time() while time.time() - start_time < timeout_seconds: - time.sleep(1) # Polling interval + time.sleep(1) if self.is_dtc_user_authenticated(): logger.info("Successfully logged in to DriveThruCards!") return diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 4bd2d7557..f3958968a 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -1,7 +1,8 @@ import sys from typing import Optional -from selenium.webdriver import Chrome, Edge, Firefox +import undetected_chromedriver as uc +from selenium.webdriver import Edge, Firefox from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.chromium.options import ChromiumOptions from selenium.webdriver.chromium.webdriver import ChromiumDriver @@ -9,59 +10,42 @@ from selenium.webdriver.firefox.options import Options as FirefoxOptions -def _apply_stealth_options(options: ChromeOptions) -> None: +def get_chrome_driver( + headless: bool = False, + binary_location: Optional[str] = None, + remote_debugging_port: Optional[int] = None, +) -> uc.Chrome: """ - Apply anti-bot-detection options to Chrome/Chromium browsers. - This helps bypass Cloudflare and similar bot detection systems. + Create a Chrome driver using undetected-chromedriver to bypass bot detection. + This automatically handles Cloudflare and similar anti-bot systems. """ - # Disable automation indicators - options.add_argument("--disable-blink-features=AutomationControlled") - # Exclude automation switches that reveal Selenium - options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"]) - # Disable the "Chrome is being controlled by automated software" infobar - options.add_experimental_option("useAutomationExtension", False) - - -def _apply_stealth_cdp(driver: Chrome) -> None: - """ - Use Chrome DevTools Protocol to further hide automation indicators. - """ - # Remove navigator.webdriver flag - driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { - "source": """ - Object.defineProperty(navigator, 'webdriver', { - get: () => undefined - }); - """ - }) - - -def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: - options = ChromeOptions() + options = uc.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") - _apply_stealth_options(options) - options.add_experimental_option("detach", True) + if remote_debugging_port is not None: + options.add_argument(f"--remote-debugging-port={remote_debugging_port}") if binary_location is not None: options.binary_location = binary_location - driver = Chrome(options=options) - _apply_stealth_cdp(driver) - driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) + + # undetected-chromedriver handles stealth automatically + # Detect Chrome version from browser or use explicit version if needed + driver = uc.Chrome(options=options, version_main=144) return driver -def get_brave_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: - options = ChromeOptions() +def get_brave_driver(headless: bool = False, binary_location: Optional[str] = None) -> uc.Chrome: + """ + Create a Brave driver using undetected-chromedriver. + """ + options = uc.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") - _apply_stealth_options(options) - options.add_experimental_option("detach", True) # the binary location for brave must be manually specified (otherwise chrome will open instead) if binary_location is not None: @@ -78,13 +62,15 @@ def get_brave_driver(headless: bool = False, binary_location: Optional[str] = No ) options.binary_location = default_binary_locations[sys.platform] - driver = Chrome(options=options) - _apply_stealth_cdp(driver) - driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) + driver = uc.Chrome(options=options) return driver def get_edge_driver(headless: bool = False, binary_location: Optional[str] = None) -> ChromiumDriver: + """ + Create an Edge driver with stealth options. + Note: Edge doesn't have an undetected variant, so we use standard stealth measures. + """ options: ChromiumOptions = EdgeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") @@ -98,7 +84,7 @@ def get_edge_driver(headless: bool = False, binary_location: Optional[str] = Non if binary_location is not None: options.binary_location = binary_location driver: ChromiumDriver = Edge(options=options) # type: ignore - # Apply CDP stealth for Edge as well + # Apply CDP stealth for Edge driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { @@ -106,7 +92,6 @@ def get_edge_driver(headless: bool = False, binary_location: Optional[str] = Non }); """ }) - driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) return driver From 77e5b273735ca8b2714dea98f323245dd89a88f4 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 1 Feb 2026 23:55:24 -0800 Subject: [PATCH 06/76] Add dynamic Chrome version detection for undetected-chromedriver - Remove unused ChromeOptions import - Add _detect_chrome_version() to query installed Chrome version - Support macOS, Windows, and Linux version detection - Pass detected version to undetected-chromedriver to fix version mismatch --- desktop-tool/src/webdrivers.py | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index f3958968a..4fd3179f8 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -1,15 +1,56 @@ +import re +import subprocess import sys from typing import Optional import undetected_chromedriver as uc from selenium.webdriver import Edge, Firefox -from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.chromium.options import ChromiumOptions from selenium.webdriver.chromium.webdriver import ChromiumDriver from selenium.webdriver.edge.options import Options as EdgeOptions from selenium.webdriver.firefox.options import Options as FirefoxOptions +def _detect_chrome_version() -> Optional[int]: + """ + Detect the installed Chrome version by querying the browser. + Returns the major version number (e.g., 144) or None if detection fails. + """ + try: + if sys.platform == "darwin": + # macOS: Use the Chrome binary to get version + result = subprocess.run( + ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"], + capture_output=True, + text=True, + timeout=5, + ) + elif sys.platform == "win32": + # Windows: Query registry or use wmic + result = subprocess.run( + ["reg", "query", r"HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon", "/v", "version"], + capture_output=True, + text=True, + timeout=5, + ) + else: + # Linux: Use google-chrome binary + result = subprocess.run( + ["google-chrome", "--version"], + capture_output=True, + text=True, + timeout=5, + ) + + # Extract version number from output (e.g., "Google Chrome 144.0.7559.110") + match = re.search(r"(\d+)\.\d+\.\d+\.\d+", result.stdout) + if match: + return int(match.group(1)) + except Exception: + pass + return None + + def get_chrome_driver( headless: bool = False, binary_location: Optional[str] = None, @@ -31,8 +72,9 @@ def get_chrome_driver( options.binary_location = binary_location # undetected-chromedriver handles stealth automatically - # Detect Chrome version from browser or use explicit version if needed - driver = uc.Chrome(options=options, version_main=144) + # Detect Chrome version since auto-detection can fail + version_main = _detect_chrome_version() + driver = uc.Chrome(options=options, version_main=version_main) return driver From e00a26f5947cdb71d6b5a980ef2308d53f48c477 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 1 Feb 2026 23:57:51 -0800 Subject: [PATCH 07/76] Add setuptools dependency for Python 3.13 compatibility --- desktop-tool/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop-tool/requirements.txt b/desktop-tool/requirements.txt index 2eee0bcda..a5b08a836 100644 --- a/desktop-tool/requirements.txt +++ b/desktop-tool/requirements.txt @@ -19,5 +19,6 @@ ratelimit~=2.2.1 requests~=2.32.5 sanitize-filename~=1.2.0 selenium~=4.35 +setuptools # Required for undetected-chromedriver on Python 3.13+ (distutils removed) undetected-chromedriver~=3.5.5 wakepy==0.6.0 From 20b418861c16068e6746109a2b93fa40709f5d0d Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Mon, 2 Feb 2026 00:25:13 -0800 Subject: [PATCH 08/76] Add stealth measures for DriveThruCards login (MVP) - Add JavaScript patches to hide automation traces (webdriver property, chrome object, plugins, languages, ChromeDriver detection variables) - Add --disable-blink-features=AutomationControlled flag - Remove ineffective focus workaround that caused unnecessary delays --- desktop-tool/src/driver.py | 6 ---- desktop-tool/src/webdrivers.py | 54 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 36be3d80e..661d45df7 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -617,9 +617,6 @@ def _perform_login_focus_workaround(self) -> bool: def authenticate_dtc(self) -> None: """ Handle DriveThruCards login flow. - - Note: DriveThruCards has bot detection that shows "Unable to log in" error. - Workaround: Click "log in with D20" then hit back before actual login. """ selectors = self.target_site.value.selectors @@ -629,9 +626,6 @@ def authenticate_dtc(self) -> None: self.set_state(States.defining_order, "Awaiting DriveThruCards login") - # Perform workaround to bypass bot detection (focus change resets state) - self._perform_login_focus_workaround() - logger.info("Please log in to your DriveThruCards account.") # Now attempt actual login flow diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 4fd3179f8..842b702cf 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -51,6 +51,55 @@ def _detect_chrome_version() -> Optional[int]: return None +def _apply_stealth_scripts(driver: uc.Chrome) -> None: + """ + Apply additional stealth JavaScript to hide automation traces. + These patches help bypass bot detection on sites like DriveThruCards. + """ + stealth_js = """ + // Hide webdriver property + Object.defineProperty(navigator, 'webdriver', { + get: () => undefined + }); + + // Fix chrome object + window.chrome = { + runtime: {}, + loadTimes: function() {}, + csi: function() {}, + app: {} + }; + + // Fix permissions query + const originalQuery = window.navigator.permissions.query; + window.navigator.permissions.query = (parameters) => ( + parameters.name === 'notifications' ? + Promise.resolve({ state: Notification.permission }) : + originalQuery(parameters) + ); + + // Fix plugins to look more realistic + Object.defineProperty(navigator, 'plugins', { + get: () => [ + { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' }, + { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' }, + { name: 'Native Client', filename: 'internal-nacl-plugin' } + ] + }); + + // Fix languages + Object.defineProperty(navigator, 'languages', { + get: () => ['en-US', 'en'] + }); + + // Remove automation-related properties from window + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; + """ + driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": stealth_js}) + + def get_chrome_driver( headless: bool = False, binary_location: Optional[str] = None, @@ -64,6 +113,7 @@ def get_chrome_driver( options.add_argument("--no-sandbox") options.add_argument("--log-level=3") options.add_argument("--disable-dev-shm-usage") + options.add_argument("--disable-blink-features=AutomationControlled") if headless: options.add_argument("--headless=new") if remote_debugging_port is not None: @@ -75,6 +125,10 @@ def get_chrome_driver( # Detect Chrome version since auto-detection can fail version_main = _detect_chrome_version() driver = uc.Chrome(options=options, version_main=version_main) + + # Apply additional stealth scripts + _apply_stealth_scripts(driver) + return driver From 3d532e714abbb18c2c66c05eba290a32619d75d7 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Mon, 2 Feb 2026 00:37:10 -0800 Subject: [PATCH 09/76] Fix slow login after Cloudflare CAPTCHA by disabling implicit wait during polling Selenium's implicit wait (5s) was causing long delays between solving the Cloudflare CAPTCHA and clicking the login button. Fixed by temporarily disabling implicit wait in polling methods: - _try_click_turnstile_checkbox() - _is_cloudflare_challenge_active() - _is_site_loaded() - is_dtc_user_authenticated() - click_element_polling() --- desktop-tool/src/driver.py | 159 ++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 66 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 661d45df7..297455fd7 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -340,48 +340,53 @@ def _try_click_turnstile_checkbox(self) -> bool: Returns True if checkbox was found and clicked, False otherwise. """ try: - # Turnstile is rendered in an iframe - find it by common attributes - iframe_selectors = [ - "iframe[src*='challenges.cloudflare.com']", - "iframe[title*='cloudflare']", - "iframe[title*='Cloudflare']", - "iframe[id*='cf-']", - ] + # Disable implicit wait for fast polling + self.driver.implicitly_wait(0) + try: + # Turnstile is rendered in an iframe - find it by common attributes + iframe_selectors = [ + "iframe[src*='challenges.cloudflare.com']", + "iframe[title*='cloudflare']", + "iframe[title*='Cloudflare']", + "iframe[id*='cf-']", + ] - iframe = None - for selector in iframe_selectors: - iframes = self.driver.find_elements(By.CSS_SELECTOR, selector) - if iframes: - iframe = iframes[0] - break + iframe = None + for selector in iframe_selectors: + iframes = self.driver.find_elements(By.CSS_SELECTOR, selector) + if iframes: + iframe = iframes[0] + break - if not iframe: - return False + if not iframe: + return False - # Switch to iframe context - self.driver.switch_to.frame(iframe) + # Switch to iframe context + self.driver.switch_to.frame(iframe) - try: - # Look for the checkbox input or clickable verification element - checkbox_selectors = [ - "input[type='checkbox']", - ".ctp-checkbox-label", - "#challenge-stage", - "[data-testid='challenge-input']", - ] + try: + # Look for the checkbox input or clickable verification element + checkbox_selectors = [ + "input[type='checkbox']", + ".ctp-checkbox-label", + "#challenge-stage", + "[data-testid='challenge-input']", + ] + + for selector in checkbox_selectors: + elements = self.driver.find_elements(By.CSS_SELECTOR, selector) + for element in elements: + if element.is_displayed(): + element.click() + logger.debug("Clicked Turnstile checkbox") + return True + finally: + # Always switch back to main content + self.driver.switch_to.default_content() - for selector in checkbox_selectors: - elements = self.driver.find_elements(By.CSS_SELECTOR, selector) - for element in elements: - if element.is_displayed(): - element.click() - logger.debug("Clicked Turnstile checkbox") - return True + return False finally: - # Always switch back to main content - self.driver.switch_to.default_content() - - return False + self.driver.implicitly_wait(5) except Exception as e: logger.debug(f"Error clicking Turnstile checkbox: {e}") # Ensure we're back in main content even on error @@ -397,11 +402,16 @@ def _is_cloudflare_challenge_active(self) -> bool: title = self.driver.title.lower() if "just a moment" in title: return True - # Also check for challenge body text - body_text = self.driver.find_element(By.TAG_NAME, "body").text.lower() - if "verifying you are human" in body_text or "checking your browser" in body_text: - return True - return False + # Temporarily disable implicit wait for fast polling + self.driver.implicitly_wait(0) + try: + # Also check for challenge body text + body_text = self.driver.find_element(By.TAG_NAME, "body").text.lower() + if "verifying you are human" in body_text or "checking your browser" in body_text: + return True + return False + finally: + self.driver.implicitly_wait(5) except Exception: return False @@ -412,10 +422,16 @@ def _is_site_loaded(self) -> bool: title = self.driver.title.lower() if "just a moment" in title: return False - # Check for login button or logged-in indicator - login_btns = self.driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) - logged_in = self.driver.find_elements(By.CSS_SELECTOR, selectors.logged_in_indicator_selector) - return bool(login_btns or logged_in) + # Temporarily disable implicit wait for fast polling + self.driver.implicitly_wait(0) + try: + # Check for login button or logged-in indicator + login_btns = self.driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) + logged_in = self.driver.find_elements(By.CSS_SELECTOR, selectors.logged_in_indicator_selector) + return bool(login_btns or logged_in) + finally: + # Restore implicit wait + self.driver.implicitly_wait(5) except Exception: return False @@ -468,18 +484,23 @@ def is_dtc_user_authenticated(self) -> bool: """Check if the user is logged in to DriveThruCards.""" selectors = self.target_site.value.selectors try: - # Look for logout button as indicator of being logged in - # This needs to be specific - only match elements that appear when logged in - logged_in_elements = self.driver.find_elements( - By.CSS_SELECTOR, selectors.logged_in_indicator_selector - ) - # Filter to only visible elements - visible_elements = [el for el in logged_in_elements if el.is_displayed()] - if visible_elements: - logger.debug(f"Found {len(visible_elements)} visible logged-in indicator(s)") - for el in visible_elements[:3]: - logger.debug(f" Element: tag={el.tag_name}, text='{el.text}', aria-label='{el.get_attribute('aria-label')}'") - return len(visible_elements) > 0 + # Temporarily disable implicit wait for fast check + self.driver.implicitly_wait(0) + try: + # Look for logout button as indicator of being logged in + # This needs to be specific - only match elements that appear when logged in + logged_in_elements = self.driver.find_elements( + By.CSS_SELECTOR, selectors.logged_in_indicator_selector + ) + # Filter to only visible elements + visible_elements = [el for el in logged_in_elements if el.is_displayed()] + if visible_elements: + logger.debug(f"Found {len(visible_elements)} visible logged-in indicator(s)") + for el in visible_elements[:3]: + logger.debug(f" Element: tag={el.tag_name}, text='{el.text}', aria-label='{el.get_attribute('aria-label')}'") + return len(visible_elements) > 0 + finally: + self.driver.implicitly_wait(5) except Exception as e: logger.debug(f"Error checking auth status: {e}") return False @@ -512,15 +533,21 @@ def click_element_polling(self, by: By, selector: str, timeout: int = 30) -> boo No fixed waits - keeps trying until success or timeout. """ start = time.time() - while time.time() - start < timeout: - try: - elements = self.driver.find_elements(by, selector) - for el in elements: - if el.is_displayed() and self.click_element_with_retry(el): - return True - except Exception: - pass - return False + # Disable implicit wait for true aggressive polling + self.driver.implicitly_wait(0) + try: + while time.time() - start < timeout: + try: + elements = self.driver.find_elements(by, selector) + for el in elements: + if el.is_displayed() and self.click_element_with_retry(el): + return True + except Exception: + pass + time.sleep(0.1) # Small delay to avoid CPU spinning + return False + finally: + self.driver.implicitly_wait(5) def _click_dtc_login_button(self) -> bool: """Click the DriveThruCards login button to open the login modal.""" From 752a3eb69ff48277993d7bd6c3f6bab7742c0825 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Mon, 2 Feb 2026 00:44:38 -0800 Subject: [PATCH 10/76] Remove unused login workaround methods These methods were part of a failed attempt to bypass bot detection by triggering visibility/focus events. They are no longer called: - _simulate_human_behavior() - _trigger_visibility_change_via_minimize() - _perform_tab_switch_workaround() - _perform_login_focus_workaround() Also added commit/cleanup workflow guidance to CLAUDE.md. --- desktop-tool/src/driver.py | 87 -------------------------------------- 1 file changed, 87 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 297455fd7..3396ab1e1 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -554,93 +554,6 @@ def _click_dtc_login_button(self) -> bool: selectors = self.target_site.value.selectors return self.click_element_polling(By.CSS_SELECTOR, selectors.login_button_selector, timeout=15) - def _simulate_human_behavior(self) -> None: - """ - Simulate human-like behavior to help bypass bot detection. - Adds natural mouse movements and small delays. - """ - try: - # Move mouse to a neutral position and perform small movements - body = self.driver.find_element(By.TAG_NAME, "body") - actions = ActionChains(self.driver) - # Small random-ish movements to simulate human behavior - actions.move_to_element(body) - actions.move_by_offset(50, 30) - actions.pause(0.1) - actions.move_by_offset(-20, 15) - actions.pause(0.1) - actions.perform() - logger.debug("Simulated human mouse movements") - except Exception as e: - logger.debug(f"Could not simulate mouse movements: {e}") - - def _trigger_visibility_change_via_minimize(self) -> bool: - """ - Trigger visibility change by minimizing and restoring the window. - This causes real browser visibility events without the jarring tab switch. - """ - try: - # Store current window size/position to restore later - original_rect = self.driver.get_window_rect() - - # Minimize triggers visibilitychange (document.hidden = true) - self.driver.minimize_window() - time.sleep(0.15) - - # Restore triggers visibilitychange (document.hidden = false) and focus - self.driver.set_window_rect( - x=original_rect['x'], - y=original_rect['y'], - width=original_rect['width'], - height=original_rect['height'] - ) - time.sleep(0.1) - - logger.debug("Triggered visibility change via window minimize/restore") - return True - except Exception as e: - logger.debug(f"Window minimize/restore failed: {e}") - return False - - def _perform_tab_switch_workaround(self) -> bool: - """ - Fallback workaround: open a new tab, close it, return to original. - This triggers real browser visibility/focus events that reset bot detection state. - """ - try: - original_window = self.driver.current_window_handle - self.driver.switch_to.new_window('tab') - self.driver.close() - self.driver.switch_to.window(original_window) - logger.debug("Tab switch workaround completed") - return True - except Exception as e: - logger.debug(f"Tab switch workaround failed: {e}") - return False - - def _perform_login_focus_workaround(self) -> bool: - """ - Workaround for DriveThruCards bot detection. - The site shows "Unable to log in" error on automated first attempts. - - The bot detection appears to track whether the page has received natural - visibility/focus events. By triggering these events, we reset the detection state. - - Tries minimize/restore first (cleanest), falls back to tab switch if needed. - """ - logger.debug("Performing focus workaround to bypass bot detection...") - - # First, add some human-like behavior - self._simulate_human_behavior() - - # Try minimize/restore approach first (cleaner than tab switch) - if self._trigger_visibility_change_via_minimize(): - return True - - # Fallback to tab switch (more reliable but visible to user) - logger.debug("Minimize/restore failed, falling back to tab switch workaround") - return self._perform_tab_switch_workaround() - def authenticate_dtc(self) -> None: """ Handle DriveThruCards login flow. From 832c15dd4516763d1ceba9c488a061699c18c755 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 6 Feb 2026 03:13:43 -0800 Subject: [PATCH 11/76] Add consistent order ending message --- desktop-tool/autofill.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 40213e7f4..adbdfd73c 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -60,6 +60,14 @@ def get_default_dtc_icc_profile() -> Optional[str]: return str(candidate) if candidate.is_file() else None +def wait_for_user_to_complete_order() -> None: + input( + f"If this software has brought you joy and you'd like to throw a few bucks my way,\n" + f"you can find my tip jar here: {bold('https://www.buymeacoffee.com/chilli.axe')} Thank you!\n\n" + f"Press {bold('Enter')} to close this window - your browser window will remain open.\n" + ) + + def ensure_ghostscript_available() -> str: while True: gs_path = get_ghostscript_path() @@ -354,6 +362,7 @@ def main( ).execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") + wait_for_user_to_complete_order() elif exportpdf: PdfExporter(order=CardOrder.from_xmls_in_folder(working_directory=working_directory)[0]).execute( post_processing_config=post_processing_config @@ -375,11 +384,7 @@ def main( auto_save_threshold=auto_save_threshold if auto_save else None, post_processing_config=post_processing_config, ) - input( - f"If this software has brought you joy and you'd like to throw a few bucks my way,\n" - f"you can find my tip jar here: {bold('https://www.buymeacoffee.com/chilli.axe')} Thank you!\n\n" - f"Press {bold('Enter')} to close this window - your browser window will remain open.\n" - ) + wait_for_user_to_complete_order() except ValidationException as e: input(f"There was a problem with your order file:\n\n{bold(e)}\n\nPress Enter to exit.") sys.exit(0) From d27f9eb37bc35248c5adc42f2d7cdc08327cda9a Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 6 Feb 2026 03:47:57 -0800 Subject: [PATCH 12/76] Fix continue button click after PDF upload by polling for activation The 'Click here after uploading your files' button starts with an onclick handler that shows an error and returns false. After server-side upload validation, the page JS replaces this with the actual form submit handler. Previously the click fired before this replacement, causing the page to not navigate. --- desktop-tool/src/driver.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 3396ab1e1..e87f67af7 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -953,15 +953,24 @@ def find_and_use_file_input() -> bool: except sl_exc.TimeoutException: logger.warning("Could not confirm upload completion. Please verify manually.") - # Click the continue button + # Wait for the continue button to become active. + # The button starts with an onclick handler that shows an error and returns false. + # After upload validation, the page JS replaces this handler to enable navigation. + # We detect activation by checking that the onclick no longer contains "return false". try: - continue_button = WebDriverWait(self.driver, 10).until( - element_to_be_clickable((By.ID, "continue_button")) - ) + def continue_button_is_active(d: Any) -> Any: + btn = d.find_element(By.ID, "continue_button") + onclick = btn.get_attribute("onclick") or "" + if "return false" in onclick: + return False + return btn + + continue_button = WebDriverWait(self.driver, 60).until(continue_button_is_active) + logger.debug(f"Continue button activated. onclick: {continue_button.get_attribute('onclick')}") self.driver.execute_script("arguments[0].click();", continue_button) logger.info("Clicked 'Click here after uploading your files' button.") except sl_exc.TimeoutException: - logger.warning("Could not find continue button. Please click it manually.") + logger.warning("Continue button did not activate. Please click it manually.") # Click the "Complete Setup" button on the next page try: From fb2b01695c90e5b15d64dfa2ae283ed9ff3ec943 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 6 Feb 2026 03:53:01 -0800 Subject: [PATCH 13/76] Navigate directly to buy_now URL instead of clicking target=_blank link --- desktop-tool/src/driver.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index e87f67af7..738258094 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -982,13 +982,19 @@ def continue_button_is_active(d: Any) -> Any: except sl_exc.TimeoutException: logger.warning("Could not find 'Complete Setup' button. Please click it manually.") - # Click the "buy now" link on the next page + # Click the "buy now" link to start placing the order. + # This link has target="_blank", so navigate directly to avoid new-tab issues. try: buy_now_link = WebDriverWait(self.driver, 30).until( element_to_be_clickable((By.CSS_SELECTOR, "a[href*='action=buy_now']")) ) - self.driver.execute_script("arguments[0].click();", buy_now_link) - logger.info("Clicked 'buy now' link.") + buy_now_href = buy_now_link.get_attribute("href") + if buy_now_href: + self.driver.get(buy_now_href) + logger.info("Navigated to 'buy now' page to start placing the order.") + else: + self.driver.execute_script("arguments[0].click();", buy_now_link) + logger.info("Clicked 'buy now' link.") except sl_exc.TimeoutException: logger.warning("Could not find 'buy now' link. Please click it manually.") except Exception as e: From f52ef6820964884dfcdea429efd736f5854e5750 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 6 Feb 2026 04:00:32 -0800 Subject: [PATCH 14/76] Navigate to upload page in same tab instead of opening new window --- desktop-tool/src/driver.py | 39 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 738258094..35c6d4083 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -1,4 +1,5 @@ import datetime as dt +import re import textwrap import time from concurrent.futures import ThreadPoolExecutor @@ -740,36 +741,28 @@ def submit_dtc_description_page(self) -> None: def open_dtc_upload_page(self) -> None: """ - Click 'Upload print-ready file' button which opens a new tab, - then switch to that tab. - Waits for button to be available (page loaded from previous step). + Navigate to the upload page by extracting the URL from the 'Upload print-ready file' + button's onclick attribute, rather than letting it open a new tab via window.open(). """ self.set_state(States.defining_order, "Opening upload page") - - # Store current window handles - original_windows = set(self.driver.window_handles) - + try: - # Wait for and click the upload button upload_button = WebDriverWait(self.driver, 15).until( element_to_be_clickable((By.XPATH, "//button[contains(@onclick, 'pub_upload_podcard_files.php')]")) ) - logger.info("Clicking 'Upload print-ready file' button...") - self.click_element_with_retry(upload_button) - - # Wait for new window/tab to open (poll until a new window appears) - WebDriverWait(self.driver, 15).until( - lambda d: len(d.window_handles) > len(original_windows) - ) - - # Find the new window and switch to it - new_windows = set(self.driver.window_handles) - original_windows - if new_windows: - new_window = new_windows.pop() - self.driver.switch_to.window(new_window) - logger.info(f"Switched to upload tab: {self.driver.current_url}") + onclick = upload_button.get_attribute("onclick") or "" + # Extract URL from onclick like: window.open('https://...pub_upload_podcard_files.php?products_id=123'); + url_match = re.search(r"window\.open\(['\"]([^'\"]+)['\"]\)", onclick) + if url_match: + upload_url = url_match.group(1) + self.driver.execute_script("window.location.href = arguments[0];", upload_url) + # Wait for the upload page to load + WebDriverWait(self.driver, 15).until( + presence_of_element_located((By.ID, "card_type_select")) + ) + logger.info(f"Navigated to upload page: {self.driver.current_url}") else: - logger.warning("No new tab detected after clicking upload button.") + logger.warning(f"Could not extract URL from upload button onclick: {onclick}") except Exception as e: logger.warning(f"Could not open upload page: {e}") From 2eb06fc4161fc0a3aba72b71ea4835417b9a7fae Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:32:12 -0800 Subject: [PATCH 15/76] Fix PDF exports to reload updated card images --- desktop-tool/src/pdf_maker.py | 6 +++++- desktop-tool/tests/test_desktop_tool.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index c14386b44..d6faa7b8c 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -229,7 +229,11 @@ def add_image(self, image_path: str) -> None: if os.path.exists(tmp_path): os.unlink(tmp_path) else: - self.pdf.image(image_path, x=0, y=0, w=self.card_width_in_inches, h=self.card_height_in_inches) + with open(image_path, "rb") as f: + image_bytes = f.read() + # Pass raw bytes so fpdf keys the cache by content hash rather than file path. + # This ensures refreshed image files are re-read when re-exporting PDFs. + self.pdf.image(image_bytes, x=0, y=0, w=self.card_width_in_inches, h=self.card_height_in_inches) def save_file(self) -> str: extra = "" diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 286930b5a..1682d0e1f 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -9,6 +9,7 @@ import pytest from enlighten import Counter +from PIL import Image from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait @@ -167,6 +168,26 @@ def do_nothing(_): remove_directories(["export/test_order", "export"]) +def test_pdf_exporter_add_image_uses_image_bytes(monkeypatch: pytest.MonkeyPatch, card_order_valid, tmp_path) -> None: + monkeypatch.setattr("src.pdf_maker.PdfExporter.ask_questions", lambda _self: None) + pdf_exporter = PdfExporter(order=card_order_valid, number_of_cards_per_file=1) + pdf_exporter.generate_pdf() + + image_path = tmp_path / "sample.png" + Image.new("RGB", (4, 4), "red").save(image_path) + + captured_name = {"value": None} + + def fake_image(name, **_kwargs): + captured_name["value"] = name + + monkeypatch.setattr(pdf_exporter.pdf, "image", fake_image) + + pdf_exporter.add_image(str(image_path)) + + assert isinstance(captured_name["value"], bytes) + + # endregion # region constants From f795e5c4c97ba020e664583179dc287917ab82ea Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:34:56 -0800 Subject: [PATCH 16/76] Add skip-pdf-if-exists with stale cards prompt --- desktop-tool/autofill.py | 109 +++++++++++++++++-- desktop-tool/tests/test_desktop_tool.py | 139 ++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 9 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index adbdfd73c..68debc169 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -23,6 +23,7 @@ import subprocess import sys from contextlib import nullcontext +from glob import glob from pathlib import Path from typing import Optional, Union @@ -33,7 +34,7 @@ from src.driver import AutofillDriver from src.exc import ValidationException from src.formatting import bold -from src.io import DEFAULT_WORKING_DIRECTORY, create_image_directory_if_not_exists +from src.io import DEFAULT_WORKING_DIRECTORY, create_image_directory_if_not_exists, get_image_directory from src.logging import configure_loggers, logger from src.order import CardOrder, aggregate_and_split_orders from src.pdf_maker import PdfExporter, PdfXConversionConfig, get_ghostscript_path, get_ghostscript_version @@ -140,6 +141,73 @@ def ensure_ghostscript_available() -> str: input("Press Enter to re-check for Ghostscript, or Ctrl+C to exit.") +def get_export_directory_for_order(order_name: Optional[str]) -> str: + basename = os.path.basename(str(order_name)) + if not basename: + basename = "cards.xml" + file_name = os.path.splitext(basename)[0] + return os.path.join("export", file_name) + + +def get_existing_pdf_paths(order_name: Optional[str]) -> list[str]: + export_directory = get_export_directory_for_order(order_name=order_name) + return sorted([path for path in glob(os.path.join(export_directory, "**", "*.pdf"), recursive=True)]) + + +def get_newest_mtime_in_directory(directory: str) -> Optional[float]: + if not os.path.isdir(directory): + return None + newest_mtime = None + for root, _, files in os.walk(directory): + for file_name in files: + file_path = os.path.join(root, file_name) + mtime = os.path.getmtime(file_path) + if newest_mtime is None or mtime > newest_mtime: + newest_mtime = mtime + return newest_mtime + + +def existing_pdfs_are_stale(existing_pdf_paths: list[str], cards_directory: str) -> bool: + if not existing_pdf_paths: + return False + newest_cards_mtime = get_newest_mtime_in_directory(cards_directory) + if newest_cards_mtime is None: + return False + newest_pdf_mtime = max(os.path.getmtime(path) for path in existing_pdf_paths) + return newest_cards_mtime > newest_pdf_mtime + + +def maybe_reuse_existing_pdfs( + order_name: Optional[str], + skip_pdf_if_exists: bool, + cards_directory: str, + require_pdfx: bool = False, +) -> Optional[list[str]]: + if not skip_pdf_if_exists: + return None + + existing_pdf_paths = get_existing_pdf_paths(order_name=order_name) + if not existing_pdf_paths: + return None + + if require_pdfx and not any(path.endswith("_pdfx.pdf") for path in existing_pdf_paths): + logger.info( + "Existing PDF files were found, but no PDF/X-1a output was found. Recreating PDF export." + ) + return None + + if existing_pdfs_are_stale(existing_pdf_paths=existing_pdf_paths, cards_directory=cards_directory): + recreate_pdf = click.confirm( + "Existing PDF export found, but images in cards/ are newer. Recreate PDF now?", + default=True, + ) + if recreate_pdf: + return None + + logger.info("Skipping PDF generation because existing exported PDF files were found.") + return existing_pdf_paths + + @click.command(context_settings={"show_default": True}) @click.option("-d", "--directory", default=None, help="The directory to search for order XML files.") @click.option( @@ -188,6 +256,12 @@ def ensure_ghostscript_available() -> str: help="Create a PDF export of the card images instead of creating a project with a printing site.", is_flag=True, ) +@click.option( + "--skip-pdf-if-exists/--no-skip-pdf-if-exists", + default=False, + help="Skip creating a new PDF export when one already exists for the order.", + is_flag=True, +) @click.option( "--allowsleep/--disallow-sleep", default=False, @@ -269,6 +343,7 @@ def main( binary_location: Optional[str], site: str, exportpdf: bool, + skip_pdf_if_exists: bool, allowsleep: bool, image_post_processing: bool, max_dpi: int, @@ -340,12 +415,19 @@ def main( ) orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) for i, order in enumerate(orders, start=1): - exporter = PdfExporter( - order=order, - export_mode="drive_thru_cards", - pdfx_config=PdfXConversionConfig(icc_profile_path=resolved_icc_profile), + pdf_paths = maybe_reuse_existing_pdfs( + order_name=order.name, + skip_pdf_if_exists=skip_pdf_if_exists, + cards_directory=get_image_directory(working_directory), + require_pdfx=True, ) - pdf_paths = exporter.execute(post_processing_config=dtc_post_processing_config) + if pdf_paths is None: + exporter = PdfExporter( + order=order, + export_mode="drive_thru_cards", + pdfx_config=PdfXConversionConfig(icc_profile_path=resolved_icc_profile), + ) + pdf_paths = exporter.execute(post_processing_config=dtc_post_processing_config) # Only use the Ghostscript PDF/X-1a output - no fallback dtc_pdf_path = next((path for path in reversed(pdf_paths) if path.endswith("_pdfx.pdf")), None) if dtc_pdf_path is None: @@ -364,9 +446,18 @@ def main( input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") wait_for_user_to_complete_order() elif exportpdf: - PdfExporter(order=CardOrder.from_xmls_in_folder(working_directory=working_directory)[0]).execute( - post_processing_config=post_processing_config - ) + order = CardOrder.from_xmls_in_folder(working_directory=working_directory)[0] + if ( + maybe_reuse_existing_pdfs( + order_name=order.name, + skip_pdf_if_exists=skip_pdf_if_exists, + cards_directory=get_image_directory(working_directory), + ) + is None + ): + PdfExporter(order=order).execute( + post_processing_config=post_processing_config + ) else: card_orders = aggregate_and_split_orders( orders=CardOrder.from_xmls_in_folder(working_directory=working_directory), diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 1682d0e1f..95c446ea4 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -109,6 +109,145 @@ def fake_get_version(_path: str) -> str: assert called["version"] == 1 +def test_maybe_reuse_existing_pdfs_returns_none_when_skip_disabled(tmp_path) -> None: + order_name = "example.xml" + export_dir = tmp_path / "export" / "example" + export_dir.mkdir(parents=True) + pdf_path = export_dir / "1.pdf" + pdf_path.write_bytes(b"pdf") + + cwd_before = os.getcwd() + os.chdir(tmp_path) + try: + assert ( + autofill_cli.maybe_reuse_existing_pdfs( + order_name=order_name, + skip_pdf_if_exists=False, + cards_directory=str(tmp_path / "cards"), + ) + is None + ) + finally: + os.chdir(cwd_before) + + +def test_maybe_reuse_existing_pdfs_reuses_existing_pdf_when_fresh(tmp_path) -> None: + order_name = "example.xml" + export_dir = tmp_path / "export" / "example" + cards_dir = tmp_path / "cards" + export_dir.mkdir(parents=True) + cards_dir.mkdir() + + pdf_path = export_dir / "1.pdf" + pdf_path.write_bytes(b"pdf") + card_path = cards_dir / "card.png" + card_path.write_bytes(b"card") + + now = time.time() + os.utime(card_path, (now - 20, now - 20)) + os.utime(pdf_path, (now - 10, now - 10)) + + cwd_before = os.getcwd() + os.chdir(tmp_path) + try: + reused = autofill_cli.maybe_reuse_existing_pdfs( + order_name=order_name, + skip_pdf_if_exists=True, + cards_directory=str(cards_dir), + ) + assert reused == [str(pdf_path.relative_to(tmp_path))] + finally: + os.chdir(cwd_before) + + +def test_maybe_reuse_existing_pdfs_recreates_when_cards_newer_and_user_confirms( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + order_name = "example.xml" + export_dir = tmp_path / "export" / "example" + cards_dir = tmp_path / "cards" + export_dir.mkdir(parents=True) + cards_dir.mkdir() + + pdf_path = export_dir / "1.pdf" + pdf_path.write_bytes(b"pdf") + card_path = cards_dir / "card.png" + card_path.write_bytes(b"card") + + now = time.time() + os.utime(pdf_path, (now - 20, now - 20)) + os.utime(card_path, (now - 10, now - 10)) + monkeypatch.setattr("autofill.click.confirm", lambda *_args, **_kwargs: True) + + cwd_before = os.getcwd() + os.chdir(tmp_path) + try: + assert ( + autofill_cli.maybe_reuse_existing_pdfs( + order_name=order_name, + skip_pdf_if_exists=True, + cards_directory=str(cards_dir), + ) + is None + ) + finally: + os.chdir(cwd_before) + + +def test_maybe_reuse_existing_pdfs_keeps_existing_when_cards_newer_and_user_declines( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + order_name = "example.xml" + export_dir = tmp_path / "export" / "example" + cards_dir = tmp_path / "cards" + export_dir.mkdir(parents=True) + cards_dir.mkdir() + + pdf_path = export_dir / "1.pdf" + pdf_path.write_bytes(b"pdf") + card_path = cards_dir / "card.png" + card_path.write_bytes(b"card") + + now = time.time() + os.utime(pdf_path, (now - 20, now - 20)) + os.utime(card_path, (now - 10, now - 10)) + monkeypatch.setattr("autofill.click.confirm", lambda *_args, **_kwargs: False) + + cwd_before = os.getcwd() + os.chdir(tmp_path) + try: + reused = autofill_cli.maybe_reuse_existing_pdfs( + order_name=order_name, + skip_pdf_if_exists=True, + cards_directory=str(cards_dir), + ) + assert reused == [str(pdf_path.relative_to(tmp_path))] + finally: + os.chdir(cwd_before) + + +def test_maybe_reuse_existing_pdfs_requires_pdfx_if_requested(tmp_path) -> None: + order_name = "example.xml" + export_dir = tmp_path / "export" / "example" + export_dir.mkdir(parents=True) + (export_dir / "1.pdf").write_bytes(b"pdf") + + cwd_before = os.getcwd() + os.chdir(tmp_path) + try: + assert ( + autofill_cli.maybe_reuse_existing_pdfs( + order_name=order_name, + skip_pdf_if_exists=True, + cards_directory=str(tmp_path / "cards"), + require_pdfx=True, + ) + is None + ) + finally: + os.chdir(cwd_before) + + # endregion # region PDF/X conversion From 7b043f42c24f5e17c751c0160fe18730d97d662d Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:36:38 -0800 Subject: [PATCH 17/76] Add Chromium profile options for DriveThruCards automation --- desktop-tool/autofill.py | 24 +++++++++++++ desktop-tool/src/driver.py | 9 ++++- desktop-tool/src/webdrivers.py | 28 +++++++++++++-- desktop-tool/tests/test_desktop_tool.py | 48 +++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 68debc169..607a49184 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -227,6 +227,19 @@ def maybe_reuse_existing_pdfs( "to locate it at startup. This is most likely to occur if using the Brave browser." ), ) +@click.option( + "--browser-profile-path", + default=None, + help=( + "Optional Chromium user data directory to use (for existing profiles, cookies, and password managers). " + "Example on macOS: ~/Library/Application Support/Google/Chrome" + ), +) +@click.option( + "--browser-profile-name", + default="Default", + help="Profile directory name inside --browser-profile-path, e.g. Default or 'Profile 1'.", +) @click.option( "--site", prompt=prompt_if_no_arguments("Which site should the tool auto-fill your project into?"), @@ -341,6 +354,8 @@ def main( browser: str, directory: Optional[str], binary_location: Optional[str], + browser_profile_path: Optional[str], + browser_profile_name: str, site: str, exportpdf: bool, skip_pdf_if_exists: bool, @@ -369,6 +384,11 @@ def main( raise Exception( f"Binary location was specified but is not a directory (or it doesn't exist): {bold(binary_location)}" ) + if browser_profile_path and not os.path.isdir(browser_profile_path): + raise Exception( + "Browser profile path was specified but is not a directory (or it doesn't exist): " + f"{bold(browser_profile_path)}" + ) configure_loggers( working_directory=working_directory, @@ -440,6 +460,8 @@ def main( browser=Browsers[browser], target_site=target_site, binary_location=binary_location, + browser_profile_path=browser_profile_path, + browser_profile_name=browser_profile_name if browser_profile_path else None, starting_url=target_site.value.starting_url, ).execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): @@ -469,6 +491,8 @@ def main( browser=Browsers[browser], target_site=target_site, binary_location=binary_location, + browser_profile_path=browser_profile_path, + browser_profile_name=browser_profile_name if browser_profile_path else None, starting_url=web_server.server_url(), ).execute_orders( orders=card_orders, diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 35c6d4083..d25a373d6 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -53,6 +53,8 @@ class AutofillDriver: driver: WebDriver = attr.ib(default=None) # delay initialisation until XML is selected and parsed browser: Browsers = attr.ib(default=Browsers.chrome) binary_location: Optional[str] = attr.ib(default=None) # path to browser executable + browser_profile_path: Optional[str] = attr.ib(default=None) # user data dir for Chromium browsers + browser_profile_name: Optional[str] = attr.ib(default=None) # profile directory name, e.g. "Profile 1" target_site: TargetSites = attr.ib(default=TargetSites.MakePlayingCards) headless: bool = attr.ib(default=False) starting_url: str = attr.ib(default="data:") @@ -70,7 +72,12 @@ class AutofillDriver: def initialise_driver(self) -> None: try: - driver = self.browser.value(headless=self.headless, binary_location=self.binary_location) # type: ignore # TODO + driver = self.browser.value( + headless=self.headless, + binary_location=self.binary_location, + user_data_dir=self.browser_profile_path, + profile_directory=self.browser_profile_name, + ) # type: ignore # TODO driver.set_window_size(1200, 900) driver.implicitly_wait(5) driver.get(self.starting_url) diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 842b702cf..34730ab14 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -104,6 +104,8 @@ def get_chrome_driver( headless: bool = False, binary_location: Optional[str] = None, remote_debugging_port: Optional[int] = None, + user_data_dir: Optional[str] = None, + profile_directory: Optional[str] = None, ) -> uc.Chrome: """ Create a Chrome driver using undetected-chromedriver to bypass bot detection. @@ -120,6 +122,10 @@ def get_chrome_driver( options.add_argument(f"--remote-debugging-port={remote_debugging_port}") if binary_location is not None: options.binary_location = binary_location + if user_data_dir is not None: + options.add_argument(f"--user-data-dir={user_data_dir}") + if profile_directory is not None: + options.add_argument(f"--profile-directory={profile_directory}") # undetected-chromedriver handles stealth automatically # Detect Chrome version since auto-detection can fail @@ -132,7 +138,12 @@ def get_chrome_driver( return driver -def get_brave_driver(headless: bool = False, binary_location: Optional[str] = None) -> uc.Chrome: +def get_brave_driver( + headless: bool = False, + binary_location: Optional[str] = None, + user_data_dir: Optional[str] = None, + profile_directory: Optional[str] = None, +) -> uc.Chrome: """ Create a Brave driver using undetected-chromedriver. """ @@ -142,6 +153,10 @@ def get_brave_driver(headless: bool = False, binary_location: Optional[str] = No options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") + if user_data_dir is not None: + options.add_argument(f"--user-data-dir={user_data_dir}") + if profile_directory is not None: + options.add_argument(f"--profile-directory={profile_directory}") # the binary location for brave must be manually specified (otherwise chrome will open instead) if binary_location is not None: @@ -162,7 +177,12 @@ def get_brave_driver(headless: bool = False, binary_location: Optional[str] = No return driver -def get_edge_driver(headless: bool = False, binary_location: Optional[str] = None) -> ChromiumDriver: +def get_edge_driver( + headless: bool = False, + binary_location: Optional[str] = None, + user_data_dir: Optional[str] = None, + profile_directory: Optional[str] = None, +) -> ChromiumDriver: """ Create an Edge driver with stealth options. Note: Edge doesn't have an undetected variant, so we use standard stealth measures. @@ -179,6 +199,10 @@ def get_edge_driver(headless: bool = False, binary_location: Optional[str] = Non options.add_experimental_option("detach", True) if binary_location is not None: options.binary_location = binary_location + if user_data_dir is not None: + options.add_argument(f"--user-data-dir={user_data_dir}") + if profile_directory is not None: + options.add_argument(f"--profile-directory={profile_directory}") driver: ChromiumDriver = Edge(options=options) # type: ignore # Apply CDP stealth for Edge driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 95c446ea4..26128ee61 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -16,6 +16,7 @@ import src import src.constants as constants +import src.webdrivers as webdrivers from src.constants import OrderFulfilmentMethod, SourceType from src.driver import AutofillDriver from src.exc import ValidationException @@ -248,6 +249,53 @@ def test_maybe_reuse_existing_pdfs_requires_pdfx_if_requested(tmp_path) -> None: os.chdir(cwd_before) +def test_get_chrome_driver_applies_user_profile_options(monkeypatch: pytest.MonkeyPatch) -> None: + captured = {"options": None, "version_main": None} + + class DummyDriver: + def execute_cdp_cmd(self, _command, _payload): + return None + + def fake_chrome(*, options, version_main): + captured["options"] = options + captured["version_main"] = version_main + return DummyDriver() + + monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) + monkeypatch.setattr(webdrivers.uc, "Chrome", fake_chrome) + + webdrivers.get_chrome_driver( + user_data_dir="/tmp/chrome-data", + profile_directory="Profile 7", + ) + + assert "--user-data-dir=/tmp/chrome-data" in captured["options"].arguments + assert "--profile-directory=Profile 7" in captured["options"].arguments + assert captured["version_main"] == 120 + + +def test_get_brave_driver_applies_user_profile_options(monkeypatch: pytest.MonkeyPatch) -> None: + captured = {"options": None} + + class DummyDriver: + pass + + def fake_chrome(*, options): + captured["options"] = options + return DummyDriver() + + monkeypatch.setattr(webdrivers.uc, "Chrome", fake_chrome) + + webdrivers.get_brave_driver( + binary_location="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", + user_data_dir="/tmp/brave-data", + profile_directory="Default", + ) + + assert "--user-data-dir=/tmp/brave-data" in captured["options"].arguments + assert "--profile-directory=Default" in captured["options"].arguments + + # endregion # region PDF/X conversion From 27aa8cc0c19ecb9cca5d09e70c2d51a70a5f1321 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:37:47 -0800 Subject: [PATCH 18/76] Add CLI mode to download images without uploading --- desktop-tool/autofill.py | 33 ++++++++++++++++++++++++- desktop-tool/tests/test_desktop_tool.py | 27 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 607a49184..359771936 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -22,15 +22,17 @@ import shutil import subprocess import sys +from concurrent.futures import ThreadPoolExecutor from contextlib import nullcontext from glob import glob from pathlib import Path from typing import Optional, Union import click +import enlighten from wakepy import keepawake -from src.constants import Browsers, ImageResizeMethods, TargetSites +from src.constants import THREADS, Browsers, ImageResizeMethods, TargetSites from src.driver import AutofillDriver from src.exc import ValidationException from src.formatting import bold @@ -208,6 +210,24 @@ def maybe_reuse_existing_pdfs( return existing_pdf_paths +def download_images_for_orders( + orders: list[CardOrder], + post_processing_config: Optional[ImagePostProcessingConfig], +) -> None: + total_images = sum( + len(order.fronts.cards_by_id) + len(order.backs.cards_by_id) + for order in orders + ) + manager = enlighten.get_manager() + download_bar = manager.counter(total=total_images, desc="Images Downloaded", position=1, autorefresh=True) + with ThreadPoolExecutor(max_workers=THREADS) as pool: + for order in orders: + logger.info(f"Downloading images for {bold(order.name or 'Unnamed Project')}...") + order.fronts.download_images(pool, download_bar, post_processing_config) + order.backs.download_images(pool, download_bar, post_processing_config) + logger.info("Finished downloading card images.") + + @click.command(context_settings={"show_default": True}) @click.option("-d", "--directory", default=None, help="The directory to search for order XML files.") @click.option( @@ -269,6 +289,12 @@ def maybe_reuse_existing_pdfs( help="Create a PDF export of the card images instead of creating a project with a printing site.", is_flag=True, ) +@click.option( + "--download-images-only", + default=False, + help="Only download card images to the cards directory and exit.", + is_flag=True, +) @click.option( "--skip-pdf-if-exists/--no-skip-pdf-if-exists", default=False, @@ -358,6 +384,7 @@ def main( browser_profile_name: str, site: str, exportpdf: bool, + download_images_only: bool, skip_pdf_if_exists: bool, allowsleep: bool, image_post_processing: bool, @@ -408,6 +435,10 @@ def main( if image_post_processing else None ) + if download_images_only: + orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) + download_images_for_orders(orders=orders, post_processing_config=post_processing_config) + return if target_site == TargetSites.DriveThruCards: ensure_ghostscript_available() using_default_icc = dtc_icc_profile is None diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 26128ee61..bf09b0dea 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -4,10 +4,12 @@ from concurrent.futures import ThreadPoolExecutor from itertools import groupby from queue import Queue +from types import SimpleNamespace from typing import Callable, Generator from xml.etree import ElementTree import pytest +from click.testing import CliRunner from enlighten import Counter from PIL import Image from selenium.webdriver.common.by import By @@ -296,6 +298,31 @@ def fake_chrome(*, options): assert "--profile-directory=Default" in captured["options"].arguments +def test_cli_help_includes_download_images_only_option() -> None: + result = CliRunner().invoke(autofill_cli.main, ["--help"]) + assert result.exit_code == 0 + assert "--download-images-only" in result.output + + +def test_download_images_for_orders_downloads_fronts_and_backs() -> None: + calls = {"fronts": 0, "backs": 0} + + class Face: + def __init__(self, key: str) -> None: + self._key = key + self.cards_by_id = {"a": object()} + + def download_images(self, _pool, _download_bar, _post_processing_config): + calls[self._key] += 1 + + order = SimpleNamespace(name="order1", fronts=Face("fronts"), backs=Face("backs")) + + autofill_cli.download_images_for_orders(orders=[order], post_processing_config=DEFAULT_POST_PROCESSING) + + assert calls["fronts"] == 1 + assert calls["backs"] == 1 + + # endregion # region PDF/X conversion From b31c505ad417f7f32f0aff64a96ac3e5f06e5a0b Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:38:30 -0800 Subject: [PATCH 19/76] Log PDF/X conversion progress during DTC exports --- desktop-tool/src/pdf_maker.py | 5 +++- desktop-tool/tests/test_desktop_tool.py | 34 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index d6faa7b8c..854448914 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -282,7 +282,10 @@ def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) - self.export() if self.pdfx_config: - for file_path in list(self.saved_files): + source_pdf_paths = list(self.saved_files) + total_files = len(source_pdf_paths) + for index, file_path in enumerate(source_pdf_paths, start=1): + logger.info(f"Converting PDF to PDF/X-1a ({index}/{total_files}): {file_path}") pdfx_path = f"{os.path.splitext(file_path)[0]}_pdfx.pdf" if convert_pdf_to_pdfx(file_path, pdfx_path, self.pdfx_config): self.saved_files.append(pdfx_path) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index bf09b0dea..6118d0ad6 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -382,6 +382,40 @@ def do_nothing(_): remove_directories(["export/test_order", "export"]) +def test_pdf_exporter_logs_pdfx_conversion_progress(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: + logged_messages = [] + + def do_nothing(_): + return None + + def fake_info(message: str): + logged_messages.append(message) + + def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> bool: + with open(output_path, "wb") as f: + f.write(b"pdfx") + return True + + monkeypatch.setattr("src.pdf_maker.PdfExporter.ask_questions", do_nothing) + monkeypatch.setattr("src.pdf_maker.convert_pdf_to_pdfx", fake_convert_pdf_to_pdfx) + monkeypatch.setattr("src.pdf_maker.logger.info", fake_info) + + card_order_valid.name = "test_order.xml" + pdf_exporter = PdfExporter( + order=card_order_valid, + number_of_cards_per_file=1, + pdfx_config=autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + ) + generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) + + progress_logs = [message for message in logged_messages if message.startswith("Converting PDF to PDF/X-1a")] + assert len(progress_logs) == 3 + assert "Converting PDF to PDF/X-1a (1/3): export/test_order/1.pdf" in progress_logs + + remove_files([path for path in generated_files if path.endswith(".pdf")]) + remove_directories(["export/test_order", "export"]) + + def test_pdf_exporter_add_image_uses_image_bytes(monkeypatch: pytest.MonkeyPatch, card_order_valid, tmp_path) -> None: monkeypatch.setattr("src.pdf_maker.PdfExporter.ask_questions", lambda _self: None) pdf_exporter = PdfExporter(order=card_order_valid, number_of_cards_per_file=1) From 0df32ada456ff54516456ccf949d2d8cdaceaa47 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:39:28 -0800 Subject: [PATCH 20/76] Bundle desktop-tool assets in Nuitka builds --- desktop-tool/autofill.py | 1 + desktop-tool/tests/test_desktop_tool.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 359771936..d1db4189f 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -1,6 +1,7 @@ # nuitka-project: --mode=onefile # nuitka-project: --include-data-files=client_secrets.json=client_secrets.json # nuitka-project: --include-data-files=post-launch.html=post-launch.html +# nuitka-project: --include-data-dir=assets=assets # nuitka-project: --include-data-files=assets/icc/USWebCoatedSWOP.icc=assets/icc/USWebCoatedSWOP.icc # nuitka-project: --include-data-files=assets/icc/Adobe-Color-Profile-EULA.pdf=assets/icc/Adobe-Color-Profile-EULA.pdf # nuitka-project: --noinclude-pytest-mode=nofollow diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 6118d0ad6..3c098a3b7 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -323,6 +323,12 @@ def download_images(self, _pool, _download_bar, _post_processing_config): assert calls["backs"] == 1 +def test_nuitka_directives_include_assets_directory() -> None: + with open(autofill_cli.__file__, "r", encoding="utf-8") as f: + source = f.read() + assert "--include-data-dir=assets=assets" in source + + # endregion # region PDF/X conversion From 3136fb6a6df6ba9f9d98a585f72be6264b8da682 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:54:54 -0800 Subject: [PATCH 21/76] Add PR-ready Desktop Tool wiki draft in repo --- desktop-tool/docs/Desktop-Tool.wiki.md | 52 +++++++++++++++++++++++++ desktop-tool/tests/test_desktop_tool.py | 19 +++++++++ 2 files changed, 71 insertions(+) create mode 100644 desktop-tool/docs/Desktop-Tool.wiki.md diff --git a/desktop-tool/docs/Desktop-Tool.wiki.md b/desktop-tool/docs/Desktop-Tool.wiki.md new file mode 100644 index 000000000..bcdce6697 --- /dev/null +++ b/desktop-tool/docs/Desktop-Tool.wiki.md @@ -0,0 +1,52 @@ +# Desktop Tool + +The desktop tool automates order creation workflows and supports DriveThruCards PDF upload. + +## DriveThruCards Workflow + +### Ghostscript Requirement + +DriveThruCards export requires PDF/X-1a conversion via Ghostscript. + +Install Ghostscript: + +- macOS: `brew install ghostscript` +- Ubuntu/Debian: `sudo apt install -y ghostscript` +- Fedora: `sudo dnf install -y ghostscript` +- Windows: install from + - Optional: `winget install --id ArtifexSoftware.Ghostscript` + +The CLI checks Ghostscript availability before DriveThruCards processing and guides install/retry when missing. + +### Standard DriveThruCards Run + +```bash +.venv/bin/python autofill.py --site drivethrucards --directory /Users/bradley/Documents/dtc-test +``` + +## Useful CLI Flags + +### PDF/Export Flags + +- `--skip-pdf-if-exists` + - Reuse existing exported PDFs when present for the order. + - If files in `cards/` are newer than existing PDFs, the CLI prompts whether to recreate. + - For DriveThruCards, existing files are reused only when a `_pdfx.pdf` output exists. + +- `--download-images-only` + - Download all card images to `cards/` and exit. + - Skips PDF creation and browser automation. + +### Browser Profile Flags + +- `--browser-profile-path` + - Use an existing Chromium user data directory (cookies/session/password managers). +- `--browser-profile-name` + - Select the profile directory inside the user data folder (default: `Default`). + +These are useful for DriveThruCards login reliability when bot detection blocks fresh sessions. + +## Notes + +- During PDF/X conversion, CLI status now reports progress per file (`Converting PDF to PDF/X-1a (n/total)`). +- Build/bundled desktop artifacts include the full `assets/` directory. diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 3c098a3b7..a61de1db9 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -329,6 +329,25 @@ def test_nuitka_directives_include_assets_directory() -> None: assert "--include-data-dir=assets=assets" in source +def test_readme_points_users_to_wiki_for_usage_docs() -> None: + readme_path = os.path.join(os.path.dirname(autofill_cli.__file__), "readme.md") + with open(readme_path, "r", encoding="utf-8") as f: + readme = f.read() + assert "https://github.com/chilli-axe/mpc-autofill/wiki/Desktop-Tool" in readme + assert "--skip-pdf-if-exists" not in readme + assert "--download-images-only" not in readme + + +def test_wiki_draft_includes_cli_usage_updates() -> None: + wiki_draft_path = os.path.join(os.path.dirname(autofill_cli.__file__), "docs", "Desktop-Tool.wiki.md") + with open(wiki_draft_path, "r", encoding="utf-8") as f: + wiki = f.read() + assert "--skip-pdf-if-exists" in wiki + assert "--download-images-only" in wiki + assert "--browser-profile-path" in wiki + assert "Ghostscript Requirement" in wiki + + # endregion # region PDF/X conversion From a4d18da394bde605200f8d44bd4fdc626de8df2c Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:58:08 -0800 Subject: [PATCH 22/76] Add unit tests for DriveThruCards-specific driver flow --- .../tests/test_drivethrucards_driver.py | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 desktop-tool/tests/test_drivethrucards_driver.py diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py new file mode 100644 index 000000000..964f5b3fa --- /dev/null +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -0,0 +1,145 @@ +import time +from types import SimpleNamespace + +import pytest +from selenium.webdriver.common.by import By + +from src.constants import States, TargetSites +from src.driver import AutofillDriver + + +@pytest.fixture +def dtc_driver(monkeypatch: pytest.MonkeyPatch) -> AutofillDriver: + monkeypatch.setattr(AutofillDriver, "__attrs_post_init__", lambda self: None) + driver = AutofillDriver(target_site=TargetSites.DriveThruCards) + driver.set_state = lambda *_args, **_kwargs: None # avoid status bar dependency in unit tests + return driver + + +def test_execute_drive_thru_cards_order_runs_expected_sequence(dtc_driver: AutofillDriver) -> None: + calls = [] + dtc_driver.driver = SimpleNamespace(get=lambda url: calls.append(("get", url))) + + dtc_driver.wait_for_cloudflare_challenge = lambda: calls.append(("wait_for_cloudflare_challenge",)) + dtc_driver.authenticate_dtc = lambda: calls.append(("authenticate_dtc",)) + dtc_driver.navigate_to_dtc_product_setup = lambda: calls.append(("navigate_to_dtc_product_setup",)) + dtc_driver.fill_dtc_product_form = lambda order: calls.append(("fill_dtc_product_form", order.name)) + dtc_driver.submit_dtc_description_page = lambda: calls.append(("submit_dtc_description_page",)) + dtc_driver.open_dtc_upload_page = lambda: calls.append(("open_dtc_upload_page",)) + dtc_driver.select_card_type_and_upload_pdf = lambda pdf_path: calls.append(("upload_pdf", pdf_path)) + + order = SimpleNamespace(name="My Order") + dtc_driver.execute_drive_thru_cards_order(order=order, pdf_path="/tmp/order.pdf") + + assert calls == [ + ("get", TargetSites.DriveThruCards.value.starting_url), + ("wait_for_cloudflare_challenge",), + ("authenticate_dtc",), + ("navigate_to_dtc_product_setup",), + ("fill_dtc_product_form", "My Order"), + ("submit_dtc_description_page",), + ("open_dtc_upload_page",), + ("upload_pdf", "/tmp/order.pdf"), + ] + + +def test_authenticate_dtc_returns_immediately_when_already_logged_in(dtc_driver: AutofillDriver) -> None: + dtc_driver.is_dtc_user_authenticated = lambda: True + dtc_driver._click_dtc_login_button = lambda: (_ for _ in ()).throw(AssertionError("should not click")) + dtc_driver.click_element_polling = lambda *_args, **_kwargs: (_ for _ in ()).throw( + AssertionError("should not poll") + ) + + dtc_driver.authenticate_dtc() + + +def test_authenticate_dtc_uses_xpath_then_css_fallback(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: + auth_checks = iter([False, False, True]) + dtc_driver.is_dtc_user_authenticated = lambda: next(auth_checks) + dtc_driver._click_dtc_login_button = lambda: True + + polling_calls = [] + + def fake_click_element_polling(by: By, selector: str, timeout: int = 30) -> bool: + polling_calls.append((by, selector, timeout)) + if by == By.XPATH: + return False + return True + + dtc_driver.click_element_polling = fake_click_element_polling + monkeypatch.setattr(time, "sleep", lambda _seconds: None) + + dtc_driver.authenticate_dtc() + + assert polling_calls[0][0] == By.XPATH + assert "Go to Log in" in polling_calls[0][1] + assert polling_calls[1][0] == By.CSS_SELECTOR + assert polling_calls[1][1] == TargetSites.DriveThruCards.value.selectors.go_to_login_selector + + +def test_wait_for_cloudflare_challenge_returns_when_site_loaded(dtc_driver: AutofillDriver) -> None: + dtc_driver._is_site_loaded = lambda: True + dtc_driver._is_cloudflare_challenge_active = lambda: (_ for _ in ()).throw( + AssertionError("should not check challenge when site already loaded") + ) + + dtc_driver.wait_for_cloudflare_challenge(timeout_seconds=1) + + +def test_open_dtc_upload_page_extracts_window_open_url(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: + script_calls = [] + + class UploadButton: + def get_attribute(self, name: str) -> str: + assert name == "onclick" + return "window.open('https://tools.drivethrucards.com/pub_upload_podcard_files.php?products_id=123');" + + class FakeWait: + call_count = 0 + + def __init__(self, *_args, **_kwargs): + pass + + def until(self, _condition): + FakeWait.call_count += 1 + if FakeWait.call_count == 1: + return UploadButton() + return object() + + dtc_driver.driver = SimpleNamespace( + execute_script=lambda script, url: script_calls.append((script, url)), + current_url="https://tools.drivethrucards.com/pub_upload_podcard_files.php?products_id=123", + ) + + monkeypatch.setattr("src.driver.WebDriverWait", FakeWait) + + dtc_driver.open_dtc_upload_page() + + assert script_calls == [ + ("window.location.href = arguments[0];", "https://tools.drivethrucards.com/pub_upload_podcard_files.php?products_id=123") + ] + + +def test_is_site_loaded_uses_login_or_logged_in_selectors(dtc_driver: AutofillDriver) -> None: + selectors = TargetSites.DriveThruCards.value.selectors + + class FakeWebDriver: + def __init__(self) -> None: + self.title = "DriveThruCards" + self.wait_values = [] + + def implicitly_wait(self, value: int) -> None: + self.wait_values.append(value) + + def find_elements(self, by: By, selector: str): + if by == By.CSS_SELECTOR and selector == selectors.login_button_selector: + return [object()] + if by == By.CSS_SELECTOR and selector == selectors.logged_in_indicator_selector: + return [] + return [] + + fake_driver = FakeWebDriver() + dtc_driver.driver = fake_driver + + assert dtc_driver._is_site_loaded() is True + assert fake_driver.wait_values == [0, 5] From 55fb476e6d36a74f614a8c13efd696dedef8cf09 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 16:59:35 -0800 Subject: [PATCH 23/76] Make DTC custom stealth opt-in and test default behavior --- desktop-tool/autofill.py | 11 +++++++++ desktop-tool/src/driver.py | 2 ++ desktop-tool/src/webdrivers.py | 9 +++++-- desktop-tool/tests/test_desktop_tool.py | 32 +++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index d1db4189f..10c7e44ff 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -261,6 +261,15 @@ def download_images_for_orders( default="Default", help="Profile directory name inside --browser-profile-path, e.g. Default or 'Profile 1'.", ) +@click.option( + "--dtc-custom-stealth/--no-dtc-custom-stealth", + default=False, + help=( + "Apply additional custom stealth JavaScript in Chromium for DriveThruCards. " + "Disabled by default because these patches can trigger bot detection on some runs." + ), + is_flag=True, +) @click.option( "--site", prompt=prompt_if_no_arguments("Which site should the tool auto-fill your project into?"), @@ -383,6 +392,7 @@ def main( binary_location: Optional[str], browser_profile_path: Optional[str], browser_profile_name: str, + dtc_custom_stealth: bool, site: str, exportpdf: bool, download_images_only: bool, @@ -494,6 +504,7 @@ def main( binary_location=binary_location, browser_profile_path=browser_profile_path, browser_profile_name=browser_profile_name if browser_profile_path else None, + apply_custom_stealth=dtc_custom_stealth, starting_url=target_site.value.starting_url, ).execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index d25a373d6..9298f4fe0 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -55,6 +55,7 @@ class AutofillDriver: binary_location: Optional[str] = attr.ib(default=None) # path to browser executable browser_profile_path: Optional[str] = attr.ib(default=None) # user data dir for Chromium browsers browser_profile_name: Optional[str] = attr.ib(default=None) # profile directory name, e.g. "Profile 1" + apply_custom_stealth: bool = attr.ib(default=False) target_site: TargetSites = attr.ib(default=TargetSites.MakePlayingCards) headless: bool = attr.ib(default=False) starting_url: str = attr.ib(default="data:") @@ -77,6 +78,7 @@ def initialise_driver(self) -> None: binary_location=self.binary_location, user_data_dir=self.browser_profile_path, profile_directory=self.browser_profile_name, + apply_custom_stealth=self.apply_custom_stealth, ) # type: ignore # TODO driver.set_window_size(1200, 900) driver.implicitly_wait(5) diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 34730ab14..1ac25f5cd 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -106,6 +106,7 @@ def get_chrome_driver( remote_debugging_port: Optional[int] = None, user_data_dir: Optional[str] = None, profile_directory: Optional[str] = None, + apply_custom_stealth: bool = False, ) -> uc.Chrome: """ Create a Chrome driver using undetected-chromedriver to bypass bot detection. @@ -132,8 +133,9 @@ def get_chrome_driver( version_main = _detect_chrome_version() driver = uc.Chrome(options=options, version_main=version_main) - # Apply additional stealth scripts - _apply_stealth_scripts(driver) + # Keep custom stealth JS opt-in: these patches can become a detection vector on some sites. + if apply_custom_stealth: + _apply_stealth_scripts(driver) return driver @@ -143,6 +145,7 @@ def get_brave_driver( binary_location: Optional[str] = None, user_data_dir: Optional[str] = None, profile_directory: Optional[str] = None, + apply_custom_stealth: bool = False, ) -> uc.Chrome: """ Create a Brave driver using undetected-chromedriver. @@ -174,6 +177,8 @@ def get_brave_driver( options.binary_location = default_binary_locations[sys.platform] driver = uc.Chrome(options=options) + if apply_custom_stealth: + _apply_stealth_scripts(driver) return driver diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index a61de1db9..a47b8f933 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -276,6 +276,38 @@ def fake_chrome(*, options, version_main): assert captured["version_main"] == 120 +def test_get_chrome_driver_does_not_apply_custom_stealth_by_default(monkeypatch: pytest.MonkeyPatch) -> None: + stealth_called = {"value": False} + + class DummyDriver: + def execute_cdp_cmd(self, _command, _payload): + return None + + monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) + monkeypatch.setattr(webdrivers.uc, "Chrome", lambda **_kwargs: DummyDriver()) + monkeypatch.setattr(webdrivers, "_apply_stealth_scripts", lambda _driver: stealth_called.update(value=True)) + + webdrivers.get_chrome_driver() + + assert stealth_called["value"] is False + + +def test_get_chrome_driver_applies_custom_stealth_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None: + stealth_called = {"value": False} + + class DummyDriver: + def execute_cdp_cmd(self, _command, _payload): + return None + + monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) + monkeypatch.setattr(webdrivers.uc, "Chrome", lambda **_kwargs: DummyDriver()) + monkeypatch.setattr(webdrivers, "_apply_stealth_scripts", lambda _driver: stealth_called.update(value=True)) + + webdrivers.get_chrome_driver(apply_custom_stealth=True) + + assert stealth_called["value"] is True + + def test_get_brave_driver_applies_user_profile_options(monkeypatch: pytest.MonkeyPatch) -> None: captured = {"options": None} From 09c8a44bcabb814d03a99b45154d7975f38e4102 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 17:02:35 -0800 Subject: [PATCH 24/76] Fail fast on DTC login timeout and add platform installer tests --- desktop-tool/src/driver.py | 13 ++-- desktop-tool/tests/test_desktop_tool.py | 61 +++++++++++++++++++ .../tests/test_drivethrucards_driver.py | 28 ++++++++- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 9298f4fe0..544bdb3cb 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -564,7 +564,7 @@ def _click_dtc_login_button(self) -> bool: selectors = self.target_site.value.selectors return self.click_element_polling(By.CSS_SELECTOR, selectors.login_button_selector, timeout=15) - def authenticate_dtc(self) -> None: + def authenticate_dtc(self) -> bool: """ Handle DriveThruCards login flow. """ @@ -572,7 +572,7 @@ def authenticate_dtc(self) -> None: if self.is_dtc_user_authenticated(): logger.info("Already logged in to DriveThruCards.") - return + return True self.set_state(States.defining_order, "Awaiting DriveThruCards login") @@ -615,12 +615,13 @@ def authenticate_dtc(self) -> None: time.sleep(1) if self.is_dtc_user_authenticated(): logger.info("Successfully logged in to DriveThruCards!") - return + return True logger.warning( f"Login timeout after {timeout_seconds}s. " "Please ensure you're logged in before continuing." ) + return False def navigate_to_dtc_product_setup(self) -> None: """ @@ -1012,7 +1013,11 @@ def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> Non self.wait_for_cloudflare_challenge() # Handle login - self.authenticate_dtc() + if not self.authenticate_dtc(): + raise Exception( + "DriveThruCards login was not completed before timeout. " + "Please log in and re-run the command." + ) # Navigate to product setup page self.navigate_to_dtc_product_setup() diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index a47b8f933..caef55489 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -107,11 +107,72 @@ def fake_get_version(_path: str) -> str: monkeypatch.setattr(autofill_cli, "get_ghostscript_path", fake_get_path) monkeypatch.setattr(autofill_cli, "get_ghostscript_version", fake_get_version) + monkeypatch.setattr(autofill_cli.click, "confirm", lambda *_args, **_kwargs: False) assert autofill_cli.ensure_ghostscript_available() == "/usr/local/bin/gs" assert called["version"] == 1 +def test_ensure_ghostscript_available_installs_with_winget_on_windows( + monkeypatch: pytest.MonkeyPatch, +) -> None: + paths = [None, "C:\\Program Files\\gs\\gswin64c.exe"] + install_calls = [] + + monkeypatch.setattr(autofill_cli.sys, "platform", "win32", raising=False) + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: paths.pop(0)) + monkeypatch.setattr(autofill_cli, "get_ghostscript_version", lambda _path: "10.0.0") + monkeypatch.setattr(autofill_cli.click, "confirm", lambda *_args, **_kwargs: True) + monkeypatch.setattr("builtins.input", lambda *_args, **_kwargs: "\n") + monkeypatch.setattr( + autofill_cli.shutil, + "which", + lambda name: "winget" if name == "winget" else None, + ) + + def fake_run(cmd, check=False): + install_calls.append(cmd) + return SimpleNamespace(returncode=0) + + monkeypatch.setattr(autofill_cli.subprocess, "run", fake_run) + + resolved = autofill_cli.ensure_ghostscript_available() + + assert resolved == "C:\\Program Files\\gs\\gswin64c.exe" + assert install_calls == [ + ["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"] + ] + + +def test_ensure_ghostscript_available_installs_with_apt_on_linux( + monkeypatch: pytest.MonkeyPatch, +) -> None: + paths = [None, "/usr/bin/gs"] + install_calls = [] + + monkeypatch.setattr(autofill_cli.sys, "platform", "linux", raising=False) + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: paths.pop(0)) + monkeypatch.setattr(autofill_cli, "get_ghostscript_version", lambda _path: "10.0.0") + monkeypatch.setattr(autofill_cli.click, "confirm", lambda *_args, **_kwargs: True) + monkeypatch.setattr("builtins.input", lambda *_args, **_kwargs: "\n") + monkeypatch.setattr( + autofill_cli.shutil, + "which", + lambda name: "/usr/bin/" + name if name in {"sudo", "apt"} else None, + ) + + def fake_run(cmd, check=False): + install_calls.append(cmd) + return SimpleNamespace(returncode=0) + + monkeypatch.setattr(autofill_cli.subprocess, "run", fake_run) + + resolved = autofill_cli.ensure_ghostscript_available() + + assert resolved == "/usr/bin/gs" + assert install_calls == [["sudo", "apt", "install", "-y", "ghostscript"]] + + def test_maybe_reuse_existing_pdfs_returns_none_when_skip_disabled(tmp_path) -> None: order_name = "example.xml" export_dir = tmp_path / "export" / "example" diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index 964f5b3fa..f01266ae6 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -21,7 +21,7 @@ def test_execute_drive_thru_cards_order_runs_expected_sequence(dtc_driver: Autof dtc_driver.driver = SimpleNamespace(get=lambda url: calls.append(("get", url))) dtc_driver.wait_for_cloudflare_challenge = lambda: calls.append(("wait_for_cloudflare_challenge",)) - dtc_driver.authenticate_dtc = lambda: calls.append(("authenticate_dtc",)) + dtc_driver.authenticate_dtc = lambda: calls.append(("authenticate_dtc",)) or True dtc_driver.navigate_to_dtc_product_setup = lambda: calls.append(("navigate_to_dtc_product_setup",)) dtc_driver.fill_dtc_product_form = lambda order: calls.append(("fill_dtc_product_form", order.name)) dtc_driver.submit_dtc_description_page = lambda: calls.append(("submit_dtc_description_page",)) @@ -50,7 +50,7 @@ def test_authenticate_dtc_returns_immediately_when_already_logged_in(dtc_driver: AssertionError("should not poll") ) - dtc_driver.authenticate_dtc() + assert dtc_driver.authenticate_dtc() is True def test_authenticate_dtc_uses_xpath_then_css_fallback(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: @@ -69,7 +69,7 @@ def fake_click_element_polling(by: By, selector: str, timeout: int = 30) -> bool dtc_driver.click_element_polling = fake_click_element_polling monkeypatch.setattr(time, "sleep", lambda _seconds: None) - dtc_driver.authenticate_dtc() + assert dtc_driver.authenticate_dtc() is True assert polling_calls[0][0] == By.XPATH assert "Go to Log in" in polling_calls[0][1] @@ -77,6 +77,28 @@ def fake_click_element_polling(by: By, selector: str, timeout: int = 30) -> bool assert polling_calls[1][1] == TargetSites.DriveThruCards.value.selectors.go_to_login_selector +def test_authenticate_dtc_returns_false_on_timeout(monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver) -> None: + dtc_driver.is_dtc_user_authenticated = lambda: False + dtc_driver._click_dtc_login_button = lambda: False + dtc_driver.click_element_polling = lambda *_args, **_kwargs: False + + time_values = iter([0.0, 301.0]) + monkeypatch.setattr(time, "time", lambda: next(time_values)) + monkeypatch.setattr(time, "sleep", lambda _seconds: None) + + assert dtc_driver.authenticate_dtc() is False + + +def test_execute_drive_thru_cards_order_raises_when_login_not_completed(dtc_driver: AutofillDriver) -> None: + dtc_driver.driver = SimpleNamespace(get=lambda _url: None) + dtc_driver.wait_for_cloudflare_challenge = lambda: None + dtc_driver.authenticate_dtc = lambda: False + dtc_driver.navigate_to_dtc_product_setup = lambda: (_ for _ in ()).throw(AssertionError("should not continue")) + + with pytest.raises(Exception, match="login was not completed"): + dtc_driver.execute_drive_thru_cards_order(order=SimpleNamespace(name="x"), pdf_path="/tmp/x.pdf") + + def test_wait_for_cloudflare_challenge_returns_when_site_loaded(dtc_driver: AutofillDriver) -> None: dtc_driver._is_site_loaded = lambda: True dtc_driver._is_cloudflare_challenge_active = lambda: (_ for _ in ()).throw( From f0f91aacbd226ec73a18bc58cd402008cb5a7146 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 17:04:03 -0800 Subject: [PATCH 25/76] Refactor DTC flow into guarded steps with clearer failures --- desktop-tool/src/driver.py | 70 ++++--------------- .../tests/test_drivethrucards_driver.py | 10 +++ 2 files changed, 23 insertions(+), 57 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 544bdb3cb..1e4abbfa8 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -1005,34 +1005,27 @@ def continue_button_is_active(d: Any) -> Any: def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> None: t = time.time() - selectors = self.target_site.value.selectors self.set_state(States.defining_order, "Opening DriveThruCards") self.driver.get(self.target_site.value.starting_url) - # Handle Cloudflare challenge if present - self.wait_for_cloudflare_challenge() + def run_step(step_name: str, func: Any, *args: Any, **kwargs: Any) -> Any: + try: + return func(*args, **kwargs) + except Exception as exc: + raise Exception(f"DriveThruCards step '{step_name}' failed: {exc}") from exc - # Handle login - if not self.authenticate_dtc(): + run_step("wait_for_cloudflare_challenge", self.wait_for_cloudflare_challenge) + login_completed = run_step("authenticate_dtc", self.authenticate_dtc) + if not login_completed: raise Exception( "DriveThruCards login was not completed before timeout. " "Please log in and re-run the command." ) - - # Navigate to product setup page - self.navigate_to_dtc_product_setup() - - # Fill out the product form (first page - title, price, cover image, checkboxes) - self.fill_dtc_product_form(order=order) - - # Submit the description preview page - self.submit_dtc_description_page() - - # Open the upload page (new tab) - self.open_dtc_upload_page() - - # Select card type and upload the PDF - self.select_card_type_and_upload_pdf(pdf_path=pdf_path) + run_step("navigate_to_dtc_product_setup", self.navigate_to_dtc_product_setup) + run_step("fill_dtc_product_form", self.fill_dtc_product_form, order) + run_step("submit_dtc_description_page", self.submit_dtc_description_page) + run_step("open_dtc_upload_page", self.open_dtc_upload_page) + run_step("select_card_type_and_upload_pdf", self.select_card_type_and_upload_pdf, pdf_path) # DriveThruCards automation complete - user should finish checkout manually log_hours_minutes_seconds_elapsed(t) @@ -1040,43 +1033,6 @@ def execute_drive_thru_cards_order(self, order: CardOrder, pdf_path: str) -> Non "DriveThruCards order setup complete!\n" "You are now at the checkout page. Please review your order and complete the purchase manually." ) - return - - if selectors.quantity_selector: - try: - self.wait_for_selector(selectors.quantity_selector) - quantity_input = self.driver.find_element(By.CSS_SELECTOR, selectors.quantity_selector) - quantity_input.clear() - quantity_input.send_keys(str(order.details.quantity)) - except sl_exc.WebDriverException as exc: - logger.warning(f"Failed to set DriveThruCards quantity automatically: {exc}") - - self.set_state(States.inserting_fronts, "Uploading PDF") - self.wait_for_selector(selectors.pdf_upload_input_selector) - upload_inputs = self.driver.find_elements(By.CSS_SELECTOR, selectors.pdf_upload_input_selector) - if len(upload_inputs) <= selectors.pdf_upload_input_index: - raise Exception( - f"DriveThruCards PDF upload input not found at index {selectors.pdf_upload_input_index} " - f"using selector {selectors.pdf_upload_input_selector}." - ) - upload_inputs[selectors.pdf_upload_input_index].send_keys(pdf_path) - logger.info("DriveThruCards PDF uploaded. Please confirm the preview and continue checkout.") - - if selectors.continue_selector: - try: - self.wait_for_selector(selectors.continue_selector) - self.driver.find_element(By.CSS_SELECTOR, selectors.continue_selector).click() - except sl_exc.WebDriverException: - logger.warning("DriveThruCards continue button could not be clicked automatically.") - - if selectors.add_to_cart_selector: - try: - self.wait_for_selector(selectors.add_to_cart_selector) - self.driver.find_element(By.CSS_SELECTOR, selectors.add_to_cart_selector).click() - except sl_exc.WebDriverException: - logger.warning("DriveThruCards add-to-cart button could not be clicked automatically.") - - log_hours_minutes_seconds_elapsed(t) # endregion diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index f01266ae6..43e56d10e 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -99,6 +99,16 @@ def test_execute_drive_thru_cards_order_raises_when_login_not_completed(dtc_driv dtc_driver.execute_drive_thru_cards_order(order=SimpleNamespace(name="x"), pdf_path="/tmp/x.pdf") +def test_execute_drive_thru_cards_order_wraps_step_failures_with_context(dtc_driver: AutofillDriver) -> None: + dtc_driver.driver = SimpleNamespace(get=lambda _url: None) + dtc_driver.wait_for_cloudflare_challenge = lambda: None + dtc_driver.authenticate_dtc = lambda: True + dtc_driver.navigate_to_dtc_product_setup = lambda: (_ for _ in ()).throw(RuntimeError("new UI mismatch")) + + with pytest.raises(Exception, match="step 'navigate_to_dtc_product_setup' failed"): + dtc_driver.execute_drive_thru_cards_order(order=SimpleNamespace(name="x"), pdf_path="/tmp/x.pdf") + + def test_wait_for_cloudflare_challenge_returns_when_site_loaded(dtc_driver: AutofillDriver) -> None: dtc_driver._is_site_loaded = lambda: True dtc_driver._is_cloudflare_challenge_active = lambda: (_ for _ in ()).throw( From 880c696015db0630395403aa238da9f224dece53 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 17:13:01 -0800 Subject: [PATCH 26/76] Add explicit DTC Ghostscript install confirmation prompt --- desktop-tool/autofill.py | 22 ++++++++++++++++--- desktop-tool/tests/test_desktop_tool.py | 29 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 10c7e44ff..d0192ab6c 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -72,7 +72,15 @@ def wait_for_user_to_complete_order() -> None: ) -def ensure_ghostscript_available() -> str: +def confirm_ghostscript_install_for_dtc() -> bool: + return click.confirm( + "DriveThruCards requires Ghostscript for PDF/X-1a export. " + "Is it okay if MPC Autofill tries to install Ghostscript now?", + default=True, + ) + + +def ensure_ghostscript_available(ask_for_install_confirmation: bool = True) -> str: while True: gs_path = get_ghostscript_path() if gs_path: @@ -87,7 +95,9 @@ def ensure_ghostscript_available() -> str: "DriveThruCards export requires Ghostscript for PDF/X-1a compliance." ) - should_install = click.confirm("Install Ghostscript now?", default=True) + should_install = True + if ask_for_install_confirmation: + should_install = click.confirm("Install Ghostscript now?", default=True) if should_install: if sys.platform.startswith("darwin"): if shutil.which("brew") is None: @@ -451,7 +461,13 @@ def main( download_images_for_orders(orders=orders, post_processing_config=post_processing_config) return if target_site == TargetSites.DriveThruCards: - ensure_ghostscript_available() + gs_missing = get_ghostscript_path() is None + if gs_missing and not confirm_ghostscript_install_for_dtc(): + raise Exception( + "Ghostscript is required for DriveThruCards exports. " + "Please install it and re-run when ready." + ) + ensure_ghostscript_available(ask_for_install_confirmation=not gs_missing) using_default_icc = dtc_icc_profile is None resolved_icc_profile = dtc_icc_profile or get_default_dtc_icc_profile() if resolved_icc_profile is None: diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index caef55489..e0ee8740d 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -173,6 +173,35 @@ def fake_run(cmd, check=False): assert install_calls == [["sudo", "apt", "install", "-y", "ghostscript"]] +def test_confirm_ghostscript_install_for_dtc_uses_requirement_prompt(monkeypatch: pytest.MonkeyPatch) -> None: + asked = {"message": None, "default": None} + + def fake_confirm(message: str, default: bool = True) -> bool: + asked["message"] = message + asked["default"] = default + return True + + monkeypatch.setattr(autofill_cli.click, "confirm", fake_confirm) + + assert autofill_cli.confirm_ghostscript_install_for_dtc() is True + assert "DriveThruCards requires Ghostscript" in asked["message"] + assert asked["default"] is True + + +def test_ensure_ghostscript_available_skips_install_confirmation_when_preapproved( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/usr/local/bin/gs") + monkeypatch.setattr(autofill_cli, "get_ghostscript_version", lambda _path: "10.0.0") + monkeypatch.setattr( + autofill_cli.click, + "confirm", + lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("should not prompt")), + ) + + assert autofill_cli.ensure_ghostscript_available(ask_for_install_confirmation=False) == "/usr/local/bin/gs" + + def test_maybe_reuse_existing_pdfs_returns_none_when_skip_disabled(tmp_path) -> None: order_name = "example.xml" export_dir = tmp_path / "export" / "example" From 10385085570a37fc445b4a335319af82254463aa Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 17:27:20 -0800 Subject: [PATCH 27/76] Reduce default DTC step logs and remove post-login navigation delay --- desktop-tool/src/driver.py | 74 +++++++++---------- desktop-tool/tests/test_desktop_tool.py | 6 ++ .../tests/test_drivethrucards_driver.py | 40 ++++++++++ 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 1e4abbfa8..687d05b0c 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -630,33 +630,25 @@ def navigate_to_dtc_product_setup(self) -> None: """ self.set_state(States.defining_order, "Navigating to Publisher Tools") selectors = self.target_site.value.selectors - - # Step 1: Click "Publisher Tools" link (use same selector as login detection) - try: - publisher_tools_link = WebDriverWait(self.driver, 5).until( - element_to_be_clickable((By.CSS_SELECTOR, selectors.logged_in_indicator_selector)) - ) - logger.info("Found 'Publisher Tools' link, clicking...") - if self.click_element_with_retry(publisher_tools_link): - logger.info("Successfully clicked 'Publisher Tools'.") - else: - logger.warning("Could not click 'Publisher Tools' automatically. Please click it manually.") - except sl_exc.TimeoutException: + + # Step 1: Try quick click first to avoid post-login pause; otherwise navigate directly. + publisher_clicked = self.click_element_polling( + By.CSS_SELECTOR, selectors.logged_in_indicator_selector, timeout=1 + ) + if publisher_clicked: + logger.debug("Clicked 'Publisher Tools' link.") + else: logger.warning("Could not find 'Publisher Tools' link. Trying direct navigation...") self.driver.get("https://site.drivethrucards.com/pub_tools.php") - - # Step 2: Wait for and click "Set up a new title" link + + # Step 2: Try quick click for setup link; otherwise navigate directly. self.set_state(States.defining_order, "Navigating to product setup") - try: - setup_link = WebDriverWait(self.driver, 10).until( - element_to_be_clickable((By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]")) - ) - logger.info("Found 'Set up a new title' link, clicking...") - if self.click_element_with_retry(setup_link): - logger.info("Successfully clicked 'Set up a new title'.") - else: - logger.warning("Could not click 'Set up a new title' automatically. Please click it manually.") - except sl_exc.TimeoutException: + setup_clicked = self.click_element_polling( + By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]", timeout=2 + ) + if setup_clicked: + logger.debug("Clicked 'Set up a new title' link.") + else: logger.warning("Could not find 'Set up a new title' link. Trying direct navigation...") self.driver.get("https://tools.drivethrucards.com/pub_enter_product.php") @@ -683,7 +675,7 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: ) title_input.clear() title_input.send_keys(title) - logger.info(f"Set product title to: {title}") + logger.debug(f"Set product title to: {title}") except Exception as e: logger.warning(f"Could not fill title field: {e}") @@ -692,7 +684,7 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: price_input = self.driver.find_element(By.ID, "options_values_total_price") price_input.clear() price_input.send_keys("0") - logger.info("Set special price to: 0") + logger.debug("Set special price to: 0") except Exception as e: logger.warning(f"Could not fill price field: {e}") @@ -700,7 +692,7 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: try: image_input = self.driver.find_element(By.NAME, "products_image") image_input.send_keys(placeholder_cover_path) - logger.info(f"Uploaded placeholder cover image: {placeholder_cover_path}") + logger.debug(f"Uploaded placeholder cover image: {placeholder_cover_path}") except Exception as e: logger.warning(f"Could not upload cover image: {e}") @@ -727,9 +719,9 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: submit_button = WebDriverWait(self.driver, 10).until( element_to_be_clickable((By.ID, "submit_id")) ) - logger.info("Clicking submit button...") + logger.debug("Clicking submit button...") self.click_element_with_retry(submit_button) - logger.info("Product form submitted successfully.") + logger.debug("Product form submitted successfully.") except Exception as e: logger.warning(f"Could not click submit button: {e}") @@ -743,9 +735,9 @@ def submit_dtc_description_page(self) -> None: save_continue_button = WebDriverWait(self.driver, 15).until( element_to_be_clickable((By.ID, "clicked_element")) ) - logger.info("Clicking 'Save and Continue' button...") + logger.debug("Clicking 'Save and Continue' button...") self.click_element_with_retry(save_continue_button) - logger.info("Description page submitted.") + logger.debug("Description page submitted.") except Exception as e: logger.warning(f"Could not click 'Save and Continue': {e}") @@ -770,7 +762,7 @@ def open_dtc_upload_page(self) -> None: WebDriverWait(self.driver, 15).until( presence_of_element_located((By.ID, "card_type_select")) ) - logger.info(f"Navigated to upload page: {self.driver.current_url}") + logger.debug(f"Navigated to upload page: {self.driver.current_url}") else: logger.warning(f"Could not extract URL from upload button onclick: {onclick}") except Exception as e: @@ -804,7 +796,7 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: if euro_poker_option_text: select.select_by_visible_text(euro_poker_option_text) - logger.info(f"Selected '{euro_poker_option_text}' from dropdown.") + logger.debug(f"Selected '{euro_poker_option_text}' from dropdown.") else: logger.warning("Could not find Euro Poker option in dropdown.") except Exception as e: @@ -819,7 +811,7 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: logger.error(f"PDF file not found: {pdf_path}") return - logger.info(f"PDF file found: {pdf_path} ({os.path.getsize(pdf_path)} bytes)") + logger.debug(f"PDF file found: {pdf_path} ({os.path.getsize(pdf_path)} bytes)") # Wait for the dropzone to appear after card type selection self.set_state(States.inserting_fronts, "Uploading PDF") @@ -842,7 +834,7 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: # Find the file input and send the file - do this in a single operation # to avoid stale element references - logger.info(f"Uploading PDF: {pdf_path}") + logger.debug(f"Uploading PDF: {pdf_path}") def find_and_use_file_input() -> bool: """Find a usable file input and send the file path to it.""" @@ -928,7 +920,7 @@ def find_and_use_file_input() -> bool: return false; """) if upload_clicked: - logger.info("Clicked 'Begin Card File Upload' button via JavaScript.") + logger.debug("Clicked 'Begin Card File Upload' button via JavaScript.") else: logger.warning("Could not find upload button.") @@ -952,7 +944,7 @@ def find_and_use_file_input() -> bool: if d.find_elements(By.ID, "status_messages") else "" ) ) - logger.info("PDF upload completed to DriveThruCards.") + logger.debug("PDF upload completed to DriveThruCards.") except sl_exc.TimeoutException: logger.warning("Could not confirm upload completion. Please verify manually.") @@ -971,7 +963,7 @@ def continue_button_is_active(d: Any) -> Any: continue_button = WebDriverWait(self.driver, 60).until(continue_button_is_active) logger.debug(f"Continue button activated. onclick: {continue_button.get_attribute('onclick')}") self.driver.execute_script("arguments[0].click();", continue_button) - logger.info("Clicked 'Click here after uploading your files' button.") + logger.debug("Clicked 'Click here after uploading your files' button.") except sl_exc.TimeoutException: logger.warning("Continue button did not activate. Please click it manually.") @@ -981,7 +973,7 @@ def continue_button_is_active(d: Any) -> Any: element_to_be_clickable((By.ID, "submit_id")) ) self.driver.execute_script("arguments[0].click();", complete_button) - logger.info("Clicked 'Complete Setup' button.") + logger.debug("Clicked 'Complete Setup' button.") except sl_exc.TimeoutException: logger.warning("Could not find 'Complete Setup' button. Please click it manually.") @@ -994,10 +986,10 @@ def continue_button_is_active(d: Any) -> Any: buy_now_href = buy_now_link.get_attribute("href") if buy_now_href: self.driver.get(buy_now_href) - logger.info("Navigated to 'buy now' page to start placing the order.") + logger.debug("Navigated to 'buy now' page to start placing the order.") else: self.driver.execute_script("arguments[0].click();", buy_now_link) - logger.info("Clicked 'buy now' link.") + logger.debug("Clicked 'buy now' link.") except sl_exc.TimeoutException: logger.warning("Could not find 'buy now' link. Please click it manually.") except Exception as e: diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index e0ee8740d..42da87f98 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -426,6 +426,12 @@ def test_cli_help_includes_download_images_only_option() -> None: assert "--download-images-only" in result.output +def test_cli_help_includes_global_log_level_option() -> None: + result = CliRunner().invoke(autofill_cli.main, ["--help"]) + assert result.exit_code == 0 + assert "--log-level" in result.output + + def test_download_images_for_orders_downloads_fronts_and_backs() -> None: calls = {"fronts": 0, "backs": 0} diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index 43e56d10e..e6706883e 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -109,6 +109,46 @@ def test_execute_drive_thru_cards_order_wraps_step_failures_with_context(dtc_dri dtc_driver.execute_drive_thru_cards_order(order=SimpleNamespace(name="x"), pdf_path="/tmp/x.pdf") +def test_navigate_to_dtc_product_setup_uses_fast_polling_and_no_direct_fallback( + monkeypatch: pytest.MonkeyPatch, dtc_driver: AutofillDriver +) -> None: + poll_calls = [] + debug_logs = [] + get_calls = [] + + dtc_driver.driver = SimpleNamespace(get=lambda url: get_calls.append(url)) + + def fake_poll(by, selector, timeout=30): + poll_calls.append((by, selector, timeout)) + return True + + dtc_driver.click_element_polling = fake_poll + monkeypatch.setattr("src.driver.logger.debug", lambda msg: debug_logs.append(msg)) + + dtc_driver.navigate_to_dtc_product_setup() + + assert poll_calls == [ + (By.CSS_SELECTOR, TargetSites.DriveThruCards.value.selectors.logged_in_indicator_selector, 1), + (By.XPATH, "//a[contains(@href, 'pub_enter_product.php')]", 2), + ] + assert "Clicked 'Publisher Tools' link." in debug_logs + assert "Clicked 'Set up a new title' link." in debug_logs + assert get_calls == [] + + +def test_navigate_to_dtc_product_setup_direct_navigates_on_missing_links(dtc_driver: AutofillDriver) -> None: + get_calls = [] + dtc_driver.driver = SimpleNamespace(get=lambda url: get_calls.append(url)) + dtc_driver.click_element_polling = lambda *_args, **_kwargs: False + + dtc_driver.navigate_to_dtc_product_setup() + + assert get_calls == [ + "https://site.drivethrucards.com/pub_tools.php", + "https://tools.drivethrucards.com/pub_enter_product.php", + ] + + def test_wait_for_cloudflare_challenge_returns_when_site_loaded(dtc_driver: AutofillDriver) -> None: dtc_driver._is_site_loaded = lambda: True dtc_driver._is_cloudflare_challenge_active = lambda: (_ for _ in ()).throw( From cb80f69f1d877fe704181fe7323424befd459be6 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 17:50:52 -0800 Subject: [PATCH 28/76] Clarify help text for new CLI flags and stealth usage --- desktop-tool/autofill.py | 17 +++++++++-------- desktop-tool/tests/test_desktop_tool.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index d0192ab6c..634db7a09 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -262,21 +262,22 @@ def download_images_for_orders( "--browser-profile-path", default=None, help=( - "Optional Chromium user data directory to use (for existing profiles, cookies, and password managers). " + "Optional Chromium user-data directory for reusing existing profiles, cookies, and password managers. " "Example on macOS: ~/Library/Application Support/Google/Chrome" ), ) @click.option( "--browser-profile-name", default="Default", - help="Profile directory name inside --browser-profile-path, e.g. Default or 'Profile 1'.", + help="Profile directory name inside --browser-profile-path (e.g. Default or 'Profile 1').", ) @click.option( "--dtc-custom-stealth/--no-dtc-custom-stealth", default=False, help=( - "Apply additional custom stealth JavaScript in Chromium for DriveThruCards. " - "Disabled by default because these patches can trigger bot detection on some runs." + "Apply extra custom stealth JavaScript in Chromium for DriveThruCards. " + "Use this as a last resort when the default undetected-chromedriver flow is not sufficient; " + "disabled by default because these patches can trigger bot detection on some runs." ), is_flag=True, ) @@ -312,13 +313,13 @@ def download_images_for_orders( @click.option( "--download-images-only", default=False, - help="Only download card images to the cards directory and exit.", + help="Download card images to cards/ and exit (skip PDF creation and browser automation).", is_flag=True, ) @click.option( "--skip-pdf-if-exists/--no-skip-pdf-if-exists", default=False, - help="Skip creating a new PDF export when one already exists for the order.", + help="Reuse existing export PDFs when present; prompts to recreate if cards/ has newer files.", is_flag=True, ) @click.option( @@ -356,7 +357,7 @@ def download_images_for_orders( @click.option( "--dtc-icc-profile", default=None, - help="Optional ICC profile path to embed in DriveThruCards exports.", + help="Optional ICC profile path for DriveThruCards PDF/X conversion (defaults to bundled profile).", ) @click.option( "--combine-orders/--no-combine-orders", @@ -379,7 +380,7 @@ def download_images_for_orders( logging.getLevelName(logging.NOTSET), ] ), - help="Controls the level of logs written to standard output.", + help="Global CLI output verbosity. Use DEBUG to show detailed Selenium step-by-step logs.", ) @click.option( "--write-debug-logs", diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 42da87f98..571ad1d62 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -432,6 +432,18 @@ def test_cli_help_includes_global_log_level_option() -> None: assert "--log-level" in result.output +def test_cli_help_documents_new_flags_and_stealth_guidance() -> None: + result = CliRunner().invoke(autofill_cli.main, ["--help"]) + assert result.exit_code == 0 + assert "--skip-pdf-if-exists" in result.output + assert "--download-images-only" in result.output + assert "--browser-profile-path" in result.output + assert "--browser-profile-name" in result.output + assert "--dtc-custom-stealth / --no-dtc-custom-stealth" in result.output + assert "last resort" in result.output + assert "detailed Selenium step-by-step logs" in result.output + + def test_download_images_for_orders_downloads_fronts_and_backs() -> None: calls = {"fronts": 0, "backs": 0} From 5cfd974aeebd49738ab3ec88c5943751c8203d12 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 17:52:09 -0800 Subject: [PATCH 29/76] Update wiki draft with new flag guidance and logging notes --- desktop-tool/docs/Desktop-Tool.wiki.md | 14 ++++++++++++++ desktop-tool/tests/test_desktop_tool.py | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/desktop-tool/docs/Desktop-Tool.wiki.md b/desktop-tool/docs/Desktop-Tool.wiki.md index bcdce6697..38b61e869 100644 --- a/desktop-tool/docs/Desktop-Tool.wiki.md +++ b/desktop-tool/docs/Desktop-Tool.wiki.md @@ -37,15 +37,29 @@ The CLI checks Ghostscript availability before DriveThruCards processing and gui - Download all card images to `cards/` and exit. - Skips PDF creation and browser automation. +- `--dtc-icc-profile` + - Override the default bundled ICC profile used during DriveThruCards PDF/X conversion. + - Useful if your print workflow requires a specific ICC profile. + ### Browser Profile Flags - `--browser-profile-path` - Use an existing Chromium user data directory (cookies/session/password managers). - `--browser-profile-name` - Select the profile directory inside the user data folder (default: `Default`). +- `--dtc-custom-stealth` + - Enable additional custom stealth JavaScript for DriveThruCards. + - Use this as a last resort when the default `undetected-chromedriver` behavior is not sufficient. + - Disabled by default because extra JS patches can trigger bot detection on some runs. These are useful for DriveThruCards login reliability when bot detection blocks fresh sessions. +### Logging Flag + +- `--log-level` + - Global CLI verbosity control. + - Use `DEBUG` to show detailed Selenium step-by-step logs. + ## Notes - During PDF/X conversion, CLI status now reports progress per file (`Converting PDF to PDF/X-1a (n/total)`). diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 571ad1d62..1ebfbfc03 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -485,6 +485,10 @@ def test_wiki_draft_includes_cli_usage_updates() -> None: assert "--skip-pdf-if-exists" in wiki assert "--download-images-only" in wiki assert "--browser-profile-path" in wiki + assert "--dtc-custom-stealth" in wiki + assert "last resort" in wiki + assert "--log-level" in wiki + assert "Use `DEBUG` to show detailed Selenium step-by-step logs" in wiki assert "Ghostscript Requirement" in wiki From 010d73a66729a6b79b6258da9e873928d10721e9 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 18:12:30 -0800 Subject: [PATCH 30/76] Simplify boolean flags and streamline wiki addendum docs --- desktop-tool/autofill.py | 10 +-- .../docs/Desktop-Tool.wiki.addendum.md | 46 +++++++++++++ desktop-tool/docs/Desktop-Tool.wiki.md | 66 ------------------- desktop-tool/tests/test_desktop_tool.py | 17 +++-- 4 files changed, 63 insertions(+), 76 deletions(-) create mode 100644 desktop-tool/docs/Desktop-Tool.wiki.addendum.md delete mode 100644 desktop-tool/docs/Desktop-Tool.wiki.md diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 634db7a09..fde387047 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -272,12 +272,12 @@ def download_images_for_orders( help="Profile directory name inside --browser-profile-path (e.g. Default or 'Profile 1').", ) @click.option( - "--dtc-custom-stealth/--no-dtc-custom-stealth", + "--dtc-custom-stealth", default=False, help=( - "Apply extra custom stealth JavaScript in Chromium for DriveThruCards. " - "Use this as a last resort when the default undetected-chromedriver flow is not sufficient; " - "disabled by default because these patches can trigger bot detection on some runs." + "Optional extra stealth JavaScript for DriveThruCards. " + "Use only as a last resort if default automation gets stuck around Cloudflare/login checks; " + "disabled by default because it can increase bot-detection risk on some runs." ), is_flag=True, ) @@ -317,7 +317,7 @@ def download_images_for_orders( is_flag=True, ) @click.option( - "--skip-pdf-if-exists/--no-skip-pdf-if-exists", + "--skip-pdf-if-exists", default=False, help="Reuse existing export PDFs when present; prompts to recreate if cards/ has newer files.", is_flag=True, diff --git a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md new file mode 100644 index 000000000..8e9e64f8a --- /dev/null +++ b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md @@ -0,0 +1,46 @@ +# Desktop Tool Wiki Addendum (PR Patch) + +This file contains only the incremental wiki updates introduced in this PR. +Target page: + +## Insert Under Existing "Useful CLI Flags" Section + +### PDF/Export Flags (add these bullets if missing) + +- `--skip-pdf-if-exists` + - Reuse existing exported PDFs when present for the order. + - If files in `cards/` are newer than existing PDFs, the CLI prompts whether to recreate. + - For DriveThruCards, existing files are reused only when a `_pdfx.pdf` output exists. + +- `--download-images-only` + - Download all card images to `cards/` and exit. + - Skips PDF creation and browser automation. + +- `--dtc-icc-profile` + - Override the default bundled ICC profile used during DriveThruCards PDF/X conversion. + - Useful if your print workflow requires a specific ICC profile. + +### Browser/Profile Flags (add these bullets if missing) + +- `--browser-profile-path` + - Use an existing Chromium user data directory (cookies/session/password managers). + +- `--browser-profile-name` + - Select the profile directory inside the user data folder (default: `Default`). + +- `--dtc-custom-stealth` + - Optional extra stealth JavaScript for DriveThruCards. + - Use only as a last resort if default automation gets stuck around Cloudflare/login checks. + - Disabled by default because it can increase bot-detection risk on some runs. + +### Logging Flag (add subsection if missing) + +- `--log-level` + - Global CLI verbosity control. + - Use `DEBUG` to show detailed Selenium step-by-step logs. + +## Insert Under Existing Notes/Behavior Section + +- During PDF/X conversion, CLI status reports progress per file (`Converting PDF to PDF/X-1a (n/total)`). +- Build/bundled desktop artifacts include the full `assets/` directory. +- DriveThruCards now explicitly asks for confirmation before attempting Ghostscript installation. diff --git a/desktop-tool/docs/Desktop-Tool.wiki.md b/desktop-tool/docs/Desktop-Tool.wiki.md deleted file mode 100644 index 38b61e869..000000000 --- a/desktop-tool/docs/Desktop-Tool.wiki.md +++ /dev/null @@ -1,66 +0,0 @@ -# Desktop Tool - -The desktop tool automates order creation workflows and supports DriveThruCards PDF upload. - -## DriveThruCards Workflow - -### Ghostscript Requirement - -DriveThruCards export requires PDF/X-1a conversion via Ghostscript. - -Install Ghostscript: - -- macOS: `brew install ghostscript` -- Ubuntu/Debian: `sudo apt install -y ghostscript` -- Fedora: `sudo dnf install -y ghostscript` -- Windows: install from - - Optional: `winget install --id ArtifexSoftware.Ghostscript` - -The CLI checks Ghostscript availability before DriveThruCards processing and guides install/retry when missing. - -### Standard DriveThruCards Run - -```bash -.venv/bin/python autofill.py --site drivethrucards --directory /Users/bradley/Documents/dtc-test -``` - -## Useful CLI Flags - -### PDF/Export Flags - -- `--skip-pdf-if-exists` - - Reuse existing exported PDFs when present for the order. - - If files in `cards/` are newer than existing PDFs, the CLI prompts whether to recreate. - - For DriveThruCards, existing files are reused only when a `_pdfx.pdf` output exists. - -- `--download-images-only` - - Download all card images to `cards/` and exit. - - Skips PDF creation and browser automation. - -- `--dtc-icc-profile` - - Override the default bundled ICC profile used during DriveThruCards PDF/X conversion. - - Useful if your print workflow requires a specific ICC profile. - -### Browser Profile Flags - -- `--browser-profile-path` - - Use an existing Chromium user data directory (cookies/session/password managers). -- `--browser-profile-name` - - Select the profile directory inside the user data folder (default: `Default`). -- `--dtc-custom-stealth` - - Enable additional custom stealth JavaScript for DriveThruCards. - - Use this as a last resort when the default `undetected-chromedriver` behavior is not sufficient. - - Disabled by default because extra JS patches can trigger bot detection on some runs. - -These are useful for DriveThruCards login reliability when bot detection blocks fresh sessions. - -### Logging Flag - -- `--log-level` - - Global CLI verbosity control. - - Use `DEBUG` to show detailed Selenium step-by-step logs. - -## Notes - -- During PDF/X conversion, CLI status now reports progress per file (`Converting PDF to PDF/X-1a (n/total)`). -- Build/bundled desktop artifacts include the full `assets/` directory. diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 1ebfbfc03..8d82c825c 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -439,7 +439,7 @@ def test_cli_help_documents_new_flags_and_stealth_guidance() -> None: assert "--download-images-only" in result.output assert "--browser-profile-path" in result.output assert "--browser-profile-name" in result.output - assert "--dtc-custom-stealth / --no-dtc-custom-stealth" in result.output + assert "--dtc-custom-stealth" in result.output assert "last resort" in result.output assert "detailed Selenium step-by-step logs" in result.output @@ -478,18 +478,25 @@ def test_readme_points_users_to_wiki_for_usage_docs() -> None: assert "--download-images-only" not in readme -def test_wiki_draft_includes_cli_usage_updates() -> None: - wiki_draft_path = os.path.join(os.path.dirname(autofill_cli.__file__), "docs", "Desktop-Tool.wiki.md") - with open(wiki_draft_path, "r", encoding="utf-8") as f: +def test_wiki_addendum_includes_cli_usage_updates() -> None: + wiki_addendum_path = os.path.join( + os.path.dirname(autofill_cli.__file__), + "docs", + "Desktop-Tool.wiki.addendum.md", + ) + with open(wiki_addendum_path, "r", encoding="utf-8") as f: wiki = f.read() + assert "contains only the incremental wiki updates" in wiki + assert "Insert Under Existing \"Useful CLI Flags\" Section" in wiki assert "--skip-pdf-if-exists" in wiki assert "--download-images-only" in wiki assert "--browser-profile-path" in wiki assert "--dtc-custom-stealth" in wiki assert "last resort" in wiki + assert "Cloudflare/login checks" in wiki assert "--log-level" in wiki assert "Use `DEBUG` to show detailed Selenium step-by-step logs" in wiki - assert "Ghostscript Requirement" in wiki + assert "confirmation before attempting Ghostscript installation" in wiki # endregion From 159f1fedf3252e11b3a9ec631c2833a1d7dbc358 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 20:47:46 -0800 Subject: [PATCH 31/76] Place DriveThruCards last in site picker choices --- desktop-tool/autofill.py | 10 +++++++++- desktop-tool/tests/test_desktop_tool.py | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index fde387047..5ccc5fdca 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -239,6 +239,14 @@ def download_images_for_orders( logger.info("Finished downloading card images.") +def get_site_picker_choices() -> list[str]: + site_names = [site.name for site in TargetSites] + # Keep DriveThruCards available but place it last in the picker list. + return [name for name in site_names if name != TargetSites.DriveThruCards.name] + [ + TargetSites.DriveThruCards.name + ] + + @click.command(context_settings={"show_default": True}) @click.option("-d", "--directory", default=None, help="The directory to search for order XML files.") @click.option( @@ -285,7 +293,7 @@ def download_images_for_orders( "--site", prompt=prompt_if_no_arguments("Which site should the tool auto-fill your project into?"), default=TargetSites.MakePlayingCards.name, - type=click.Choice(sorted([site.name for site in TargetSites]), case_sensitive=False), + type=click.Choice(get_site_picker_choices(), case_sensitive=False), help="The card printing site into which your order should be auto-filled.", ) @click.option( diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 8d82c825c..d01c67ffe 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -444,6 +444,13 @@ def test_cli_help_documents_new_flags_and_stealth_guidance() -> None: assert "detailed Selenium step-by-step logs" in result.output +def test_cli_site_choices_list_drivethrucards_last() -> None: + result = CliRunner().invoke(autofill_cli.main, ["--help"]) + assert result.exit_code == 0 + site_line = next(line for line in result.output.splitlines() if line.strip().startswith("--site [")) + assert site_line.endswith("DriveThruCards]") + + def test_download_images_for_orders_downloads_fronts_and_backs() -> None: calls = {"fronts": 0, "backs": 0} From 294f215ce3c055baa7f92c70030295ba23291ff4 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 8 Feb 2026 22:27:10 -0800 Subject: [PATCH 32/76] Ignore build files and AI help files --- .gitignore | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 111414245..99dcbc0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Ignore XML files we might use for debugging cards.xml -cards/* +**/cards/* +test_order.xml # Ignore pyinstaller stuff __pycache__/* @@ -18,6 +19,16 @@ __pycache__/ # C extensions *.so +# Ignore Nuitka package stuff +**/autofill.dist/* +**/autofill.build/* +**/autofill.onefile-build/* +**/autofill.bin +autofill.command +autofill.exe +nuitka-crash-report.xml +autofill_crash_log.txt + # Distribution / packaging .Python build/ @@ -96,7 +107,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -117,7 +128,7 @@ celerybeat.pid # Environments .env -.venv +.venv*/ env/ venv/ ENV/ @@ -185,5 +196,12 @@ frontend/public/mockServiceWorker.js # Plan files *.plan.md -# Claude Code -claude.md +# AI instruction files +**/[Cc][Ll][Aa][Uu][Dd][Ee].md +**/[Gg][Pp][Tt].md +**/[Gg][Ee][Mm][Ii][Nn][Ii].md +**/[Aa][Gg][Ee][Nn][Tt][Ss].md +**/.cursorrules +**/.github/copilot-instructions.md +**/.github/instructions/*.instructions.md +**/.cursor/rules/*.mdc From 9044d8dee09297df237161de1353f6e6b07a1552 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 13 Mar 2026 01:52:49 -0700 Subject: [PATCH 33/76] Honor DriveThruCards PDF export mode --- desktop-tool/autofill.py | 61 ++++++++----- desktop-tool/src/pdf_maker.py | 5 +- desktop-tool/tests/test_desktop_tool.py | 114 ++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 23 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 5ccc5fdca..4ee807dca 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -239,6 +239,36 @@ def download_images_for_orders( logger.info("Finished downloading card images.") +def get_dtc_pdf_paths_for_order( + order: CardOrder, + skip_pdf_if_exists: bool, + working_directory: str, + resolved_icc_profile: str, + downscale_alg: str, +) -> list[str]: + pdf_paths = maybe_reuse_existing_pdfs( + order_name=order.name, + skip_pdf_if_exists=skip_pdf_if_exists, + cards_directory=get_image_directory(working_directory), + require_pdfx=True, + ) + if pdf_paths is not None: + return pdf_paths + + dtc_post_processing_config = ImagePostProcessingConfig( + max_dpi=300, + downscale_alg=ImageResizeMethods[downscale_alg], + output_format="JPEG", + convert_to_cmyk=False, + ) + exporter = PdfExporter( + order=order, + export_mode="drive_thru_cards", + pdfx_config=PdfXConversionConfig(icc_profile_path=resolved_icc_profile), + ) + return exporter.execute(post_processing_config=dtc_post_processing_config) + + def get_site_picker_choices() -> list[str]: site_names = [site.name for site in TargetSites] # Keep DriveThruCards available but place it last in the picker list. @@ -490,31 +520,17 @@ def main( ) icc_source = "bundled default" if using_default_icc else "user-provided" logger.info(f"DriveThruCards ICC profile ({icc_source}): {bold(resolved_icc_profile)}") - # Keep images as RGB JPEG during PDF generation - fpdf can embed these directly - # with DCT compression. Ghostscript will handle CMYK conversion with the ICC - # profile during PDF/X-1a conversion, which produces better results than - # pre-converting to CMYK (which fpdf re-encodes with FlateDecode, bloating file size). - dtc_post_processing_config = ImagePostProcessingConfig( - max_dpi=300, - downscale_alg=ImageResizeMethods[downscale_alg], - output_format="JPEG", - convert_to_cmyk=False, # Let Ghostscript handle CMYK conversion - ) orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) for i, order in enumerate(orders, start=1): - pdf_paths = maybe_reuse_existing_pdfs( - order_name=order.name, + pdf_paths = get_dtc_pdf_paths_for_order( + order=order, skip_pdf_if_exists=skip_pdf_if_exists, - cards_directory=get_image_directory(working_directory), - require_pdfx=True, + working_directory=working_directory, + resolved_icc_profile=resolved_icc_profile, + downscale_alg=downscale_alg, ) - if pdf_paths is None: - exporter = PdfExporter( - order=order, - export_mode="drive_thru_cards", - pdfx_config=PdfXConversionConfig(icc_profile_path=resolved_icc_profile), - ) - pdf_paths = exporter.execute(post_processing_config=dtc_post_processing_config) + if exportpdf: + continue # Only use the Ghostscript PDF/X-1a output - no fallback dtc_pdf_path = next((path for path in reversed(pdf_paths) if path.endswith("_pdfx.pdf")), None) if dtc_pdf_path is None: @@ -534,7 +550,8 @@ def main( ).execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") - wait_for_user_to_complete_order() + if not exportpdf: + wait_for_user_to_complete_order() elif exportpdf: order = CardOrder.from_xmls_in_folder(working_directory=working_directory)[0] if ( diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 854448914..75550c812 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -143,7 +143,10 @@ def __attrs_post_init__(self) -> None: self.card_width_in_inches = DTC_CARD_WIDTH_INCHES self.card_height_in_inches = DTC_CARD_HEIGHT_INCHES self.separate_faces = False - self.number_of_cards_per_file = max(1, self.order.details.quantity) + # Build one combined PDF per order using the actual exported slots. + # This is more robust than trusting `details.quantity`, which can + # under-report cards in some XMLs that still enumerate valid slots. + self.number_of_cards_per_file = max(1, len(self.order.fronts.slots())) else: self.ask_questions() self.configure_bars() diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index d01c67ffe..35cfae608 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -1,7 +1,9 @@ import os +import re import textwrap import time from concurrent.futures import ThreadPoolExecutor +from contextlib import nullcontext from itertools import groupby from queue import Queue from types import SimpleNamespace @@ -75,6 +77,11 @@ def assert_file_size(file_path: str, size: int) -> None: assert os.stat(file_path).st_size == size, f"File size {os.stat(file_path).st_size} does not match {size}" +def count_pdf_pages(file_path: str) -> int: + with open(file_path, "rb") as pdf_file: + return len(re.findall(rb"/Type\s*/Page\b", pdf_file.read())) + + # endregion # region Ghostscript @@ -451,6 +458,58 @@ def test_cli_site_choices_list_drivethrucards_last() -> None: assert site_line.endswith("DriveThruCards]") +def test_main_drive_thru_cards_exportpdf_generates_pdfs_without_browser_automation( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + icc_path = tmp_path / "test.icc" + icc_path.write_bytes(b"icc") + + calls = {"pdf": [], "wait": 0, "driver": 0} + + monkeypatch.setattr(autofill_cli, "configure_loggers", lambda **_kwargs: None) + monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/opt/homebrew/bin/gs") + monkeypatch.setattr(autofill_cli, "ensure_ghostscript_available", lambda **_kwargs: "/opt/homebrew/bin/gs") + monkeypatch.setattr(autofill_cli, "get_default_dtc_icc_profile", lambda: str(icc_path)) + monkeypatch.setattr( + autofill_cli.CardOrder, + "from_xmls_in_folder", + lambda working_directory: [SimpleNamespace(name="test_local")], + ) + + def fake_get_dtc_pdf_paths_for_order(**kwargs): + calls["pdf"].append(kwargs["order"].name) + return ["export/test_local/1.pdf", "export/test_local/1_pdfx.pdf"] + + monkeypatch.setattr(autofill_cli, "get_dtc_pdf_paths_for_order", fake_get_dtc_pdf_paths_for_order) + monkeypatch.setattr(autofill_cli, "wait_for_user_to_complete_order", lambda: calls.__setitem__("wait", 1)) + + class ShouldNotInstantiateDriver: + def __init__(self, *args, **kwargs) -> None: + calls["driver"] += 1 + raise AssertionError("DriveThruCards browser automation should not run during --exportpdf") + + monkeypatch.setattr(autofill_cli, "AutofillDriver", ShouldNotInstantiateDriver) + + result = CliRunner().invoke( + autofill_cli.main, + [ + "--directory", + str(tmp_path), + "--site", + constants.TargetSites.DriveThruCards.name, + "--exportpdf", + "--browser", + constants.Browsers.chrome.name, + ], + ) + + assert result.exit_code == 0 + assert calls["pdf"] == ["test_local"] + assert calls["wait"] == 0 + assert calls["driver"] == 0 + + def test_download_images_for_orders_downloads_fronts_and_backs() -> None: calls = {"fronts": 0, "backs": 0} @@ -1988,6 +2047,61 @@ def do_nothing(_): remove_directories(["export/test_order/backs", "export/test_order/fronts", "export/test_order", "export"]) +def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(monkeypatch, tmp_path): + monkeypatch.chdir(tmp_path) + + for image_name, color in [("front_a.png", "red"), ("front_b.png", "blue"), ("back.png", "black")]: + Image.new("RGB", (300, 420), color).save(tmp_path / image_name) + + order = CardOrder.from_element( + working_directory=str(tmp_path), + element=ElementTree.fromstring( + textwrap.dedent( + f""" + +

hF1k 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
-
-

DriveThruCards publisher-account automation

-

Implemented browser-first experience for signing in, creating a basic account when necessary, enabling publisher permissions, and handing off to the existing card-order automation.

-
- Desktop tool action - DriveThruCards page or user action - Required success state -
-
- -
-

1. Explain sign-in before sending the user to DriveThruCards

-
- - Browser-first sign-in and publisher readiness flow - The console announces that browser instructions are opening. A local instruction page explains Google, Facebook, and new-account choices before opening DriveThruCards. After base sign-in is detected separately, the Publish tab determines whether publisher setup is required. - - - - - - -
Console message“DriveThruCards setup will open in your browser. Follow the instructions there.”
-
- - - - -
Open local instruction pageReuse the existing MakePlayingCards landing-page pattern. The user does not need to look back at the console.
-
- - - - -
Continue to DriveThruCardsExisting users sign in. New users sign in with Google or Facebook or click Get Started Here!
-
- - - - -
Handle CAPTCHA automaticallyUse the existing stealth and Turnstile techniques; never wait for the user.
-
- - - - -
Base account signed inDetect ordinary sign-in separately from publisher permissions.
-
- - - - -
Publish tab visible?
-
- - - Yes - - -
Continue card order
-
- - - No - - -
Run publisher setup
-
-
-
-

The browser page appears before DriveThruCards, so every user sees the sign-in choices without switching attention back to the console.

- - -
- -
-

2. Complete publisher setup automatically

-
- - Legacy DriveThruCards publisher wizard - The tool opens the legacy setup, chooses a non-exclusive account, fills the agreement and checks I Agree automatically, keeps payment defaults, saves, and verifies the Publish tab. - - - - - - -
Open legacy setup/joinchoice.php
-
- - - - -
Account TypeCreate a non-exclusive publisher account.
-
- - - - -
AgreementSet publisher name, check “I Agree” automatically, and submit. No console confirmation.
-
- - - - -
Payment InfoKeep the prefilled defaults and save.
-
- - - - -
Publish tab visible?
-
- - - Yes - - -
Continue card order
-
- - - No - - -
Setup failedReport the last step and leave the browser open for inspection.
-
-
-
-

Confirmation copy, URLs, and the Publisher Hub link are informational only. The visible Publish tab is authoritative.

-
- -
-

Authoritative publisher-permission check

-
- Log Off - Account - Publish - My Library - Wishlists -
-

Success means: a visible top-navigation element matches a[href*='pub_tools.php'].

-
- -
Last updated: July 16, 2026 — Source: captured DriveThruCards walkthrough, browser snapshots, and verified desktop-tool selectors.
-
- - diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 1e2821d29..d537db853 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -40,6 +40,11 @@ from src.pdf_maker import PdfExporter, convert_pdf_to_pdfx, get_ghostscript_version from src.processing import ImagePostProcessingConfig +requires_google_drive_credentials = pytest.mark.skipif( + not os.path.isfile(os.path.join(os.path.dirname(__file__), "..", "client_secrets.json")), + reason="Google Drive API credentials (client_secrets.json) are not available", +) + DEFAULT_POST_PROCESSING = ImagePostProcessingConfig(max_dpi=800, downscale_alg=constants.ImageResizeMethods.LANCZOS) @@ -625,24 +630,6 @@ def test_readme_points_users_to_wiki_for_usage_docs() -> None: assert "--download-images-only" not in readme -def test_wiki_addendum_includes_cli_usage_updates() -> None: - wiki_addendum_path = os.path.join( - os.path.dirname(autofill_cli.__file__), - "docs", - "Desktop-Tool.wiki.addendum.md", - ) - with open(wiki_addendum_path, "r", encoding="utf-8") as f: - wiki = f.read() - assert "contains only the incremental wiki updates" in wiki - assert 'Insert Under Existing "Useful CLI Flags" Section' in wiki - assert "--skip-pdf-if-exists" in wiki - assert "--download-images-only" in wiki - assert "--browser-profile-path" in wiki - assert "--log-level" in wiki - assert "Use `DEBUG` to show detailed Selenium step-by-step logs" in wiki - assert "confirmation before attempting Ghostscript installation" in wiki - - # endregion # region ICC profile resolution @@ -1366,6 +1353,7 @@ def card_order_element_missing_front_image() -> Generator[ElementTree.Element, N # region test utils.py +@requires_google_drive_credentials def test_get_google_drive_file_name(): assert get_google_drive_file_name(SIMPLE_LOTUS_ID) == f"{SIMPLE_LOTUS}.png" assert get_google_drive_file_name(SIMPLE_CUBE_ID) == f"{SIMPLE_CUBE}.png" @@ -1396,6 +1384,7 @@ def test_generate_file_path_infer_local_file(image_element_local_file_inferred_t assert image.source_type == SourceType.LOCAL_FILE +@requires_google_drive_credentials def test_download_google_drive_image_default_post_processing( image_valid_google_drive: CardImage, counter: Counter, queue: Queue[CardImage] ): @@ -1416,6 +1405,7 @@ def test_download_local_file_is_no_op(image_local_file: CardImage, counter: Coun assert_file_size(image_local_file.file_path, file_size) +@requires_google_drive_credentials def test_download_google_drive_image_downscaled( image_valid_google_drive: CardImage, counter: Counter, queue: Queue[CardImage] ): @@ -1431,6 +1421,7 @@ def test_download_google_drive_image_downscaled( assert_file_size(image_valid_google_drive.file_path, 51123) +@requires_google_drive_credentials def test_download_google_drive_image_no_post_processing( image_valid_google_drive: CardImage, counter: Counter, queue: Queue[CardImage] ): @@ -1440,6 +1431,7 @@ def test_download_google_drive_image_no_post_processing( assert_file_size(image_valid_google_drive.file_path, 155686) +@requires_google_drive_credentials def test_invalid_google_drive_image(image_invalid_google_drive: CardImage, counter: Counter, queue: Queue[CardImage]): image_invalid_google_drive.download_image( download_bar=counter, queue=queue, post_processing_config=DEFAULT_POST_PROCESSING @@ -1447,6 +1439,7 @@ def test_invalid_google_drive_image(image_invalid_google_drive: CardImage, count assert image_invalid_google_drive.errored is True +@requires_google_drive_credentials def test_retrieve_card_name_and_download_file(image_google_valid_drive_no_name, counter, queue): assert image_google_valid_drive_no_name.name == f"{SIMPLE_CUBE}.png" assert not image_google_valid_drive_no_name.file_exists() @@ -1487,6 +1480,7 @@ def test_combine_images(image_a, image_b, expected_result): # region test CardImageCollection +@requires_google_drive_credentials def test_card_image_collection_download(card_image_collection_valid, counter, image_google_valid_drive_no_name, pool): assert card_image_collection_valid.slots() == {0, 1, 2} assert [x.file_exists() for x in card_image_collection_valid.cards_by_id.values()] == [False, True] From f4561aaeac40052def61a87028a42bf4d382e0f9 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 02:15:22 -0700 Subject: [PATCH 52/76] Apply black formatting and restore browser factory type-ignore --- desktop-tool/src/driver.py | 7 +++++-- desktop-tool/src/order.py | 4 +--- desktop-tool/src/pdf_maker.py | 10 +++------- desktop-tool/src/processing.py | 4 +--- desktop-tool/tests/test_desktop_tool.py | 8 ++------ 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 2fbcfa7f3..f18688a0b 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -46,7 +46,10 @@ ignore_javascript_errors, log_hours_minutes_seconds_elapsed, ) -from src.webdrivers import get_default_brave_binary_location, get_undetected_chrome_driver +from src.webdrivers import ( + get_default_brave_binary_location, + get_undetected_chrome_driver, +) @attr.s @@ -99,7 +102,7 @@ def create_driver(self) -> WebDriver: user_data_dir=self.browser_profile_path, profile_directory=self.browser_profile_name, ) - return self.browser.value(headless=self.headless, binary_location=self.binary_location) + return self.browser.value(headless=self.headless, binary_location=self.binary_location) # type: ignore[operator] def initialise_driver(self) -> None: max_attempts = 3 diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index 502dad9bc..be3807213 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -252,9 +252,7 @@ def download_image( break if self.file_exists(): remove_if_exists(cast(str, self.file_path)) - logger.info( - f"Downloaded image '{bold(self.name)}' appears corrupted. Retrying download..." - ) + logger.info(f"Downloaded image '{bold(self.name)}' appears corrupted. Retrying download...") if attempt == 1: self.errored = True diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index fbe7839f2..5424d907b 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -2,9 +2,9 @@ import shutil import subprocess import tempfile -from pathlib import Path from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass +from pathlib import Path from typing import Optional import attr @@ -159,9 +159,7 @@ def convert_pdf_to_pdfx( result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: logger.warning( - "Ghostscript failed to convert PDF/X-1a.\n" - f"stdout: {result.stdout}\n" - f"stderr: {result.stderr}" + "Ghostscript failed to convert PDF/X-1a.\n" f"stdout: {result.stdout}\n" f"stderr: {result.stderr}" ) return False if not verify_pdfx_output(temporary_output_path): @@ -349,9 +347,7 @@ def download_and_collect_images(self, post_processing_config: Optional[ImagePost def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) -> list[str]: if self.export_mode == "drive_thru_cards" and post_processing_config is not None: # Calculate exact pixel dimensions for the target DPI at DTC card size (2.73" x 3.71") - post_processing_config.target_pixel_size = calculate_dtc_target_pixel_size( - post_processing_config.max_dpi - ) + post_processing_config.target_pixel_size = calculate_dtc_target_pixel_size(post_processing_config.max_dpi) # Embed DPI metadata so PDF tools correctly interpret the image resolution post_processing_config.embed_dpi_metadata = True self.image_post_processing_config = post_processing_config diff --git a/desktop-tool/src/processing.py b/desktop-tool/src/processing.py index 234662881..f6d524dc9 100644 --- a/desktop-tool/src/processing.py +++ b/desktop-tool/src/processing.py @@ -46,9 +46,7 @@ def get_post_processed_path(file_path: str, config: ImagePostProcessingConfig) - return os.path.join(directory, f"{base_name}{extension}") -def _apply_color_processing( - img: "Image", config: ImagePostProcessingConfig -) -> tuple["Image", Optional[bytes]]: +def _apply_color_processing(img: "Image", config: ImagePostProcessingConfig) -> tuple["Image", Optional[bytes]]: icc_profile_bytes = None if config.convert_to_cmyk: if img.mode in ("RGBA", "LA"): diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index d537db853..49e87abb7 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -681,9 +681,7 @@ def test_find_or_download_dtc_icc_profile_downloads_and_caches(monkeypatch: pyte assert (tmp_path / "cache" / "USWebCoatedSWOP.icc").read_bytes() == profile_bytes -def test_find_or_download_dtc_icc_profile_rejects_checksum_mismatch( - monkeypatch: pytest.MonkeyPatch, tmp_path -) -> None: +def test_find_or_download_dtc_icc_profile_rejects_checksum_mismatch(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: monkeypatch.setattr(src.icc, "_candidate_profile_paths", lambda: [tmp_path / "missing.icc"]) monkeypatch.setattr(src.icc, "get_profile_cache_path", lambda: tmp_path / "cache" / "USWebCoatedSWOP.icc") monkeypatch.setattr(src.icc.click, "confirm", lambda *_args, **_kwargs: True) @@ -745,9 +743,7 @@ def fake_run(cmd, capture_output=True, text=True): assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] -def test_convert_pdf_to_pdfx_rejects_output_missing_pdfx_markers( - monkeypatch: pytest.MonkeyPatch, tmp_path -) -> None: +def test_convert_pdf_to_pdfx_rejects_output_missing_pdfx_markers(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: source_path = tmp_path / "source.pdf" output_path = tmp_path / "output.pdf" source_path.write_bytes(b"source") From 91436fd0b9e1f0aa04363cae5da56079c8189e0c Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 03:25:48 -0700 Subject: [PATCH 53/76] Remove dead local-file post-processing branch from download_image No production config sets output_directory/output_extension, so the get_post_processed_path redirect was an identity no-op and the local-file post-processing elif was unreachable (its guard implied the preceding branch's condition). DTC passes download_config=None anyway; its per-card resize happens in PdfExporter.add_image. Corruption validation and the 2-attempt Drive retry are kept. --- desktop-tool/src/order.py | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index be3807213..2cde8a952 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -27,12 +27,7 @@ get_image_directory, ) from src.logging import logger -from src.processing import ( - ImagePostProcessingConfig, - get_post_processed_path, - post_process_image, - save_processed_image, -) +from src.processing import ImagePostProcessingConfig from src.utils import unpack_element @@ -203,10 +198,6 @@ def download_image( post_processing_config: Optional[ImagePostProcessingConfig], ) -> None: try: - source_path = self.file_path - if post_processing_config and self.file_path: - self.file_path = get_post_processed_path(self.file_path, post_processing_config) - os.makedirs(os.path.dirname(self.file_path), exist_ok=True) if self.source_type == SourceType.LOCAL_FILE: if self.file_exists() and not self.errored: if is_image_valid(cast(str, self.file_path)): @@ -217,26 +208,6 @@ def download_image( f"{bold(self.file_path)}\n" ) self.errored = True - elif post_processing_config and source_path and file_exists(source_path) and not self.errored: - with open(source_path, "rb") as f: - raw_image = f.read() - processed_image, icc_profile_bytes = post_process_image( - raw_image=raw_image, config=post_processing_config - ) - save_processed_image( - processed_image, - file_path=cast(str, self.file_path), - config=post_processing_config, - icc_profile_bytes=icc_profile_bytes, - ) - if is_image_valid(cast(str, self.file_path)): - self.downloaded = True - else: - logger.info( - f"Processed local file '{bold(self.name)}' appears to be corrupted at path:\n" - f"{bold(self.file_path)}\n" - ) - self.errored = True else: logger.info(f"Local file '{bold(self.name)}' does not exist at path:\n{bold(self.drive_id)}\n") elif self.source_type == SourceType.GOOGLE_DRIVE: From 3b75afeaf62d9a60c4a0ee975abcd746e04bd600 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 03:42:57 -0700 Subject: [PATCH 54/76] Process and embed each unique image once per DTC PDF export add_image re-ran the resize/JPEG-encode for every slot and wrote a uniquely named temp file each time, so a card repeated across slots (especially the shared cardback) was processed N times and, because fpdf caches embedded images by path, its JPEG data was embedded N times in the output PDF. Cache the processed temp file per source path for the exporter run and clean the temp files up when execute() finishes. --- desktop-tool/src/pdf_maker.py | 66 +++++++++++++---------- desktop-tool/tests/test_desktop_tool.py | 70 +++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 5424d907b..3c8a3f38e 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -197,6 +197,7 @@ class PdfExporter: download_bar: enlighten.Counter = attr.ib(init=False, default=None) processed_bar: enlighten.Counter = attr.ib(init=False, default=None) saved_files: list[str] = attr.ib(init=False, factory=list) + processed_image_paths: dict[str, str] = attr.ib(init=False, factory=dict) def configure_bars(self) -> None: num_images = len(self.order.fronts.cards_by_id) + len(self.order.backs.cards_by_id) @@ -277,36 +278,37 @@ def generate_pdf(self) -> None: def add_image(self, image_path: str) -> None: self.pdf.add_page() if self.export_mode == "drive_thru_cards" and self.image_post_processing_config: - with open(image_path, "rb") as f: - raw_image = f.read() - # post_process_image handles resizing to target_pixel_size (set in execute()) - # which ensures the correct DPI for the DTC card dimensions - processed_image, icc_profile_bytes = post_process_image( - raw_image=raw_image, config=self.image_post_processing_config - ) - # Save to a temporary file so fpdf embeds the JPEG data directly - # (passing BytesIO causes fpdf to re-encode with FlateDecode, bloating file size) - ext = ".jpg" if self.image_post_processing_config.output_format == "JPEG" else ".png" - with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as tmp: - tmp_path = tmp.name - try: + tmp_path = self.processed_image_paths.get(image_path) + if tmp_path is None: + with open(image_path, "rb") as f: + raw_image = f.read() + # post_process_image handles resizing to target_pixel_size (set in execute()) + # which ensures the correct DPI for the DTC card dimensions + processed_image, icc_profile_bytes = post_process_image( + raw_image=raw_image, config=self.image_post_processing_config + ) + # Save to a temporary file so fpdf embeds the JPEG data directly + # (passing BytesIO causes fpdf to re-encode with FlateDecode, bloating file size). + # Cached per source path so a repeated card (e.g. the shared cardback) is processed + # once and fpdf's per-path image cache embeds its data once per PDF. Temp files are + # cleaned up at the end of execute(). + ext = ".jpg" if self.image_post_processing_config.output_format == "JPEG" else ".png" + with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as tmp: + tmp_path = tmp.name + self.processed_image_paths[image_path] = tmp_path save_processed_image( processed_image, file_path=tmp_path, config=self.image_post_processing_config, icc_profile_bytes=icc_profile_bytes, ) - self.pdf.image( - tmp_path, - x=0, - y=0, - w=self.card_width_in_inches, - h=self.card_height_in_inches, - ) - finally: - # Clean up temp file - if os.path.exists(tmp_path): - os.unlink(tmp_path) + self.pdf.image( + tmp_path, + x=0, + y=0, + w=self.card_width_in_inches, + h=self.card_height_in_inches, + ) else: with open(image_path, "rb") as f: image_bytes = f.read() @@ -352,11 +354,17 @@ def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) - post_processing_config.embed_dpi_metadata = True self.image_post_processing_config = post_processing_config self.download_and_collect_images(post_processing_config=post_processing_config) - if self.separate_faces: - self.number_of_cards_per_file = 1 - self.export_separate_faces() - else: - self.export() + try: + if self.separate_faces: + self.number_of_cards_per_file = 1 + self.export_separate_faces() + else: + self.export() + finally: + for tmp_path in self.processed_image_paths.values(): + if os.path.exists(tmp_path): + os.unlink(tmp_path) + self.processed_image_paths.clear() if self.pdfx_config: source_pdf_paths = list(self.saved_files) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 49e87abb7..844b55b1c 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -2327,6 +2327,76 @@ def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(m assert count_pdf_pages("export/test_local/1.pdf") == 4 +def test_pdf_export_drive_thru_cards_processes_and_embeds_repeated_images_once(monkeypatch, tmp_path): + monkeypatch.chdir(tmp_path) + + for image_name, color in [("front_a.png", "red"), ("front_b.png", "blue"), ("back.png", "black")]: + Image.new("RGB", (300, 420), color).save(tmp_path / image_name) + + process_calls: list[None] = [] + real_post_process_image = src.pdf_maker.post_process_image + + def counting_post_process_image(raw_image, config): + process_calls.append(None) + return real_post_process_image(raw_image=raw_image, config=config) + + monkeypatch.setattr("src.pdf_maker.post_process_image", counting_post_process_image) + + order = CardOrder.from_element( + working_directory=str(tmp_path), + element=ElementTree.fromstring( + textwrap.dedent( + f""" + +
+ 1 + (S30) Standard Smooth + false +
+ + + {tmp_path / "front_a.png"} + {SourceType.LOCAL_FILE} + 0 + front_a.png + + + {tmp_path / "front_b.png"} + {SourceType.LOCAL_FILE} + 1 + front_b.png + + + + {tmp_path / "back.png"} +
+ """ + ) + ), + allowed_to_exceed_project_max_size=True, + ) + order.name = "test_dedup.xml" + + exporter = PdfExporter(order=order, export_mode="drive_thru_cards") + exporter.execute( + post_processing_config=ImagePostProcessingConfig( + max_dpi=300, + downscale_alg=constants.ImageResizeMethods.LANCZOS, + output_format="JPEG", + convert_to_cmyk=False, + ) + ) + + # 4 pages (back, front_a, back, front_b) but only 3 unique images: the shared + # cardback must be post-processed once and its JPEG data embedded once. + assert count_pdf_pages("export/test_dedup/1.pdf") == 4 + assert len(process_calls) == 3 + with open("export/test_dedup/1.pdf", "rb") as f: + assert f.read().count(b"DCTDecode") == 3 + # temp files are cleaned up after execute() + assert exporter.processed_image_paths == {} + + # endregion # region test driver.py From 4c9e7a1c9889f16ccc2423399b2fdaeaaefb8bbc Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 04:22:30 -0700 Subject: [PATCH 55/76] Ignore per-platform CI build artifact names --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index dc49a7eaf..9dd4daf58 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ __pycache__/ **/autofill.bin autofill.command autofill.exe +autofill-macos-arm.command +autofill-macos-intel.command +autofill-windows.exe +autofill-linux.bin nuitka-crash-report.xml autofill_crash_log.txt From 783a36f83b82ebf920478bdc77c5f180028211e8 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 06:44:06 -0700 Subject: [PATCH 56/76] Remove assets README The placeholder cover is DriveThruCards' own file, provided by DTC for this purpose (the README wrongly described it as generated with Pillow). --- desktop-tool/assets/README.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 desktop-tool/assets/README.md diff --git a/desktop-tool/assets/README.md b/desktop-tool/assets/README.md deleted file mode 100644 index ace4fe4bc..000000000 --- a/desktop-tool/assets/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Assets - -- `placeholder_cover.png`: generated by this project with Pillow (solid background + text) as a - placeholder product cover for DriveThruCards listings. Licensed under this repository's license. From 92f37c6f525b15a575581099b77c7775eb1de415 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 07:04:47 -0700 Subject: [PATCH 57/76] Redesign DTC post-launch page as a step timeline in DTC colours --- desktop-tool/dtc-post-launch.html | 139 +++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 31 deletions(-) diff --git a/desktop-tool/dtc-post-launch.html b/desktop-tool/dtc-post-launch.html index 83a93a44d..6ac23a183 100644 --- a/desktop-tool/dtc-post-launch.html +++ b/desktop-tool/dtc-post-launch.html @@ -9,49 +9,126 @@ body { min-height: 100vh; margin: 0; - padding: 20px; + padding: 40px 24px; display: flex; align-items: center; justify-content: center; - color: #333; - background: #f7f9fc; - font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background: #f6efdc; + background-image: radial-gradient(#e7dcc0 1px, transparent 1px); + background-size: 22px 22px; + color: #3a3129; + font: 16px/1.6 "Avenir Next", Avenir, "Segoe UI", sans-serif; } - main { - width: 100%; - max-width: 520px; - padding: 32px; + main { width: 100%; max-width: 560px; } + .kicker { + display: inline-block; + font-size: 0.72rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: #8c2d1e; + font-weight: 700; + background: #f1e7c4; + padding: 4px 12px; + border-radius: 3px; + margin-bottom: 14px; + } + h1 { + margin: 0 0 30px; + font-size: 2.1rem; + line-height: 1.12; + font-weight: 700; + color: #2c241c; + } + ol.journey { list-style: none; margin: 0 0 34px; padding: 0; position: relative; } + ol.journey::before { + content: ""; + position: absolute; + left: 15px; + top: 14px; + bottom: 14px; + width: 2px; + background: #d9c9a3; + } + ol.journey li { position: relative; padding: 0 0 26px 52px; } + ol.journey li:last-child { padding-bottom: 0; } + .dot { + position: absolute; + left: 4px; + top: 2px; + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid #bf5b3a; + background: #f6efdc; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.72rem; + font-weight: 700; + color: #bf5b3a; } - h1 { margin: 0 0 28px; color: #1a73e8; font-size: 2.25rem; line-height: 1.15; } - p { margin: 0 0 16px; color: #5f6368; } - .warning { margin-bottom: 28px; padding: 12px 14px; border-left: 4px solid #d9a323; background: #fff7df; color: #55451b; } - .choices { margin-bottom: 28px; color: #202124; font-size: 1.1rem; line-height: 1.65; } - .status { margin-bottom: 20px; } - .skip-note { margin-top: 28px; color: #9aa0a6; font-style: italic; } - a { + li.here .dot { background: #2c241c; border-color: #2c241c; color: #f6efdc; } + .tag { display: inline-block; - margin-top: 8px; - padding: 10px 16px; - border-radius: 2px; - color: white; - background: #1a73e8; - font-weight: 600; + font-size: 0.66rem; + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; + padding: 2px 8px; + border-radius: 3px; + margin-bottom: 6px; + } + li.here .tag { background: #2c241c; color: #f6efdc; } + li.next .tag { background: #bf5b3a; color: #fff; } + li.auto .tag { background: #dde5cf; color: #4a6234; } + .step-title { font-weight: 600; color: #2c241c; margin: 0 0 4px; } + .step-body { margin: 0; color: #6f6353; font-size: 0.95rem; } + .step-body strong { color: #8c2d1e; } + a.cta { + display: block; + text-align: center; + padding: 15px 20px; + background: #bf5b3a; + color: #fff; text-decoration: none; + font-weight: 700; + font-size: 1.05rem; + border-radius: 6px; + box-shadow: 0 3px 0 #8c3a1e; + transition: transform 0.12s ease, box-shadow 0.12s ease; } - a:focus-visible { outline: 3px solid #174ea6; outline-offset: 3px; } + a.cta:hover { transform: translateY(-2px); box-shadow: 0 5px 0 #8c3a1e; } + a.cta:focus-visible { outline: 3px solid #2c241c; outline-offset: 3px; } + .skip { margin-top: 22px; text-align: center; color: #a3937a; font-size: 0.8rem; } + .skip code { background: #ede3c6; padding: 1px 6px; border-radius: 3px; }
-

Sign in to DriveThruCards

-

Before continuing: Turn off any ad blockers; they may disrupt the login process.

-

- Next, sign in to DriveThruCards.
- No account? Click Get Started Here! on the next page to create one or sign in with Google or Facebook. -

-

MPC Autofill will continue automatically when you've logged in.

-
Continue to DriveThruCards -

To skip this page next time, use --skip-dtc-instructions.

+

MPC Autofill · DriveThruCards

+

Sign in on the next page

+
    +
  1. + 1 + This page — you are here +

    Turn off ad blockers

    +

    They can break the DriveThruCards login. Do this before continuing.

    +
  2. + +
  3. + 3 + Automatic +

    MPC Autofill resumes

    +

    Once you're logged in, the tool detects it and continues on its own.

    +
  4. +
+ Continue to DriveThruCards → +

Skip this page next time with --skip-dtc-instructions

From ee43ccc49119f83504af05b298c45a5d19441b5b Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Tue, 21 Jul 2026 08:46:47 -0700 Subject: [PATCH 58/76] Fix and validate Windows desktop builds --- .github/workflows/build-desktop-tool.yml | 62 ++++++++++++++++++++++++ .gitignore | 1 + desktop-tool/autofill.py | 10 +--- desktop-tool/src/webdrivers.py | 4 +- desktop-tool/tests/test_desktop_tool.py | 16 +++--- 5 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/build-desktop-tool.yml diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml new file mode 100644 index 000000000..e42952b26 --- /dev/null +++ b/.github/workflows/build-desktop-tool.yml @@ -0,0 +1,62 @@ +name: Desktop tool Windows CI + +on: + workflow_dispatch: + +jobs: + test: + name: Test on Windows + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/test-desktop-tool + with: + google-drive-api-key: ${{ secrets.GOOGLE_DRIVE_API_KEY }} + + build: + name: Build on Windows + needs: test + runs-on: windows-latest + defaults: + run: + working-directory: desktop-tool + steps: + - uses: actions/checkout@v6 + - name: Set up Python 3.13 + uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + python -m pip install imageio + - name: Require Google Drive API credentials + shell: pwsh + env: + GOOGLE_DRIVE_API_KEY: ${{ secrets.GOOGLE_DRIVE_API_KEY }} + run: | + if (-not $env:GOOGLE_DRIVE_API_KEY) { + throw "Add desktop-tool/client_secrets.json as the GOOGLE_DRIVE_API_KEY repository secret." + } + - name: Write Google Drive API credentials to file + uses: jsdaniell/create-json@v1.2.3 + with: + name: client_secrets.json + json: ${{ secrets.GOOGLE_DRIVE_API_KEY }} + dir: desktop-tool/ + - name: Build with Nuitka + uses: Nuitka/Nuitka-Action@main + with: + nuitka-version: main + working-directory: desktop-tool + script-name: autofill.py + mode: onefile + output-file: autofill-windows.exe + - name: Smoke test bundled executable + run: .\build\autofill-windows.exe --help + - name: Upload unsigned artifact + uses: actions/upload-artifact@v7 + with: + name: windows Unsigned Build + path: desktop-tool/build/autofill-windows.exe diff --git a/.gitignore b/.gitignore index 9dd4daf58..f11e620d2 100644 --- a/.gitignore +++ b/.gitignore @@ -130,6 +130,7 @@ celerybeat.pid # Environments .env +.login .venv env/ venv/ diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 02452475b..157c7f96c 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -448,9 +448,9 @@ def main( os.chdir(working_directory) create_image_directory_if_not_exists(working_directory=working_directory) - if binary_location and not os.path.isdir(binary_location): + if binary_location and not os.path.isfile(binary_location): raise Exception( - f"Binary location was specified but is not a directory (or it doesn't exist): {bold(binary_location)}" + f"Binary location was specified but is not a file (or it doesn't exist): {bold(binary_location)}" ) if browser_profile_path and not os.path.isdir(browser_profile_path): raise Exception( @@ -577,10 +577,4 @@ def main( if __name__ == "__main__": - click.echo( - "▙▗▌▛▀▖▞▀▖ ▞▀▖ ▐ ▗▀▖▗▜▜ \n" - "▌▘▌▙▄▘▌ ▙▄▌▌ ▌▜▀ ▞▀▖▐ ▄▐▐ \n" - "▌ ▌▌ ▌ ▖ ▌ ▌▌ ▌▐ ▖▌ ▌▜▀ ▐▐▐ \n" - "▘ ▘▘ ▝▀ ▘ ▘▝▀▘ ▀ ▝▀ ▐ ▀▘▘▘\n" - ) main() diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 2bd754206..3e295eff3 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -4,12 +4,14 @@ from typing import Optional import undetected_chromedriver as uc -from selenium.webdriver import Chrome, Edge, Firefox from selenium.webdriver.chrome.options import Options as ChromeOptions +from selenium.webdriver.chrome.webdriver import WebDriver as Chrome from selenium.webdriver.chromium.options import ChromiumOptions from selenium.webdriver.chromium.webdriver import ChromiumDriver from selenium.webdriver.edge.options import Options as EdgeOptions +from selenium.webdriver.edge.webdriver import WebDriver as Edge from selenium.webdriver.firefox.options import Options as FirefoxOptions +from selenium.webdriver.firefox.webdriver import WebDriver as Firefox def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 844b55b1c..b80f32759 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -463,6 +463,8 @@ def test_main_drive_thru_cards_exportpdf_generates_pdfs_without_browser_automati ) -> None: icc_path = tmp_path / "test.icc" icc_path.write_bytes(b"icc") + browser_path = tmp_path / "chrome.exe" + browser_path.touch() calls = {"pdf": [], "wait": 0, "driver": 0} @@ -501,6 +503,8 @@ def __init__(self, *args, **kwargs) -> None: "--exportpdf", "--browser", constants.Browsers.chrome.name, + "--binary-location", + str(browser_path), ], ) @@ -809,12 +813,12 @@ def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> boo generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) expected_pdfx_files = [ - "export/test_order/1_pdfx.pdf", - "export/test_order/2_pdfx.pdf", - "export/test_order/3_pdfx.pdf", + Path("export/test_order/1_pdfx.pdf"), + Path("export/test_order/2_pdfx.pdf"), + Path("export/test_order/3_pdfx.pdf"), ] for file_path in expected_pdfx_files: - assert file_path in generated_files + assert file_path in map(Path, generated_files) assert os.path.exists(file_path) remove_files([path for path in generated_files if path.endswith(".pdf")]) @@ -870,7 +874,7 @@ def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> boo progress_logs = [message for message in logged_messages if message.startswith("Converting PDF to PDF/X-1a")] assert len(progress_logs) == 3 - assert "Converting PDF to PDF/X-1a (1/3): export/test_order/1.pdf" in progress_logs + assert Path(progress_logs[0].rsplit(": ", 1)[1]) == Path("export/test_order/1.pdf") remove_files([path for path in generated_files if path.endswith(".pdf")]) remove_directories(["export/test_order", "export"]) @@ -2322,7 +2326,7 @@ def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(m ) ) - assert generated_files == ["export/test_local/1.pdf"] + assert list(map(Path, generated_files)) == [Path("export/test_local/1.pdf")] assert os.path.exists("export/test_local/1.pdf") assert count_pdf_pages("export/test_local/1.pdf") == 4 From 789092e4f1a61f56db58f205fca4fbbe1ce871a3 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 09:55:02 -0700 Subject: [PATCH 59/76] Validate macOS and Linux desktop builds --- .github/workflows/build-desktop-tool.yml | 49 +++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index e42952b26..e43fbd8bb 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -1,12 +1,17 @@ -name: Desktop tool Windows CI +name: Desktop tool macOS and Linux CI on: workflow_dispatch: jobs: test: - name: Test on Windows - runs-on: windows-latest + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - macos-latest + - ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/test-desktop-tool @@ -14,12 +19,27 @@ jobs: google-drive-api-key: ${{ secrets.GOOGLE_DRIVE_API_KEY }} build: - name: Build on Windows + name: Build ${{ matrix.target }} needs: test - runs-on: windows-latest + runs-on: ${{ matrix.os }} defaults: run: working-directory: desktop-tool + strategy: + matrix: + include: + - os: macos-latest + target: macos-arm + filename: autofill-macos-arm.command + macos-arch: arm64 + - os: macos-15-intel + target: macos-intel + filename: autofill-macos-intel.command + macos-arch: x86_64 + - os: ubuntu-latest + target: linux + filename: autofill-linux.bin + macos-arch: "" steps: - uses: actions/checkout@v6 - name: Set up Python 3.13 @@ -32,13 +52,9 @@ jobs: pip install -r requirements.txt python -m pip install imageio - name: Require Google Drive API credentials - shell: pwsh env: GOOGLE_DRIVE_API_KEY: ${{ secrets.GOOGLE_DRIVE_API_KEY }} - run: | - if (-not $env:GOOGLE_DRIVE_API_KEY) { - throw "Add desktop-tool/client_secrets.json as the GOOGLE_DRIVE_API_KEY repository secret." - } + run: test -n "$GOOGLE_DRIVE_API_KEY" || (echo "Add desktop-tool/client_secrets.json as the GOOGLE_DRIVE_API_KEY repository secret." && exit 1) - name: Write Google Drive API credentials to file uses: jsdaniell/create-json@v1.2.3 with: @@ -52,11 +68,16 @@ jobs: working-directory: desktop-tool script-name: autofill.py mode: onefile - output-file: autofill-windows.exe + output-file: ${{ matrix.filename }} + macos-target-arch: ${{ matrix.macos-arch }} - name: Smoke test bundled executable - run: .\build\autofill-windows.exe --help + run: | + help_output=$(./build/${{ matrix.filename }} --help) + grep -q MakePlayingCards <<< "$help_output" + grep -q DriveThruCards <<< "$help_output" + stat -f "%N: %z bytes" build/${{ matrix.filename }} 2>/dev/null || stat -c "%n: %s bytes" build/${{ matrix.filename }} - name: Upload unsigned artifact uses: actions/upload-artifact@v7 with: - name: windows Unsigned Build - path: desktop-tool/build/autofill-windows.exe + name: ${{ matrix.target }} Unsigned Build + path: desktop-tool/build/${{ matrix.filename }} From 234eb2160326e8b579a6e8963efb0430a3ccef10 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 09:58:05 -0700 Subject: [PATCH 60/76] Run Linux desktop checks with D-Bus --- .github/actions/test-desktop-tool/action.yml | 7 ++++++- .github/workflows/build-desktop-tool.yml | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/actions/test-desktop-tool/action.yml b/.github/actions/test-desktop-tool/action.yml index 04d43a26b..ccb1396ee 100644 --- a/.github/actions/test-desktop-tool/action.yml +++ b/.github/actions/test-desktop-tool/action.yml @@ -39,5 +39,10 @@ runs: dir: "desktop-tool/" - name: Run tests working-directory: desktop-tool - run: pytest . + run: | + if [[ "$RUNNER_OS" == Linux ]]; then + dbus-run-session -- pytest . + else + pytest . + fi shell: bash diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index e43fbd8bb..65c6103d7 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -72,7 +72,11 @@ jobs: macos-target-arch: ${{ matrix.macos-arch }} - name: Smoke test bundled executable run: | - help_output=$(./build/${{ matrix.filename }} --help) + if [[ "$RUNNER_OS" == Linux ]]; then + help_output=$(dbus-run-session -- ./build/${{ matrix.filename }} --help) + else + help_output=$(./build/${{ matrix.filename }} --help) + fi grep -q MakePlayingCards <<< "$help_output" grep -q DriveThruCards <<< "$help_output" stat -f "%N: %z bytes" build/${{ matrix.filename }} 2>/dev/null || stat -c "%n: %s bytes" build/${{ matrix.filename }} From ac7fdcb00d72500772c3ffbca8b597fc5da9c65c Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 14:05:12 -0700 Subject: [PATCH 61/76] feat(desktop-tool): improve startup UX and reliability - Interactive onboarding prompts (InquirerPy) with DTC question skipping - Lazy imports and Nuitka cached onefile extraction for faster startup - Bundled certifi TLS fix with --check-tls CI smoke tests - Image download failure gating (ImageDownloadError) - PDF progress bar fix --- .github/workflows/build-desktop-tool.yml | 1 + .github/workflows/desktop-tool-ci.yml | 3 + desktop-tool/autofill.py | 191 +++++++++++++++++------ desktop-tool/requirements.txt | 1 + desktop-tool/src/exc.py | 16 ++ desktop-tool/src/order.py | 5 +- desktop-tool/src/pdf_maker.py | 14 +- desktop-tool/src/webdrivers.py | 40 +++-- desktop-tool/tests/test_desktop_tool.py | 175 ++++++++++++++++++--- 9 files changed, 363 insertions(+), 83 deletions(-) diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index 65c6103d7..20bd758f0 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -72,6 +72,7 @@ jobs: macos-target-arch: ${{ matrix.macos-arch }} - name: Smoke test bundled executable run: | + env -u SSL_CERT_FILE -u SSL_CERT_DIR ./build/${{ matrix.filename }} --check-tls if [[ "$RUNNER_OS" == Linux ]]; then help_output=$(dbus-run-session -- ./build/${{ matrix.filename }} --help) else diff --git a/.github/workflows/desktop-tool-ci.yml b/.github/workflows/desktop-tool-ci.yml index 3d15f83b1..674210f23 100644 --- a/.github/workflows/desktop-tool-ci.yml +++ b/.github/workflows/desktop-tool-ci.yml @@ -120,6 +120,9 @@ jobs: macos-sign-keyring-password: ${{ secrets.KEYCHAIN_PASSWORD }} macos-signed-app-name: com.ndepaola.mpc-autofill-${{ matrix.TARGET }} macos-target-arch: ${{ matrix.MACOS_ARCH }} + - name: Smoke test bundled TLS + shell: bash + run: env -u SSL_CERT_FILE -u SSL_CERT_DIR ./build/${{ matrix.FILENAME }} --check-tls - name: Upload unsigned artifact id: upload-unsigned-artifact uses: actions/upload-artifact@v7 diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 157c7f96c..4b29e19b4 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -1,8 +1,10 @@ # nuitka-project: --mode=onefile +# nuitka-project: --onefile-tempdir-spec={CACHE_DIR}/mpc-autofill # nuitka-project: --include-data-files=client_secrets.json=client_secrets.json # nuitka-project: --include-data-files=post-launch.html=post-launch.html # nuitka-project: --include-data-files=dtc-post-launch.html=dtc-post-launch.html # nuitka-project: --include-data-dir=assets=assets +# nuitka-project: --include-package-data=certifi # nuitka-project: --noinclude-pytest-mode=nofollow # nuitka-project: --windows-icon-from-ico=favicon.ico # nuitka-project-if: {OS} == "Windows": @@ -16,59 +18,118 @@ # nuitka-project: --noinclude-data-files=selenium/webdriver/common/windows/selenium-manager.exe # nuitka-project: --noinclude-data-files=selenium/webdriver/common/macos/selenium-manager +from __future__ import annotations import logging import os import shutil import subprocess import sys -from concurrent.futures import ThreadPoolExecutor from contextlib import nullcontext from glob import glob -from typing import Optional, Union +from typing import TYPE_CHECKING, Optional +import certifi import click -import enlighten -from wakepy import keepawake +from InquirerPy import inquirer from src.constants import ( DTC_POST_LAUNCH_HTML_FILENAME, - THREADS, Browsers, ImageResizeMethods, TargetSites, ) -from src.driver import AutofillDriver -from src.exc import ValidationException from src.formatting import bold -from src.icc import find_or_download_dtc_icc_profile -from src.io import ( - DEFAULT_WORKING_DIRECTORY, - create_image_directory_if_not_exists, - get_image_directory, -) -from src.logging import configure_loggers, logger -from src.order import CardOrder, aggregate_and_split_orders -from src.pdf_maker import ( - PdfExporter, - PdfXConversionConfig, - get_export_directory, - get_ghostscript_path, - get_ghostscript_version, -) -from src.processing import ImagePostProcessingConfig -from src.web_server import WebServer +from src.logging import logger + +if TYPE_CHECKING: + from src.order import CardOrder + from src.processing import ImagePostProcessingConfig + + +def configure_tls() -> str: + """Use the bundled CA bundle unless the user supplied one explicitly.""" + + return os.environ.setdefault("SSL_CERT_FILE", certifi.where()) + + +configure_tls() # https://stackoverflow.com/questions/12492810/python-how-can-i-make-the-ansi-escape-codes-to-work-also-in-windows -os.system("") # enables ansi escape characters in terminal +if sys.platform == "win32": + os.system("") # enables ansi escape characters in the Windows terminal +DEFAULT_BROWSER = Browsers.chrome.name +DEFAULT_SITE = TargetSites.MakePlayingCards.name +DEFAULT_AUTO_SAVE = True +DEFAULT_IMAGE_POST_PROCESSING = True +TLS_CHECK_URL = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json" -def prompt_if_no_arguments(prompt: str) -> Union[str, bool]: - """ - We only prompt users to specify some flags if the tool was executed with no command-line arguments. - """ - return f"{prompt} (Press Enter if you're not sure.)" if len(sys.argv) == 1 else False +def get_browser_picker_choices() -> list[str]: + return sorted(browser.name for browser in Browsers) + + +def get_site_picker_choices() -> list[str]: + return [site.name for site in TargetSites] + + +def should_run_interactive_onboarding() -> bool: + return len(sys.argv) == 1 and sys.stdin.isatty() and sys.stdout.isatty() + + +def run_interactive_onboarding() -> tuple[str, str, bool, bool]: + browser = inquirer.rawlist( + message="Which web browser should the tool run on? (Press Enter if you're not sure.)", + choices=get_browser_picker_choices(), + default=DEFAULT_BROWSER, + ).execute() + site = inquirer.rawlist( + message="Which site should the tool auto-fill your project into? (Press Enter if you're not sure.)", + choices=get_site_picker_choices(), + default=DEFAULT_SITE, + ).execute() + + if site == TargetSites.DriveThruCards.name: + return browser, site, True, False + + auto_save = inquirer.rawlist( + message=( + "Automatically save this project to your account while the tool is running? " + "(Press Enter if you're not sure.)" + ), + choices=[{"name": "Yes", "value": True}, {"name": "No", "value": False}], + default=DEFAULT_AUTO_SAVE, + ).execute() + image_post_processing = inquirer.rawlist( + message=( + "Should the tool post-process your images to reduce upload times? By default, images will be " + "downscaled to 800 DPI. (Press Enter if you're not sure.)" + ), + choices=[{"name": "Yes", "value": True}, {"name": "No", "value": False}], + default=DEFAULT_IMAGE_POST_PROCESSING, + ).execute() + return browser, site, auto_save, image_post_processing + + +def check_tls_connection() -> None: + from urllib.request import urlopen + + with urlopen(TLS_CHECK_URL, timeout=30) as response: + if response.status != 200: + raise click.ClickException(f"TLS check returned HTTP {response.status}.") + + +def get_ghostscript_path(path: Optional[str] = None) -> Optional[str]: + from src.pdf_maker import get_ghostscript_path as resolve_path + + return resolve_path(path) + + +def get_ghostscript_version(path: str) -> Optional[str]: + from src.pdf_maker import get_ghostscript_version as resolve_version + + return resolve_version(path) def wait_for_user_to_complete_order() -> None: @@ -152,6 +213,8 @@ def ensure_ghostscript_available() -> str: def get_existing_pdf_paths(order_name: Optional[str]) -> list[str]: + from src.pdf_maker import get_export_directory + export_directory = get_export_directory(order_name=order_name) return sorted([path for path in glob(os.path.join(export_directory, "**", "*.pdf"), recursive=True)]) @@ -217,6 +280,12 @@ def download_images_for_orders( orders: list[CardOrder], post_processing_config: Optional[ImagePostProcessingConfig], ) -> None: + from concurrent.futures import ThreadPoolExecutor + + import enlighten + + from src.constants import THREADS + total_images = sum(len(order.fronts.cards_by_id) + len(order.backs.cards_by_id) for order in orders) manager = enlighten.get_manager() download_bar = manager.counter(total=total_images, desc="Images Downloaded", position=1, autorefresh=True) @@ -235,6 +304,10 @@ def get_dtc_pdf_paths_for_order( resolved_icc_profile: Optional[str], downscale_alg: str, ) -> list[str]: + from src.io import get_image_directory + from src.pdf_maker import PdfExporter, PdfXConversionConfig + from src.processing import ImagePostProcessingConfig + pdf_paths = maybe_reuse_existing_pdfs( order_name=order.name, skip_pdf_if_exists=skip_pdf_if_exists, @@ -263,9 +336,8 @@ def get_dtc_pdf_paths_for_order( @click.option( "-b", "--browser", - prompt=prompt_if_no_arguments("Which web browser should the tool run on?"), - default=Browsers.chrome.name, - type=click.Choice(sorted([browser.name for browser in Browsers]), case_sensitive=False), + default=DEFAULT_BROWSER, + type=click.Choice(get_browser_picker_choices(), case_sensitive=False), help="The web browser to run the tool on.", ) @click.option( @@ -298,16 +370,13 @@ def get_dtc_pdf_paths_for_order( ) @click.option( "--site", - prompt=prompt_if_no_arguments("Which site should the tool auto-fill your project into?"), - default=TargetSites.MakePlayingCards.name, - # enum order intentionally lists DriveThruCards last in the picker - type=click.Choice([site.name for site in TargetSites], case_sensitive=False), + default=DEFAULT_SITE, + type=click.Choice(get_site_picker_choices(), case_sensitive=False), help="The card printing site into which your order should be auto-filled.", ) @click.option( "--auto-save/--no-auto-save", - prompt=prompt_if_no_arguments("Automatically save this project to your account while the tool is running?"), - default=True, + default=DEFAULT_AUTO_SAVE, help=( "If this flag is passed, the tool will automatically save your project to your account after " "processing each batch of cards." @@ -346,11 +415,7 @@ def get_dtc_pdf_paths_for_order( ) @click.option( "--image-post-processing/--no-image-post-processing", - default=True, - prompt=prompt_if_no_arguments( - "Should the tool post-process your images to reduce upload times? " - "By default, images will be downscaled to 800 DPI." - ), + default=DEFAULT_IMAGE_POST_PROCESSING, help="Post-process images to reduce file upload time.", is_flag=True, ) @@ -407,6 +472,7 @@ def get_dtc_pdf_paths_for_order( help="If True, debug logs about the tool's actions will be logged to autofill_log.txt in the tool's directory.", is_flag=True, ) +@click.option("--check-tls", is_flag=True, hidden=True) # @click.option( # TODO: finish implementing jpeg conversion # "--convert-to-jpeg", # default=True, @@ -435,8 +501,33 @@ def main( combine_orders: bool, log_level: str, write_debug_logs: bool, + check_tls: bool, # convert_to_jpeg: bool, ) -> None: + if check_tls: + check_tls_connection() + click.echo("TLS check succeeded.") + return + + if should_run_interactive_onboarding(): + browser, site, auto_save, image_post_processing = run_interactive_onboarding() + + if TargetSites[site] == TargetSites.DriveThruCards: + auto_save = True + image_post_processing = False + + from wakepy import keepawake + + from src.exc import ImageDownloadError, ValidationException + from src.io import ( + DEFAULT_WORKING_DIRECTORY, + create_image_directory_if_not_exists, + get_image_directory, + ) + from src.logging import configure_loggers + from src.order import CardOrder, aggregate_and_split_orders + from src.processing import ImagePostProcessingConfig + working_directory: str = DEFAULT_WORKING_DIRECTORY if directory: if not os.path.isdir(directory): @@ -481,6 +572,10 @@ def main( download_images_for_orders(orders=orders, post_processing_config=post_processing_config) return if target_site == TargetSites.DriveThruCards: + from src.driver import AutofillDriver + from src.icc import find_or_download_dtc_icc_profile + from src.web_server import WebServer + ensure_ghostscript_available() if dtc_icc_profile and not os.path.isfile(dtc_icc_profile): raise Exception( @@ -536,6 +631,8 @@ def main( if dtc_driver is not None: wait_for_user_to_complete_order() elif exportpdf: + from src.pdf_maker import PdfExporter + order = CardOrder.from_xmls_in_folder(working_directory=working_directory)[0] if ( maybe_reuse_existing_pdfs( @@ -547,6 +644,9 @@ def main( ): PdfExporter(order=order).execute(post_processing_config=post_processing_config) else: + from src.driver import AutofillDriver + from src.web_server import WebServer + card_orders = aggregate_and_split_orders( orders=CardOrder.from_xmls_in_folder(working_directory=working_directory), target_site=target_site, @@ -567,6 +667,9 @@ def main( post_processing_config=post_processing_config, ) wait_for_user_to_complete_order() + except ImageDownloadError as e: + logger.error(str(e)) + input(f"{e}\n\nPress Enter to exit.") except ValidationException as e: input(f"There was a problem with your order file:\n\n{bold(e)}\n\nPress Enter to exit.") sys.exit(0) diff --git a/desktop-tool/requirements.txt b/desktop-tool/requirements.txt index a5b08a836..a51b18961 100644 --- a/desktop-tool/requirements.txt +++ b/desktop-tool/requirements.txt @@ -1,4 +1,5 @@ attrs~=25.3.0 +certifi click==8.1.8 colorama~=0.4.6 coverage~=7.2.7 diff --git a/desktop-tool/src/exc.py b/desktop-tool/src/exc.py index ba2ca1d22..491b0e428 100644 --- a/desktop-tool/src/exc.py +++ b/desktop-tool/src/exc.py @@ -13,3 +13,19 @@ def __init__(self, state: str, expected_state: str): class ValidationException(Exception): pass + + +class ImageDownloadError(Exception): + def __init__(self, failed_images: list[tuple[str, str]]) -> None: + failed_list = "\n".join( + f"- {name or 'Unknown image'}" + (f" (Drive ID: {drive_id})" if drive_id else "") + for name, drive_id in failed_images + ) + super().__init__( + "Some card images could not be downloaded, so PDF creation has stopped.\n" + f"{failed_list}\n\n" + "This usually means the saved XML refers to an image that was removed or replaced after the order " + "was created. Import this XML into a new MPC Fill project to identify the unmatched cards and choose " + "replacements, then download a new XML and try again. Technical details were saved to " + "autofill_crash_log.txt." + ) diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index 2cde8a952..024b1d591 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -230,15 +230,16 @@ def download_image( if self.file_exists() and not self.errored: self.downloaded = True else: - logger.info( + logger.error( f"Failed to download '{bold(self.name)}' - allocated to slot/s {bold(sorted(self.slots))}.\n" f"Download link - {bold(f'https://drive.google.com/uc?id={self.drive_id}&export=download')}\n" ) except Exception as e: # note: python threads die silently if they encounter an exception. if an exception does occur, # log it, but still put the card onto the queue so the main thread doesn't spin its wheels forever waiting. - logger.info( + logger.exception( f"An uncaught exception occurred when attempting to download '{bold(self.name)}':\n{bold(e)}\n" + f"Allocated to slot/s {bold(sorted(self.slots))}.\n" f"Download link - {bold(f'https://drive.google.com/uc?id={self.drive_id}&export=download')}\n" ) finally: diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 3c8a3f38e..83a07b9d7 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -13,6 +13,7 @@ from fpdf import FPDF from src.constants import THREADS, States +from src.exc import ImageDownloadError from src.formatting import bold from src.logging import logger from src.order import CardOrder @@ -201,6 +202,7 @@ class PdfExporter: def configure_bars(self) -> None: num_images = len(self.order.fronts.cards_by_id) + len(self.order.backs.cards_by_id) + num_cards = len(self.order.fronts.slots()) status_format = "State: {state}" self.status_bar = self.manager.status_bar( status_format=status_format, @@ -208,7 +210,7 @@ def configure_bars(self) -> None: position=1, ) self.download_bar = self.manager.counter(total=num_images, desc="Images Downloaded", position=2) - self.processed_bar = self.manager.counter(total=num_images, desc="Images Processed", position=3) + self.processed_bar = self.manager.counter(total=num_cards, desc="Cards Added to PDF", position=3) self.download_bar.refresh() self.processed_bar.refresh() @@ -331,6 +333,15 @@ def download_and_collect_images(self, post_processing_config: Optional[ImagePost self.order.fronts.download_images(pool, self.download_bar, download_config) self.order.backs.download_images(pool, self.download_bar, download_config) + failed_images = { + (card.name or "Unknown image", card.drive_id) + for collection in (self.order.fronts, self.order.backs) + for card in collection.cards_by_id.values() + if not card.downloaded + } + if failed_images: + raise ImageDownloadError(sorted(failed_images)) + backs_by_slots = {} for card in self.order.backs.cards_by_id.values(): for slot in card.slots: @@ -414,3 +425,4 @@ def export_separate_faces(self) -> None: self.save_file() if face_index == 1: self.file_num = self.file_num + 1 + self.processed_bar.update() diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index 3e295eff3..aeddbf4db 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -1,20 +1,20 @@ import re import subprocess import sys -from typing import Optional +from typing import TYPE_CHECKING, Any, Optional -import undetected_chromedriver as uc -from selenium.webdriver.chrome.options import Options as ChromeOptions -from selenium.webdriver.chrome.webdriver import WebDriver as Chrome -from selenium.webdriver.chromium.options import ChromiumOptions -from selenium.webdriver.chromium.webdriver import ChromiumDriver -from selenium.webdriver.edge.options import Options as EdgeOptions -from selenium.webdriver.edge.webdriver import WebDriver as Edge -from selenium.webdriver.firefox.options import Options as FirefoxOptions -from selenium.webdriver.firefox.webdriver import WebDriver as Firefox +if TYPE_CHECKING: + from selenium.webdriver.chrome.webdriver import WebDriver as Chrome + from selenium.webdriver.chromium.webdriver import ChromiumDriver + from selenium.webdriver.firefox.webdriver import WebDriver as Firefox +else: + Chrome = ChromiumDriver = Firefox = Any def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: + from selenium.webdriver.chrome.options import Options as ChromeOptions + from selenium.webdriver.chrome.webdriver import WebDriver as ChromeDriver + options = ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") @@ -25,12 +25,15 @@ def get_chrome_driver(headless: bool = False, binary_location: Optional[str] = N options.add_experimental_option("detach", True) if binary_location is not None: options.binary_location = binary_location - driver = Chrome(options=options) + driver = ChromeDriver(options=options) driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) return driver def get_brave_driver(headless: bool = False, binary_location: Optional[str] = None) -> Chrome: + from selenium.webdriver.chrome.options import Options as ChromeOptions + from selenium.webdriver.chrome.webdriver import WebDriver as ChromeDriver + options = ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") @@ -43,12 +46,16 @@ def get_brave_driver(headless: bool = False, binary_location: Optional[str] = No # the binary location for brave must be manually specified (otherwise chrome will open instead) options.binary_location = binary_location or get_default_brave_binary_location() - driver = Chrome(options=options) + driver = ChromeDriver(options=options) driver.set_network_conditions(offline=False, latency=5, throughput=5 * 125000) return driver def get_edge_driver(headless: bool = False, binary_location: Optional[str] = None) -> ChromiumDriver: + from selenium.webdriver.chromium.options import ChromiumOptions + from selenium.webdriver.edge.options import Options as EdgeOptions + from selenium.webdriver.edge.webdriver import WebDriver as Edge + options: ChromiumOptions = EdgeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") @@ -66,13 +73,16 @@ def get_edge_driver(headless: bool = False, binary_location: Optional[str] = Non # note: firefox is not currently supported def get_firefox_driver(headless: bool = False, binary_location: Optional[str] = None) -> Firefox: + from selenium.webdriver.firefox.options import Options as FirefoxOptions + from selenium.webdriver.firefox.webdriver import WebDriver as FirefoxDriver + options = FirefoxOptions() options.add_argument("--log-level=3") if headless: options.add_argument("--headless") if binary_location is not None: options.binary_location = binary_location - driver = Firefox(options=options) + driver = FirefoxDriver(options=options) return driver @@ -129,11 +139,13 @@ def get_undetected_chrome_driver( binary_location: Optional[str] = None, user_data_dir: Optional[str] = None, profile_directory: Optional[str] = None, -) -> uc.Chrome: +) -> Chrome: """ Create a Chrome driver using undetected-chromedriver, for sites (DriveThruCards) whose bot detection blocks standard Selenium. Only used when targeting DriveThruCards. """ + import undetected_chromedriver as uc + options = uc.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index b80f32759..8933f5dc0 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -1,7 +1,10 @@ import gc import inspect +import logging import os import re +import subprocess +import sys import textwrap import time from concurrent.futures import ThreadPoolExecutor @@ -27,7 +30,7 @@ import src.webdrivers as webdrivers from src.constants import OrderFulfilmentMethod, SourceType from src.driver import AutofillDriver -from src.exc import ValidationException +from src.exc import ImageDownloadError, ValidationException from src.formatting import text_to_set from src.io import get_google_drive_file_name, remove_directories, remove_files from src.order import ( @@ -37,7 +40,12 @@ Details, aggregate_and_split_orders, ) -from src.pdf_maker import PdfExporter, convert_pdf_to_pdfx, get_ghostscript_version +from src.pdf_maker import ( + PdfExporter, + PdfXConversionConfig, + convert_pdf_to_pdfx, + get_ghostscript_version, +) from src.processing import ImagePostProcessingConfig requires_google_drive_credentials = pytest.mark.skipif( @@ -408,7 +416,7 @@ def fake_chrome(*, options, version_main): return object() monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) - monkeypatch.setattr(webdrivers.uc, "Chrome", fake_chrome) + monkeypatch.setattr("undetected_chromedriver.Chrome", fake_chrome) webdrivers.get_undetected_chrome_driver( user_data_dir="/tmp/chrome-data", @@ -451,6 +459,80 @@ def test_cli_help_documents_new_flags() -> None: assert "detailed Selenium step-by-step logs" in result.output +def test_configure_tls_uses_bundled_certificates_without_overwriting_user_value( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + monkeypatch.delenv("SSL_CERT_FILE", raising=False) + assert autofill_cli.configure_tls() == autofill_cli.certifi.where() + assert os.path.isfile(os.environ["SSL_CERT_FILE"]) + + custom_bundle = tmp_path / "company-ca.pem" + custom_bundle.touch() + monkeypatch.setenv("SSL_CERT_FILE", str(custom_bundle)) + assert autofill_cli.configure_tls() == str(custom_bundle) + + +def test_startup_defers_heavy_runtime_imports() -> None: + result = subprocess.run( + [ + sys.executable, + "-c", + ( + "import sys, autofill; " + "heavy=('undetected_chromedriver','fpdf','selenium','googleapiclient','wakepy'); " + "assert not [name for name in heavy if any(m == name or m.startswith(name + '.') for m in sys.modules)]" + ), + ], + cwd=Path(autofill_cli.__file__).parent, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + + +def test_should_run_interactive_onboarding_only_for_no_argument_tty(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(autofill_cli.sys, "argv", ["autofill.py"]) + monkeypatch.setattr(autofill_cli.sys.stdin, "isatty", lambda: True) + monkeypatch.setattr(autofill_cli.sys.stdout, "isatty", lambda: True) + assert autofill_cli.should_run_interactive_onboarding() + + monkeypatch.setattr(autofill_cli.sys, "argv", ["autofill.py", "--site", "MakePlayingCards"]) + assert not autofill_cli.should_run_interactive_onboarding() + + +@pytest.mark.parametrize( + ("site", "responses", "expected", "prompt_count"), + [ + ( + "MakePlayingCards", + ["chrome", "MakePlayingCards", False, True], + ("chrome", "MakePlayingCards", False, True), + 4, + ), + ("DriveThruCards", ["chrome", "DriveThruCards"], ("chrome", "DriveThruCards", True, False), 2), + ], +) +def test_interactive_onboarding_uses_picker_and_skips_dtc_only_questions( + monkeypatch: pytest.MonkeyPatch, site: str, responses: list[object], expected: tuple, prompt_count: int +) -> None: + prompts = [] + answers = iter(responses) + + class Prompt: + def execute(self): + return next(answers) + + def fake_rawlist(**kwargs): + prompts.append(kwargs) + return Prompt() + + monkeypatch.setattr(autofill_cli.inquirer, "rawlist", fake_rawlist) + + assert autofill_cli.run_interactive_onboarding() == expected + assert len(prompts) == prompt_count + assert prompts[1]["choices"][-1] == "DriveThruCards" + + def test_cli_site_choices_list_drivethrucards_last() -> None: result = CliRunner().invoke(autofill_cli.main, ["--help"]) assert result.exit_code == 0 @@ -468,13 +550,13 @@ def test_main_drive_thru_cards_exportpdf_generates_pdfs_without_browser_automati calls = {"pdf": [], "wait": 0, "driver": 0} - monkeypatch.setattr(autofill_cli, "configure_loggers", lambda **_kwargs: None) - monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) + monkeypatch.setattr("src.logging.configure_loggers", lambda **_kwargs: None) + monkeypatch.setattr("wakepy.keepawake", lambda **_kwargs: nullcontext()) monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/opt/homebrew/bin/gs") monkeypatch.setattr(autofill_cli, "ensure_ghostscript_available", lambda **_kwargs: "/opt/homebrew/bin/gs") - monkeypatch.setattr(autofill_cli, "find_or_download_dtc_icc_profile", lambda: str(icc_path)) + monkeypatch.setattr("src.icc.find_or_download_dtc_icc_profile", lambda: str(icc_path)) monkeypatch.setattr( - autofill_cli.CardOrder, + CardOrder, "from_xmls_in_folder", lambda working_directory: [SimpleNamespace(name="test_local")], ) @@ -491,7 +573,7 @@ def __init__(self, *args, **kwargs) -> None: calls["driver"] += 1 raise AssertionError("DriveThruCards browser automation should not run during --exportpdf") - monkeypatch.setattr(autofill_cli, "AutofillDriver", ShouldNotInstantiateDriver) + monkeypatch.setattr("src.driver.AutofillDriver", ShouldNotInstantiateDriver) result = CliRunner().invoke( autofill_cli.main, @@ -533,13 +615,13 @@ def test_main_drive_thru_cards_keeps_driver_alive_until_user_handoff( state = {"finalized": 0, "executed": 0, "wait_seen": None, "servers": 0, "starting_url": None} - monkeypatch.setattr(autofill_cli, "configure_loggers", lambda **_kwargs: None) - monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) + monkeypatch.setattr("src.logging.configure_loggers", lambda **_kwargs: None) + monkeypatch.setattr("wakepy.keepawake", lambda **_kwargs: nullcontext()) monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/opt/homebrew/bin/gs") monkeypatch.setattr(autofill_cli, "ensure_ghostscript_available", lambda **_kwargs: "/opt/homebrew/bin/gs") - monkeypatch.setattr(autofill_cli, "find_or_download_dtc_icc_profile", lambda: str(icc_path)) + monkeypatch.setattr("src.icc.find_or_download_dtc_icc_profile", lambda: str(icc_path)) monkeypatch.setattr( - autofill_cli.CardOrder, + CardOrder, "from_xmls_in_folder", lambda working_directory: [SimpleNamespace(name="test_local")], ) @@ -557,7 +639,7 @@ def __init__(self, html_filename: str) -> None: def server_url(self) -> str: return "http://localhost:1234/" - monkeypatch.setattr(autofill_cli, "WebServer", FakeWebServer) + monkeypatch.setattr("src.web_server.WebServer", FakeWebServer) class FakeDriver: def __init__(self, *args, **kwargs) -> None: @@ -569,7 +651,7 @@ def execute_drive_thru_cards_order(self, order, pdf_path) -> None: def __del__(self) -> None: state["finalized"] += 1 - monkeypatch.setattr(autofill_cli, "AutofillDriver", FakeDriver) + monkeypatch.setattr("src.driver.AutofillDriver", FakeDriver) def fake_wait_for_user_to_complete_order() -> None: gc.collect() @@ -619,10 +701,12 @@ def download_images(self, _pool, _download_bar, _post_processing_config): assert calls["backs"] == 1 -def test_nuitka_directives_include_assets_directory() -> None: +def test_nuitka_directives_include_runtime_data_and_cached_extraction() -> None: with open(autofill_cli.__file__, "r", encoding="utf-8") as f: source = f.read() assert "--include-data-dir=assets=assets" in source + assert "--include-package-data=certifi" in source + assert "--onefile-tempdir-spec={CACHE_DIR}/mpc-autofill" in source def test_readme_points_users_to_wiki_for_usage_docs() -> None: @@ -716,7 +800,7 @@ def fake_run(cmd, capture_output=True, text=True): assert convert_pdf_to_pdfx( str(source_path), str(output_path), - autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + PdfXConversionConfig(icc_profile_path="dummy.icc"), ) assert output_path.read_bytes() == b"%PDF-1.3 (PDF/X-1a:2001) /OutputIntents" assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] @@ -741,7 +825,7 @@ def fake_run(cmd, capture_output=True, text=True): assert not convert_pdf_to_pdfx( str(source_path), str(output_path), - autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + PdfXConversionConfig(icc_profile_path="dummy.icc"), ) assert output_path.read_bytes() == b"previous" assert sorted(path.name for path in tmp_path.iterdir()) == ["output.pdf", "source.pdf"] @@ -765,7 +849,7 @@ def fake_run(cmd, capture_output=True, text=True): assert not convert_pdf_to_pdfx( str(source_path), str(output_path), - autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + PdfXConversionConfig(icc_profile_path="dummy.icc"), ) assert output_path.read_bytes() == b"previous" @@ -783,7 +867,7 @@ def test_convert_pdf_to_pdfx_produces_verified_pdfx_with_real_ghostscript(tmp_pa pdf.output(str(source_path)) output_path = tmp_path / "output_pdfx.pdf" - assert convert_pdf_to_pdfx(str(source_path), str(output_path), autofill_cli.PdfXConversionConfig()) + assert convert_pdf_to_pdfx(str(source_path), str(output_path), PdfXConversionConfig()) contents = output_path.read_bytes() assert b"(PDF/X-1:2001)" in contents # GTS_PDFXVersion @@ -808,7 +892,7 @@ def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> boo pdf_exporter = PdfExporter( order=card_order_valid, number_of_cards_per_file=1, - pdfx_config=autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + pdfx_config=PdfXConversionConfig(icc_profile_path="dummy.icc"), ) generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) @@ -836,7 +920,7 @@ def do_nothing(_): pdf_exporter = PdfExporter( order=card_order_valid, number_of_cards_per_file=1, - pdfx_config=autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + pdfx_config=PdfXConversionConfig(icc_profile_path="dummy.icc"), ) generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) @@ -868,7 +952,7 @@ def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> boo pdf_exporter = PdfExporter( order=card_order_valid, number_of_cards_per_file=1, - pdfx_config=autofill_cli.PdfXConversionConfig(icc_profile_path="dummy.icc"), + pdfx_config=PdfXConversionConfig(icc_profile_path="dummy.icc"), ) generated_files = pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) @@ -1439,6 +1523,25 @@ def test_invalid_google_drive_image(image_invalid_google_drive: CardImage, count assert image_invalid_google_drive.errored is True +def test_failed_image_summary_is_logged_with_name_slots_and_link(monkeypatch, tmp_path, caplog): + image = CardImage( + drive_id="missing-drive-id", + slots={2, 5}, + name="Missing Card.png", + file_path=str(tmp_path / "Missing Card.png"), + ) + monkeypatch.setattr("src.order.download_google_drive_file", lambda **_kwargs: False) + progress = SimpleNamespace(update=lambda: None, refresh=lambda: None) + + with caplog.at_level(logging.ERROR, logger="src.logging"): + image.download_image(queue=Queue(), download_bar=progress, post_processing_config=None) + + message = caplog.text + assert "Missing Card.png" in message + assert "[2, 5]" in message + assert "missing-drive-id" in message + + @requires_google_drive_credentials def test_retrieve_card_name_and_download_file(image_google_valid_drive_no_name, counter, queue): assert image_google_valid_drive_no_name.name == f"{SIMPLE_CUBE}.png" @@ -2225,6 +2328,9 @@ def do_nothing(_): pdf_exporter = PdfExporter(order=card_order_valid) pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) + assert pdf_exporter.processed_bar.total == 3 + assert pdf_exporter.processed_bar.count == 3 + expected_generated_files = [ "export/test_order/1.pdf", ] @@ -2261,6 +2367,9 @@ def do_nothing(_): pdf_exporter = PdfExporter(order=card_order_valid, separate_faces=True, number_of_cards_per_file=1) pdf_exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) + assert pdf_exporter.processed_bar.total == 3 + assert pdf_exporter.processed_bar.count == 3 + expected_generated_files = [ "export/test_order/backs/1.pdf", "export/test_order/backs/2.pdf", @@ -2276,6 +2385,28 @@ def do_nothing(_): remove_directories(["export/test_order/backs", "export/test_order/fronts", "export/test_order", "export"]) +def test_pdf_export_stops_before_creating_pdf_when_an_image_download_fails(monkeypatch, card_order_valid): + monkeypatch.setattr("src.pdf_maker.PdfExporter.ask_questions", lambda _self: None) + + def download_fronts(*_args): + for index, card in enumerate(card_order_valid.fronts.cards_by_id.values()): + card.downloaded = index != 0 + + def download_backs(*_args): + for card in card_order_valid.backs.cards_by_id.values(): + card.downloaded = True + + monkeypatch.setattr(card_order_valid.fronts, "download_images", download_fronts) + monkeypatch.setattr(card_order_valid.backs, "download_images", download_backs) + exporter = PdfExporter(order=card_order_valid) + monkeypatch.setattr(exporter, "export", lambda: pytest.fail("PDF export should not start")) + + with pytest.raises(ImageDownloadError, match="Import this XML into a new MPC Fill project"): + exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) + + assert exporter.saved_files == [] + + def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(monkeypatch, tmp_path): monkeypatch.chdir(tmp_path) From 3dad8b26bc0586d5355f8d3d204627d426daceb6 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 14:09:25 -0700 Subject: [PATCH 62/76] chore(desktop-tool): review cleanup for DTC UX changes - Log an explanation when explicit --no-auto-save/--image-post-processing flags are overridden on the DriveThruCards path (click ParameterSource). - Stop printing ImageDownloadError twice on exit. - Share CRASH_LOG_FILENAME constant between logging and exc modules. - Pin pre-commit mypy hook to python3.13 to match CI and fix local env build. --- .pre-commit-config.yaml | 1 + desktop-tool/autofill.py | 18 +++++++++++----- desktop-tool/src/exc.py | 3 ++- desktop-tool/src/logging.py | 4 +++- desktop-tool/tests/test_desktop_tool.py | 28 +++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6763d784..72296f615 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,6 +25,7 @@ repos: rev: v1.7.0 hooks: - id: mypy + language_version: python3.13 args: [--config-file, mypy.ini, desktop-tool/, MPCAutofill/] additional_dependencies: [ # typing diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 4b29e19b4..40e83bd06 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -31,6 +31,7 @@ import certifi import click +from click.core import ParameterSource from InquirerPy import inquirer from src.constants import ( @@ -512,10 +513,6 @@ def main( if should_run_interactive_onboarding(): browser, site, auto_save, image_post_processing = run_interactive_onboarding() - if TargetSites[site] == TargetSites.DriveThruCards: - auto_save = True - image_post_processing = False - from wakepy import keepawake from src.exc import ImageDownloadError, ValidationException @@ -554,6 +551,17 @@ def main( log_debug_to_file=write_debug_logs, stdout_log_level=logging.getLevelName(log_level), ) + if TargetSites[site] == TargetSites.DriveThruCards: + explicit = click.get_current_context().get_parameter_source + if not auto_save and explicit("auto_save") == ParameterSource.COMMANDLINE: + logger.info("Ignoring --no-auto-save: DriveThruCards orders are always saved to your account.") + if image_post_processing and explicit("image_post_processing") == ParameterSource.COMMANDLINE: + logger.info( + "Ignoring --image-post-processing: DriveThruCards images are already downscaled once " + "during PDF creation." + ) + auto_save = True + image_post_processing = False try: with keepawake(keep_screen_awake=True) if not allowsleep else nullcontext(): logger.info("MPC Autofill desktop tool has successfully initialised!") @@ -669,7 +677,7 @@ def main( wait_for_user_to_complete_order() except ImageDownloadError as e: logger.error(str(e)) - input(f"{e}\n\nPress Enter to exit.") + input("Press Enter to exit.") except ValidationException as e: input(f"There was a problem with your order file:\n\n{bold(e)}\n\nPress Enter to exit.") sys.exit(0) diff --git a/desktop-tool/src/exc.py b/desktop-tool/src/exc.py index 491b0e428..a569a6f58 100644 --- a/desktop-tool/src/exc.py +++ b/desktop-tool/src/exc.py @@ -1,4 +1,5 @@ from src.formatting import bold +from src.logging import CRASH_LOG_FILENAME class InvalidStateException(Exception): @@ -27,5 +28,5 @@ def __init__(self, failed_images: list[tuple[str, str]]) -> None: "This usually means the saved XML refers to an image that was removed or replaced after the order " "was created. Import this XML into a new MPC Fill project to identify the unmatched cards and choose " "replacements, then download a new XML and try again. Technical details were saved to " - "autofill_crash_log.txt." + f"{CRASH_LOG_FILENAME}." ) diff --git a/desktop-tool/src/logging.py b/desktop-tool/src/logging.py index 585f6b017..34eec256d 100644 --- a/desktop-tool/src/logging.py +++ b/desktop-tool/src/logging.py @@ -6,6 +6,8 @@ logger = logging.getLogger(__name__) +CRASH_LOG_FILENAME = "autofill_crash_log.txt" + class FileLogFormatter(logging.Formatter): # A custom formatter which removes bold start/end characters from records before writing to disk @@ -32,7 +34,7 @@ def configure_loggers(working_directory: str, log_debug_to_file: bool, stdout_lo stdout_handler.setFormatter(logging.Formatter(console_debug_format_string)) logger.addHandler(stdout_handler) - file_crash_logger = logging.FileHandler(os.path.join(working_directory, "autofill_crash_log.txt")) + file_crash_logger = logging.FileHandler(os.path.join(working_directory, CRASH_LOG_FILENAME)) file_crash_logger.setLevel(logging.ERROR) file_crash_logger.setFormatter(FileLogFormatter(file_debug_format_string)) logger.addHandler(file_crash_logger) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 8933f5dc0..503a1c9ce 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -533,6 +533,34 @@ def fake_rawlist(**kwargs): assert prompts[1]["choices"][-1] == "DriveThruCards" +def test_dtc_overridden_explicit_flags_are_explained_in_logs( + tmp_path, caplog, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.chdir(tmp_path) + monkeypatch.setattr("src.logging.configure_loggers", lambda **_kwargs: None) + monkeypatch.setattr("wakepy.keepawake", lambda **_kwargs: nullcontext()) + + with caplog.at_level(logging.INFO, logger="src.logging"): + # no XML files in tmp_path, so the run exits at the "No XML files found" input() prompt + result = CliRunner().invoke( + autofill_cli.main, + [ + "-d", + str(tmp_path), + "--site", + "DriveThruCards", + "--image-post-processing", + "--no-auto-save", + "--download-images-only", + ], + input="\n", + ) + + assert result.exit_code == 0 + assert "Ignoring --image-post-processing" in caplog.text + assert "Ignoring --no-auto-save" in caplog.text + + def test_cli_site_choices_list_drivethrucards_last() -> None: result = CliRunner().invoke(autofill_cli.main, ["--help"]) assert result.exit_code == 0 From cb4a46d57dc59897bf0ae18831b6959fe86b61a1 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 14:45:11 -0700 Subject: [PATCH 63/76] Gate credential-dependent tests behind the Google Drive skipif marker fork PR CI runs have no GOOGLE_DRIVE_API_KEY secret, and 9 tests that download fixture images from Google Drive were failing there; marking them with requires_google_drive_credentials makes the workflow's self-skip promise true (verified locally: hiding client_secrets.json yields 101 passed / 21 skipped / 0 failures). --- desktop-tool/tests/test_desktop_tool.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 503a1c9ce..4cd600e6f 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -904,6 +904,7 @@ def test_convert_pdf_to_pdfx_produces_verified_pdfx_with_real_ghostscript(tmp_pa assert b"CGATS TR 001" in contents +@requires_google_drive_credentials def test_pdf_exporter_appends_pdfx_on_success(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: def do_nothing(_): return None @@ -937,6 +938,7 @@ def fake_convert_pdf_to_pdfx(source_path: str, output_path: str, _config) -> boo remove_directories(["export/test_order", "export"]) +@requires_google_drive_credentials def test_pdf_exporter_skips_pdfx_on_failure(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: def do_nothing(_): return None @@ -958,6 +960,7 @@ def do_nothing(_): remove_directories(["export/test_order", "export"]) +@requires_google_drive_credentials def test_pdf_exporter_logs_pdfx_conversion_progress(monkeypatch: pytest.MonkeyPatch, card_order_valid) -> None: logged_messages = [] @@ -1707,6 +1710,7 @@ def test_card_order_valid(card_order_valid): ) +@requires_google_drive_credentials def test_card_order_multiple_cardbacks(card_order_multiple_cardbacks): assert_orders_identical( card_order_multiple_cardbacks, @@ -1760,6 +1764,7 @@ def test_card_order_multiple_cardbacks(card_order_multiple_cardbacks): ) +@requires_google_drive_credentials def test_card_order_valid_from_file(): card_order = CardOrder.from_file_path(working_directory=FILE_PATH, file_path="test_order.xml") for card in (card_order.fronts.cards_by_id | card_order.backs.cards_by_id).values(): @@ -2347,6 +2352,7 @@ def key(order: CardOrder) -> int: # region test PdfExporter +@requires_google_drive_credentials def test_pdf_export_complete_3_cards_single_file(monkeypatch, card_order_valid): def do_nothing(_): return None @@ -2369,6 +2375,7 @@ def do_nothing(_): remove_directories(["export/test_order", "export"]) +@requires_google_drive_credentials def test_pdf_export_complete_3_cards_separate_files(monkeypatch, card_order_valid): def do_nothing(_): return None @@ -2386,6 +2393,7 @@ def do_nothing(_): remove_directories(["export/test_order", "export"]) +@requires_google_drive_credentials def test_pdf_export_complete_separate_faces(monkeypatch, card_order_valid): def do_nothing(_): return None @@ -2609,6 +2617,7 @@ def test_card_order_complete_run_single_cardback(browser, site, input_enter, car constants.TargetSites.PrinterStudioFR, ], ) +@requires_google_drive_credentials def test_card_order_complete_run_multiple_cardbacks(browser, site, input_enter, card_order_multiple_cardbacks): autofill_driver = AutofillDriver(browser=browser, target_site=site, headless=True) autofill_driver.execute_order( From 848a03a6bde83602984f4c6581122eb93efab663 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 15:04:45 -0700 Subject: [PATCH 64/76] Declutter progress bars across DTC export and browser phases - PdfExporter now stops its enlighten manager when execute() finishes or aborts, freezing completed counters into scrollback and releasing terminal rows so later bars cannot collide with them - Its transient State row is cleared (leave=False) - AutofillDriver no longer creates the per-image upload/download and project counters on the DriveThruCards path (they only ever showed 0/0) - just the State/Action row - DTC order completion now sets State: Finished instead of leaving a stale state - Also includes a black reformat of a test signature that the previous commit missed --- desktop-tool/src/driver.py | 7 ++- desktop-tool/src/pdf_maker.py | 56 ++++++++++--------- desktop-tool/tests/test_desktop_tool.py | 15 +++-- .../tests/test_drivethrucards_driver.py | 18 ++++++ 4 files changed, 66 insertions(+), 30 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index f18688a0b..914fac2b2 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -140,12 +140,16 @@ def initialise_bars(self) -> None: self.status_bar = self.manager.status_bar( status_format=status_format, state=bold(self.state), action=bold("N/A"), position=1, autorefresh=True ) + self.status_bar.refresh() + if self.target_site == TargetSites.DriveThruCards: + # DriveThruCards uploads a single PDF rather than individual images, so the per-image + # upload/download and project counters would only ever show 0/0 - don't create them. + return self.order_progress_bar = self.manager.counter( total=0, desc="Projects Auto-Filled", position=2, autorefresh=True ) self.download_bar = self.manager.counter(total=0, desc="Images Downloaded ", position=3, autorefresh=True) self.upload_bar = self.manager.counter(total=0, desc="Images Uploaded ", position=4, autorefresh=True) - self.status_bar.refresh() self.order_progress_bar.refresh() self.download_bar.refresh() self.upload_bar.refresh() @@ -1036,6 +1040,7 @@ def run_step(step_name: str, func: Any, *args: Any, **kwargs: Any) -> Any: run_step("select_card_type_and_upload_pdf", self.select_card_type_and_upload_pdf, pdf_path) # DriveThruCards automation complete - user should finish checkout manually + self.set_state(States.finished, "Complete your purchase in the browser") log_hours_minutes_seconds_elapsed(t) logger.info( "DriveThruCards order setup complete!\n" diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 83a07b9d7..0be717877 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -208,6 +208,7 @@ def configure_bars(self) -> None: status_format=status_format, state=f"{bold(self.state)}", position=1, + leave=False, # transient - cleared when the export finishes, unlike the counters below ) self.download_bar = self.manager.counter(total=num_images, desc="Images Downloaded", position=2) self.processed_bar = self.manager.counter(total=num_cards, desc="Cards Added to PDF", position=3) @@ -364,33 +365,38 @@ def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) - # Embed DPI metadata so PDF tools correctly interpret the image resolution post_processing_config.embed_dpi_metadata = True self.image_post_processing_config = post_processing_config - self.download_and_collect_images(post_processing_config=post_processing_config) try: - if self.separate_faces: - self.number_of_cards_per_file = 1 - self.export_separate_faces() - else: - self.export() - finally: - for tmp_path in self.processed_image_paths.values(): - if os.path.exists(tmp_path): - os.unlink(tmp_path) - self.processed_image_paths.clear() - - if self.pdfx_config: - source_pdf_paths = list(self.saved_files) - total_files = len(source_pdf_paths) - for index, file_path in enumerate(source_pdf_paths, start=1): - logger.info(f"Converting PDF to PDF/X-1a ({index}/{total_files}): {file_path}") - pdfx_path = f"{os.path.splitext(file_path)[0]}_pdfx.pdf" - if convert_pdf_to_pdfx(file_path, pdfx_path, self.pdfx_config): - self.saved_files.append(pdfx_path) - logger.info(f"PDF/X-1a conversion succeeded: {pdfx_path}") + self.download_and_collect_images(post_processing_config=post_processing_config) + try: + if self.separate_faces: + self.number_of_cards_per_file = 1 + self.export_separate_faces() else: - logger.info(f"PDF/X-1a conversion failed for {file_path}. Using original PDF.") - - logger.info(f"Finished exporting files! They should be accessible at {self.save_path}.") - return self.saved_files + self.export() + finally: + for tmp_path in self.processed_image_paths.values(): + if os.path.exists(tmp_path): + os.unlink(tmp_path) + self.processed_image_paths.clear() + + if self.pdfx_config: + source_pdf_paths = list(self.saved_files) + total_files = len(source_pdf_paths) + for index, file_path in enumerate(source_pdf_paths, start=1): + logger.info(f"Converting PDF to PDF/X-1a ({index}/{total_files}): {file_path}") + pdfx_path = f"{os.path.splitext(file_path)[0]}_pdfx.pdf" + if convert_pdf_to_pdfx(file_path, pdfx_path, self.pdfx_config): + self.saved_files.append(pdfx_path) + logger.info(f"PDF/X-1a conversion succeeded: {pdfx_path}") + else: + logger.info(f"PDF/X-1a conversion failed for {file_path}. Using original PDF.") + + logger.info(f"Finished exporting files! They should be accessible at {self.save_path}.") + return self.saved_files + finally: + # Freeze the counters into scrollback as a static record of the export and release the + # terminal rows, so any progress bars created later render below instead of colliding. + self.manager.stop() def export(self) -> None: for slot in sorted(self.paths_by_slot.keys()): diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 4cd600e6f..b4f761dd8 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -533,9 +533,7 @@ def fake_rawlist(**kwargs): assert prompts[1]["choices"][-1] == "DriveThruCards" -def test_dtc_overridden_explicit_flags_are_explained_in_logs( - tmp_path, caplog, monkeypatch: pytest.MonkeyPatch -) -> None: +def test_dtc_overridden_explicit_flags_are_explained_in_logs(tmp_path, caplog, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) monkeypatch.setattr("src.logging.configure_loggers", lambda **_kwargs: None) monkeypatch.setattr("wakepy.keepawake", lambda **_kwargs: nullcontext()) @@ -2436,11 +2434,15 @@ def download_backs(*_args): monkeypatch.setattr(card_order_valid.backs, "download_images", download_backs) exporter = PdfExporter(order=card_order_valid) monkeypatch.setattr(exporter, "export", lambda: pytest.fail("PDF export should not start")) + manager_stop_calls = [] + monkeypatch.setattr(exporter.manager, "stop", lambda: manager_stop_calls.append(True)) with pytest.raises(ImageDownloadError, match="Import this XML into a new MPC Fill project"): exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) assert exporter.saved_files == [] + # the terminal rows must be released even when the export aborts + assert manager_stop_calls == [True] def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(monkeypatch, tmp_path): @@ -2484,7 +2486,10 @@ def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(m ) order.name = "test_local.xml" - generated_files = PdfExporter(order=order, export_mode="drive_thru_cards").execute( + exporter = PdfExporter(order=order, export_mode="drive_thru_cards") + manager_stop_calls = [] + monkeypatch.setattr(exporter.manager, "stop", lambda: manager_stop_calls.append(True)) + generated_files = exporter.execute( post_processing_config=ImagePostProcessingConfig( max_dpi=300, downscale_alg=constants.ImageResizeMethods.LANCZOS, @@ -2496,6 +2501,8 @@ def test_pdf_export_drive_thru_cards_combines_actual_front_slots_into_one_file(m assert list(map(Path, generated_files)) == [Path("export/test_local/1.pdf")] assert os.path.exists("export/test_local/1.pdf") assert count_pdf_pages("export/test_local/1.pdf") == 4 + # progress bars are frozen into scrollback once the export completes + assert manager_stop_calls == [True] def test_pdf_export_drive_thru_cards_processes_and_embeds_repeated_images_once(monkeypatch, tmp_path): diff --git a/desktop-tool/tests/test_drivethrucards_driver.py b/desktop-tool/tests/test_drivethrucards_driver.py index fac2d7787..6d9cbabfb 100644 --- a/desktop-tool/tests/test_drivethrucards_driver.py +++ b/desktop-tool/tests/test_drivethrucards_driver.py @@ -502,3 +502,21 @@ def test_select_card_type_and_upload_pdf_raises_when_pdf_is_missing( with pytest.raises(Exception, match="PDF file not found"): dtc_driver.select_card_type_and_upload_pdf(pdf_path=str(tmp_path / "does-not-exist.pdf")) + + +def test_initialise_bars_creates_only_status_bar_for_dtc(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(AutofillDriver, "__attrs_post_init__", lambda self: None) + + dtc = AutofillDriver(target_site=TargetSites.DriveThruCards) + dtc.initialise_bars() + assert dtc.status_bar + assert dtc.order_progress_bar is None + assert dtc.download_bar is None + assert dtc.upload_bar is None + + mpc = AutofillDriver(target_site=TargetSites.MakePlayingCards) + mpc.initialise_bars() + assert mpc.status_bar + assert mpc.order_progress_bar is not None + assert mpc.download_bar is not None + assert mpc.upload_bar is not None From 4c5fe1e31bacce26e35174db0cbe463bf4420768 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 15:09:04 -0700 Subject: [PATCH 65/76] Hide tracebacks from the console and gate uploads on download success for all sites - ConsoleFormatter strips exception tracebacks from console output at non-DEBUG levels while the crash log still records them in full - execute_order now completes all image downloads before any browser automation and raises ImageDownloadError listing the failed cards, so no site gets a partially-uploaded project - failed-download collection moved to CardOrder.get_failed_downloads and shared with PdfExporter - ImageDownloadError message generalised to cover both PDF and upload flows --- desktop-tool/src/driver.py | 53 ++++++++++++++----------- desktop-tool/src/exc.py | 2 +- desktop-tool/src/logging.py | 15 +++++++ desktop-tool/src/order.py | 14 +++++++ desktop-tool/src/pdf_maker.py | 9 +---- desktop-tool/tests/test_desktop_tool.py | 47 ++++++++++++++++++++++ 6 files changed, 108 insertions(+), 32 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 914fac2b2..a5c3ce090 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -35,7 +35,7 @@ States, TargetSites, ) -from src.exc import InvalidStateException +from src.exc import ImageDownloadError, InvalidStateException from src.formatting import bold from src.logging import logger from src.order import CardImage, CardImageCollection, CardOrder @@ -1530,6 +1530,8 @@ def execute_order( ) -> None: t = time.time() self.configure_bars_for_order(order=order) + # complete all downloads before any browser automation so a failed image stops the + # order before anything is uploaded, rather than leaving a partially-filled project with ThreadPoolExecutor(max_workers=THREADS) as pool: order.fronts.download_images( pool=pool, download_bar=self.download_bar, post_processing_config=post_processing_config @@ -1537,35 +1539,38 @@ def execute_order( order.backs.download_images( pool=pool, download_bar=self.download_bar, post_processing_config=post_processing_config ) - if any( - [ - fulfilment_method == OrderFulfilmentMethod.append_to_project, - fulfilment_method == OrderFulfilmentMethod.continue_project, - auto_save_threshold is not None, - ] - ): - self.authenticate() - - self.initialise_order(order=order) - if fulfilment_method == OrderFulfilmentMethod.new_project: - logger.info("Configuring a new project.") - self.define_project(order=order) - self.page_to_fronts(order=order) - else: - order = self.redefine_project(order=order, fulfilment_method=fulfilment_method) + failed_images = order.get_failed_downloads() + if failed_images: + raise ImageDownloadError(failed_images) + + if any( + [ + fulfilment_method == OrderFulfilmentMethod.append_to_project, + fulfilment_method == OrderFulfilmentMethod.continue_project, + auto_save_threshold is not None, + ] + ): + self.authenticate() + + self.initialise_order(order=order) + if fulfilment_method == OrderFulfilmentMethod.new_project: + logger.info("Configuring a new project.") + self.define_project(order=order) + self.page_to_fronts(order=order) + else: + order = self.redefine_project(order=order, fulfilment_method=fulfilment_method) - self.insert_fronts(order=order, auto_save_threshold=auto_save_threshold) - self.page_to_backs(order=order) - self.insert_backs(order=order, auto_save_threshold=auto_save_threshold) - self.page_to_review() + self.insert_fronts(order=order, auto_save_threshold=auto_save_threshold) + self.page_to_backs(order=order) + self.insert_backs(order=order, auto_save_threshold=auto_save_threshold) + self.page_to_review() log_hours_minutes_seconds_elapsed(t) logger.info( textwrap.dedent( f""" Please review your project and ensure everything has been uploaded correctly before finalising with - {self.target_site.name}. If any images failed to download, links to download them will have been printed - above. If you need to make any changes to your order, you can do so by adding it to your Saved Projects - and editing in your normal browser. + {self.target_site.name}. If you need to make any changes to your order, you can do so by adding it + to your Saved Projects and editing in your normal browser. """ ) ) diff --git a/desktop-tool/src/exc.py b/desktop-tool/src/exc.py index a569a6f58..7e052471d 100644 --- a/desktop-tool/src/exc.py +++ b/desktop-tool/src/exc.py @@ -23,7 +23,7 @@ def __init__(self, failed_images: list[tuple[str, str]]) -> None: for name, drive_id in failed_images ) super().__init__( - "Some card images could not be downloaded, so PDF creation has stopped.\n" + "Some card images could not be downloaded, so the tool has stopped before creating your order.\n" f"{failed_list}\n\n" "This usually means the saved XML refers to an image that was removed or replaced after the order " "was created. Import this XML into a new MPC Fill project to identify the unmatched cards and choose " diff --git a/desktop-tool/src/logging.py b/desktop-tool/src/logging.py index 34eec256d..99459e4c3 100644 --- a/desktop-tool/src/logging.py +++ b/desktop-tool/src/logging.py @@ -17,6 +17,18 @@ def format(self, record: logging.LogRecord) -> str: return super().format(new_record) +class ConsoleFormatter(logging.Formatter): + # Hides exception tracebacks from the console - they're still written in full to the crash log. + # Formats a copy so the original record's traceback isn't stripped for other handlers. + def format(self, record: logging.LogRecord) -> str: + if record.exc_info or record.exc_text or record.stack_info: + record = copy(record) + record.exc_info = None + record.exc_text = None + record.stack_info = None + return super().format(record) + + def configure_loggers(working_directory: str, log_debug_to_file: bool, stdout_log_level: int) -> None: logging.getLogger("googleapiclient").setLevel(logging.ERROR) logging.getLogger("oauth2client").setLevel(logging.ERROR) @@ -30,8 +42,11 @@ def configure_loggers(working_directory: str, log_debug_to_file: bool, stdout_lo stdout_handler.setLevel(stdout_log_level) if stdout_log_level <= logging.DEBUG: # If the user has opted into debug logging, format stdout logs with their log level + # and keep exception tracebacks visible on the console console_debug_format_string = "[%(levelname)s] %(message)s" stdout_handler.setFormatter(logging.Formatter(console_debug_format_string)) + else: + stdout_handler.setFormatter(ConsoleFormatter()) logger.addHandler(stdout_handler) file_crash_logger = logging.FileHandler(os.path.join(working_directory, CRASH_LOG_FILENAME)) diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index 024b1d591..bbaf2ea96 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -710,6 +710,20 @@ def from_multiple_orders(cls, orders: list["CardOrder"]) -> "CardOrder": assert len(orders) > 0, "Attempted to produce a CardOrder from multiple CardOrders but none were given!" return reduce(lambda a, b: a.combine(b), orders) + def get_failed_downloads(self) -> list[tuple[str, str]]: + """ + Return (name, drive_id) for each image in this order which has not been downloaded successfully. + """ + + return sorted( + { + (card.name or "Unknown image", card.drive_id) + for collection in (self.fronts, self.backs) + for card in collection.cards_by_id.values() + if not card.downloaded + } + ) + def get_overview(self) -> str: return ( f"Total of {bold(self.details.quantity)} cards. " diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 0be717877..44e3cb66b 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -334,14 +334,9 @@ def download_and_collect_images(self, post_processing_config: Optional[ImagePost self.order.fronts.download_images(pool, self.download_bar, download_config) self.order.backs.download_images(pool, self.download_bar, download_config) - failed_images = { - (card.name or "Unknown image", card.drive_id) - for collection in (self.order.fronts, self.order.backs) - for card in collection.cards_by_id.values() - if not card.downloaded - } + failed_images = self.order.get_failed_downloads() if failed_images: - raise ImageDownloadError(sorted(failed_images)) + raise ImageDownloadError(failed_images) backs_by_slots = {} for card in self.order.backs.cards_by_id.values(): diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index b4f761dd8..b0124c492 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -2644,3 +2644,50 @@ def test_card_order_complete_run_multiple_cardbacks(browser, site, input_enter, # endregion + + +def test_console_formatter_hides_tracebacks_but_default_formatter_keeps_them(): + from src.logging import ConsoleFormatter + + try: + raise ValueError("boom") + except ValueError: + record = logging.LogRecord( + name="src.logging", + level=logging.ERROR, + pathname=__file__, + lineno=1, + msg="download failed", + args=(), + exc_info=sys.exc_info(), + ) + + assert ConsoleFormatter().format(record) == "download failed" + # the record itself is untouched, so the crash log formatter still sees the traceback + assert "Traceback" in logging.Formatter().format(record) + + +def test_execute_order_stops_before_upload_when_downloads_fail(monkeypatch, card_order_valid): + monkeypatch.setattr(AutofillDriver, "__attrs_post_init__", lambda self: None) + driver = AutofillDriver(target_site=constants.TargetSites.MakePlayingCards) + driver.initialise_bars() + + def fail_fronts(**_kwargs): + for index, card in enumerate(card_order_valid.fronts.cards_by_id.values()): + card.downloaded = index != 0 + + def download_backs(**_kwargs): + for card in card_order_valid.backs.cards_by_id.values(): + card.downloaded = True + + monkeypatch.setattr(card_order_valid.fronts, "download_images", fail_fronts) + monkeypatch.setattr(card_order_valid.backs, "download_images", download_backs) + monkeypatch.setattr(driver, "initialise_order", lambda **_kwargs: pytest.fail("upload must not start")) + + with pytest.raises(ImageDownloadError, match="stopped before creating your order"): + driver.execute_order( + order=card_order_valid, + fulfilment_method=OrderFulfilmentMethod.new_project, + auto_save_threshold=None, + post_processing_config=None, + ) From baf2b4bc32c44c5f915400b1a0a4ac02d70206b1 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 15:19:48 -0700 Subject: [PATCH 66/76] Version the onefile extraction cache to survive binary upgrades on macOS Nuitka's cached onefile extraction rewrites changed files in place when a new build reuses the cache directory; on macOS this invalidates the kernel's cached code signature of the previously-executed binary and the process dies with SIGKILL and no output. Extracting each build version to its own directory ({VERSION} in --onefile-tempdir-spec, fed by --file-version=1.0. in CI) sidesteps the in-place rewrite entirely, and the tool prunes stale version directories at startup so they do not accumulate. Builds without --file-version now fail loudly instead of silently reintroducing the bug. --- .github/workflows/build-desktop-tool.yml | 2 ++ .github/workflows/desktop-tool-ci.yml | 2 ++ desktop-tool/autofill.py | 22 +++++++++++++++++++- desktop-tool/tests/test_desktop_tool.py | 26 +++++++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index 20bd758f0..f91dc1d09 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -69,6 +69,8 @@ jobs: script-name: autofill.py mode: onefile output-file: ${{ matrix.filename }} + # required by the versioned onefile cache directory ({VERSION} in --onefile-tempdir-spec) + file-version: 1.0.${{ github.run_number }} macos-target-arch: ${{ matrix.macos-arch }} - name: Smoke test bundled executable run: | diff --git a/.github/workflows/desktop-tool-ci.yml b/.github/workflows/desktop-tool-ci.yml index 674210f23..29ba509ba 100644 --- a/.github/workflows/desktop-tool-ci.yml +++ b/.github/workflows/desktop-tool-ci.yml @@ -114,6 +114,8 @@ jobs: script-name: autofill.py mode: onefile output-file: ${{ matrix.FILENAME }} + # required by the versioned onefile cache directory ({VERSION} in --onefile-tempdir-spec) + file-version: 1.0.${{ github.run_number }} macos-sign-notarization: ${{ (matrix.TARGET == 'macos-arm') || (matrix.TARGET == 'macos-intel') }} macos-sign-identity: ${{ secrets.MACOS_SIGN_IDENTITY }} macos-sign-keyring-filename: "${{ runner.temp }}/app-signing.keychain-db" diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 40e83bd06..08c2763dc 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -1,5 +1,8 @@ # nuitka-project: --mode=onefile -# nuitka-project: --onefile-tempdir-spec={CACHE_DIR}/mpc-autofill +# Each build version extracts to its own cache directory: rewriting a previously-run executable +# in place invalidates macOS's cached code-signature and the OS kills the process with SIGKILL. +# {VERSION} makes builds fail loudly unless --file-version (or --product-version) is passed. +# nuitka-project: --onefile-tempdir-spec={CACHE_DIR}/mpc-autofill/{VERSION} # nuitka-project: --include-data-files=client_secrets.json=client_secrets.json # nuitka-project: --include-data-files=post-launch.html=post-launch.html # nuitka-project: --include-data-files=dtc-post-launch.html=dtc-post-launch.html @@ -54,7 +57,24 @@ def configure_tls() -> str: return os.environ.setdefault("SSL_CERT_FILE", certifi.where()) +def prune_stale_onefile_caches(extraction_directory: str) -> None: + """ + Each build version extracts to its own directory under the mpc-autofill cache directory + (see the --onefile-tempdir-spec build directive) - delete extractions left by old versions. + """ + + parent_directory = os.path.dirname(extraction_directory) + if os.path.basename(parent_directory) != "mpc-autofill": + return + for entry in os.listdir(parent_directory): + sibling = os.path.join(parent_directory, entry) + if sibling != extraction_directory and os.path.isdir(sibling): + shutil.rmtree(sibling, ignore_errors=True) + + configure_tls() +if "__compiled__" in globals(): + prune_stale_onefile_caches(os.path.dirname(os.path.abspath(__file__))) # https://stackoverflow.com/questions/12492810/python-how-can-i-make-the-ansi-escape-codes-to-work-also-in-windows if sys.platform == "win32": diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index b0124c492..e77721d54 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -732,7 +732,7 @@ def test_nuitka_directives_include_runtime_data_and_cached_extraction() -> None: source = f.read() assert "--include-data-dir=assets=assets" in source assert "--include-package-data=certifi" in source - assert "--onefile-tempdir-spec={CACHE_DIR}/mpc-autofill" in source + assert "--onefile-tempdir-spec={CACHE_DIR}/mpc-autofill/{VERSION}" in source def test_readme_points_users_to_wiki_for_usage_docs() -> None: @@ -2691,3 +2691,27 @@ def download_backs(**_kwargs): auto_save_threshold=None, post_processing_config=None, ) + + +def test_prune_stale_onefile_caches_removes_only_sibling_version_dirs(tmp_path): + cache_root = tmp_path / "mpc-autofill" + current = cache_root / "1.0.2" + stale = cache_root / "1.0.1" + for directory in (current, stale): + directory.mkdir(parents=True) + (directory / "autofill.bin").touch() + (cache_root / "unrelated-file.txt").touch() + + autofill_cli.prune_stale_onefile_caches(str(current)) + + assert current.exists() + assert not stale.exists() + assert (cache_root / "unrelated-file.txt").exists() + + # refuses to delete anything when not inside an mpc-autofill cache directory + other = tmp_path / "somewhere-else" / "1.0.2" + other_sibling = tmp_path / "somewhere-else" / "1.0.1" + other.mkdir(parents=True) + other_sibling.mkdir(parents=True) + autofill_cli.prune_stale_onefile_caches(str(other)) + assert other_sibling.exists() From cb44a0461a9e11df2e05a849cae16b7b684e9b52 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 15:29:53 -0700 Subject: [PATCH 67/76] Keep per-image download errors out of the console per-image failure logs (HTTP error lines, failed-download lines with links, local-file problems) now carry a FILE_ONLY extra that a console-handler filter drops at non-DEBUG levels - the crash log still records everything and the ImageDownloadError summary remains the single user-facing report; --download-images-only now raises the same summary so silencing per-image lines leaves no path without feedback; PdfExporter closes its transient status row explicitly so leave=False actually clears it on stop. --- desktop-tool/autofill.py | 17 +++++++++---- desktop-tool/src/io.py | 4 +-- desktop-tool/src/logging.py | 9 +++++++ desktop-tool/src/order.py | 18 ++++++++----- desktop-tool/src/pdf_maker.py | 2 ++ desktop-tool/tests/test_desktop_tool.py | 34 ++++++++++++++++++++++++- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 08c2763dc..e3139a047 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -306,15 +306,22 @@ def download_images_for_orders( import enlighten from src.constants import THREADS + from src.exc import ImageDownloadError total_images = sum(len(order.fronts.cards_by_id) + len(order.backs.cards_by_id) for order in orders) manager = enlighten.get_manager() download_bar = manager.counter(total=total_images, desc="Images Downloaded", position=1, autorefresh=True) - with ThreadPoolExecutor(max_workers=THREADS) as pool: - for order in orders: - logger.info(f"Downloading images for {bold(order.name or 'Unnamed Project')}...") - order.fronts.download_images(pool, download_bar, post_processing_config) - order.backs.download_images(pool, download_bar, post_processing_config) + try: + with ThreadPoolExecutor(max_workers=THREADS) as pool: + for order in orders: + logger.info(f"Downloading images for {bold(order.name or 'Unnamed Project')}...") + order.fronts.download_images(pool, download_bar, post_processing_config) + order.backs.download_images(pool, download_bar, post_processing_config) + finally: + manager.stop() + failed_images = sorted({failed for order in orders for failed in order.get_failed_downloads()}) + if failed_images: + raise ImageDownloadError(failed_images) logger.info("Finished downloading card images.") diff --git a/desktop-tool/src/io.py b/desktop-tool/src/io.py index 35767d104..b45bb19ba 100644 --- a/desktop-tool/src/io.py +++ b/desktop-tool/src/io.py @@ -13,7 +13,7 @@ from oauth2client.service_account import ServiceAccountCredentials import src.constants as constants -from src.logging import logger +from src.logging import FILE_ONLY, logger from src.processing import ( ImagePostProcessingConfig, get_post_processed_path, @@ -194,7 +194,7 @@ def download_google_drive_file( _, done = downloader.next_chunk() file_bytes = file.getvalue() except HttpError: - logger.exception(f"Encountered a HTTP error while downloading Google Drive image {drive_id}") + logger.exception(f"Encountered a HTTP error while downloading Google Drive image {drive_id}", extra=FILE_ONLY) return False if post_processing_config is not None: diff --git a/desktop-tool/src/logging.py b/desktop-tool/src/logging.py index 99459e4c3..f5323acd4 100644 --- a/desktop-tool/src/logging.py +++ b/desktop-tool/src/logging.py @@ -8,6 +8,14 @@ CRASH_LOG_FILENAME = "autofill_crash_log.txt" +# pass as `extra` to log calls whose details belong in the crash log but not on the console, +# e.g. per-image download failures that are also reported in a user-facing summary +FILE_ONLY = {"console": False} + + +def _console_visible(record: logging.LogRecord) -> bool: + return getattr(record, "console", True) + class FileLogFormatter(logging.Formatter): # A custom formatter which removes bold start/end characters from records before writing to disk @@ -47,6 +55,7 @@ def configure_loggers(working_directory: str, log_debug_to_file: bool, stdout_lo stdout_handler.setFormatter(logging.Formatter(console_debug_format_string)) else: stdout_handler.setFormatter(ConsoleFormatter()) + stdout_handler.addFilter(_console_visible) logger.addHandler(stdout_handler) file_crash_logger = logging.FileHandler(os.path.join(working_directory, CRASH_LOG_FILENAME)) diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index bbaf2ea96..9db08ac5b 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -26,7 +26,7 @@ get_google_drive_file_name, get_image_directory, ) -from src.logging import logger +from src.logging import FILE_ONLY, logger from src.processing import ImagePostProcessingConfig from src.utils import unpack_element @@ -203,13 +203,17 @@ def download_image( if is_image_valid(cast(str, self.file_path)): self.downloaded = True else: - logger.info( + logger.error( f"Local file '{bold(self.name)}' appears to be corrupted at path:\n" - f"{bold(self.file_path)}\n" + f"{bold(self.file_path)}\n", + extra=FILE_ONLY, ) self.errored = True else: - logger.info(f"Local file '{bold(self.name)}' does not exist at path:\n{bold(self.drive_id)}\n") + logger.error( + f"Local file '{bold(self.name)}' does not exist at path:\n{bold(self.drive_id)}\n", + extra=FILE_ONLY, + ) elif self.source_type == SourceType.GOOGLE_DRIVE: if self.file_path is not None and not self.errored: for attempt in range(2): @@ -232,7 +236,8 @@ def download_image( else: logger.error( f"Failed to download '{bold(self.name)}' - allocated to slot/s {bold(sorted(self.slots))}.\n" - f"Download link - {bold(f'https://drive.google.com/uc?id={self.drive_id}&export=download')}\n" + f"Download link - {bold(f'https://drive.google.com/uc?id={self.drive_id}&export=download')}\n", + extra=FILE_ONLY, ) except Exception as e: # note: python threads die silently if they encounter an exception. if an exception does occur, @@ -240,7 +245,8 @@ def download_image( logger.exception( f"An uncaught exception occurred when attempting to download '{bold(self.name)}':\n{bold(e)}\n" f"Allocated to slot/s {bold(sorted(self.slots))}.\n" - f"Download link - {bold(f'https://drive.google.com/uc?id={self.drive_id}&export=download')}\n" + f"Download link - {bold(f'https://drive.google.com/uc?id={self.drive_id}&export=download')}\n", + extra=FILE_ONLY, ) finally: queue.put((self.drive_id, self.downloaded)) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 44e3cb66b..849735db6 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -391,6 +391,8 @@ def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) - finally: # Freeze the counters into scrollback as a static record of the export and release the # terminal rows, so any progress bars created later render below instead of colliding. + # The transient status row must be closed explicitly for its leave=False to clear it. + self.status_bar.close() self.manager.stop() def export(self) -> None: diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index e77721d54..f6822e931 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -719,7 +719,7 @@ def __init__(self, key: str) -> None: def download_images(self, _pool, _download_bar, _post_processing_config): calls[self._key] += 1 - order = SimpleNamespace(name="order1", fronts=Face("fronts"), backs=Face("backs")) + order = SimpleNamespace(name="order1", fronts=Face("fronts"), backs=Face("backs"), get_failed_downloads=lambda: []) autofill_cli.download_images_for_orders(orders=[order], post_processing_config=DEFAULT_POST_PROCESSING) @@ -2715,3 +2715,35 @@ def test_prune_stale_onefile_caches_removes_only_sibling_version_dirs(tmp_path): other_sibling.mkdir(parents=True) autofill_cli.prune_stale_onefile_caches(str(other)) assert other_sibling.exists() + + +def test_console_filter_hides_file_only_records(): + from src.logging import FILE_ONLY, _console_visible + + visible = logging.LogRecord( + name="src.logging", level=logging.ERROR, pathname=__file__, lineno=1, msg="shown", args=(), exc_info=None + ) + hidden = logging.LogRecord( + name="src.logging", level=logging.ERROR, pathname=__file__, lineno=1, msg="hidden", args=(), exc_info=None + ) + for key, value in FILE_ONLY.items(): + setattr(hidden, key, value) + + assert _console_visible(visible) is True + assert _console_visible(hidden) is False + + +def test_download_images_only_raises_summary_when_downloads_fail(monkeypatch, card_order_valid): + def fail_fronts(*_args, **_kwargs): + for card in card_order_valid.fronts.cards_by_id.values(): + card.downloaded = False + + def download_backs(*_args, **_kwargs): + for card in card_order_valid.backs.cards_by_id.values(): + card.downloaded = True + + monkeypatch.setattr(card_order_valid.fronts, "download_images", fail_fronts) + monkeypatch.setattr(card_order_valid.backs, "download_images", download_backs) + + with pytest.raises(ImageDownloadError, match="stopped before creating your order"): + autofill_cli.download_images_for_orders(orders=[card_order_valid], post_processing_config=None) From 42cee0599263e28f88d5e984f890f9c2f87ef644 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 15:44:30 -0700 Subject: [PATCH 68/76] Clear progress bars when their phase ends and link to the mpcfill.com editor progress bars are now fully transient - live while their phase runs, cleared (leave=False + close(clear=True)) when it ends - so final output reads as naturally-scrolled log text with no frozen bar rows pinned to the bottom of the window; the failed-download message now points at https://mpcfill.com/editor to distinguish the web app from the desktop tool. --- desktop-tool/autofill.py | 5 ++++- desktop-tool/src/exc.py | 6 +++--- desktop-tool/src/pdf_maker.py | 16 ++++++++++------ desktop-tool/tests/test_desktop_tool.py | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index e3139a047..5194a5d27 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -310,7 +310,9 @@ def download_images_for_orders( total_images = sum(len(order.fronts.cards_by_id) + len(order.backs.cards_by_id) for order in orders) manager = enlighten.get_manager() - download_bar = manager.counter(total=total_images, desc="Images Downloaded", position=1, autorefresh=True) + download_bar = manager.counter( + total=total_images, desc="Images Downloaded", position=1, autorefresh=True, leave=False + ) try: with ThreadPoolExecutor(max_workers=THREADS) as pool: for order in orders: @@ -318,6 +320,7 @@ def download_images_for_orders( order.fronts.download_images(pool, download_bar, post_processing_config) order.backs.download_images(pool, download_bar, post_processing_config) finally: + download_bar.close(clear=True) manager.stop() failed_images = sorted({failed for order in orders for failed in order.get_failed_downloads()}) if failed_images: diff --git a/desktop-tool/src/exc.py b/desktop-tool/src/exc.py index 7e052471d..8e173a250 100644 --- a/desktop-tool/src/exc.py +++ b/desktop-tool/src/exc.py @@ -26,7 +26,7 @@ def __init__(self, failed_images: list[tuple[str, str]]) -> None: "Some card images could not be downloaded, so the tool has stopped before creating your order.\n" f"{failed_list}\n\n" "This usually means the saved XML refers to an image that was removed or replaced after the order " - "was created. Import this XML into a new MPC Fill project to identify the unmatched cards and choose " - "replacements, then download a new XML and try again. Technical details were saved to " - f"{CRASH_LOG_FILENAME}." + f"was created. Import this XML into a new project at {bold('https://mpcfill.com/editor')} to identify " + "the unmatched cards and choose replacements, then download a new XML and try again. " + f"Technical details were saved to {CRASH_LOG_FILENAME}." ) diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 849735db6..489d95085 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -210,8 +210,10 @@ def configure_bars(self) -> None: position=1, leave=False, # transient - cleared when the export finishes, unlike the counters below ) - self.download_bar = self.manager.counter(total=num_images, desc="Images Downloaded", position=2) - self.processed_bar = self.manager.counter(total=num_cards, desc="Cards Added to PDF", position=3) + # all bars are transient (leave=False): they're live UI while the export runs and are + # cleared when it ends - the log output is the permanent record + self.download_bar = self.manager.counter(total=num_images, desc="Images Downloaded", position=2, leave=False) + self.processed_bar = self.manager.counter(total=num_cards, desc="Cards Added to PDF", position=3, leave=False) self.download_bar.refresh() self.processed_bar.refresh() @@ -389,10 +391,12 @@ def execute(self, post_processing_config: Optional[ImagePostProcessingConfig]) - logger.info(f"Finished exporting files! They should be accessible at {self.save_path}.") return self.saved_files finally: - # Freeze the counters into scrollback as a static record of the export and release the - # terminal rows, so any progress bars created later render below instead of colliding. - # The transient status row must be closed explicitly for its leave=False to clear it. - self.status_bar.close() + # The bars are transient UI - clear them and release the terminal rows so subsequent + # output (log lines, error summaries, later progress bars) flows naturally below the + # scrolled log output instead of around bars pinned to the bottom of the window. + self.status_bar.close(clear=True) + self.download_bar.close(clear=True) + self.processed_bar.close(clear=True) self.manager.stop() def export(self) -> None: diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index f6822e931..0793b21f0 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -2437,7 +2437,7 @@ def download_backs(*_args): manager_stop_calls = [] monkeypatch.setattr(exporter.manager, "stop", lambda: manager_stop_calls.append(True)) - with pytest.raises(ImageDownloadError, match="Import this XML into a new MPC Fill project"): + with pytest.raises(ImageDownloadError, match="Import this XML into a new project at"): exporter.execute(post_processing_config=DEFAULT_POST_PROCESSING) assert exporter.saved_files == [] From 1038c53189db6de4e0cc4c604bf6522122de33cf Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 16:22:03 -0700 Subject: [PATCH 69/76] Gate the single-cardback complete run behind Google Drive credentials this test previously survived fork-PR CI without credentials because failed downloads were merely skipped at upload time; the new stop-before-upload gate correctly aborts it, so it needs the requires_google_drive_credentials marker like the other secret-backed tests (verified: full suite without client_secrets.json now passes 101/skips 27). --- desktop-tool/tests/test_desktop_tool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 0793b21f0..aec5e6d7f 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -2593,6 +2593,7 @@ def counting_post_process_image(raw_image, config): constants.TargetSites.PrinterStudioFR, ], ) +@requires_google_drive_credentials def test_card_order_complete_run_single_cardback(browser, site, input_enter, card_order_valid): autofill_driver = AutofillDriver(browser=browser, target_site=site, headless=True) autofill_driver.execute_order( From 1af1675d9e8262502c48547fb5c92edc1c278706 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 17:12:37 -0700 Subject: [PATCH 70/76] Add Windows to the dispatchable desktop tool build workflow The signed desktop-tool-ci workflow cannot be dispatched on the fork (not on the default branch), so the build workflow now also produces the Windows executable; the smoke test step declares bash so its syntax works on the Windows runner. --- .github/workflows/build-desktop-tool.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index f91dc1d09..8b927e06f 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -1,4 +1,4 @@ -name: Desktop tool macOS and Linux CI +name: Desktop tool build on: workflow_dispatch: @@ -40,6 +40,10 @@ jobs: target: linux filename: autofill-linux.bin macos-arch: "" + - os: windows-latest + target: windows + filename: autofill-windows.exe + macos-arch: "" steps: - uses: actions/checkout@v6 - name: Set up Python 3.13 @@ -73,6 +77,7 @@ jobs: file-version: 1.0.${{ github.run_number }} macos-target-arch: ${{ matrix.macos-arch }} - name: Smoke test bundled executable + shell: bash run: | env -u SSL_CERT_FILE -u SSL_CERT_DIR ./build/${{ matrix.filename }} --check-tls if [[ "$RUNNER_OS" == Linux ]]; then From 00f945f73df9559154375044c6e3e75b704c0fdd Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 17:28:34 -0700 Subject: [PATCH 71/76] Run the credentials check step under bash on Windows the step uses test/subshell syntax that pwsh cannot parse, which failed the first Windows build job. --- .github/workflows/build-desktop-tool.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index 8b927e06f..8f2176b5a 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -58,6 +58,7 @@ jobs: - name: Require Google Drive API credentials env: GOOGLE_DRIVE_API_KEY: ${{ secrets.GOOGLE_DRIVE_API_KEY }} + shell: bash run: test -n "$GOOGLE_DRIVE_API_KEY" || (echo "Add desktop-tool/client_secrets.json as the GOOGLE_DRIVE_API_KEY repository secret." && exit 1) - name: Write Google Drive API credentials to file uses: jsdaniell/create-json@v1.2.3 From c9190ff0b2d7dd40f233f9843fbd7a94f5c1679f Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Wed, 22 Jul 2026 23:43:15 -0700 Subject: [PATCH 72/76] Fix DTC browser version detection --- desktop-tool/src/webdrivers.py | 28 ++++++++++++++++++------- desktop-tool/tests/test_desktop_tool.py | 25 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/desktop-tool/src/webdrivers.py b/desktop-tool/src/webdrivers.py index aeddbf4db..58573c56c 100644 --- a/desktop-tool/src/webdrivers.py +++ b/desktop-tool/src/webdrivers.py @@ -97,29 +97,34 @@ def get_default_brave_binary_location() -> str: return default_binary_locations[sys.platform] -def _detect_chrome_version() -> Optional[int]: +def _detect_chrome_version(binary_location: Optional[str] = None) -> Optional[int]: """ - Detect the installed Chrome version by querying the browser. + Detect the selected Chromium browser version. Returns the major version number (e.g., 144) or None if detection fails. """ try: if sys.platform == "darwin": result = subprocess.run( - ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"], + [binary_location or "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"], capture_output=True, text=True, timeout=5, ) elif sys.platform == "win32": + registry_key = ( + r"HKEY_CURRENT_USER\Software\BraveSoftware\Brave-Browser\BLBeacon" + if binary_location and "brave" in binary_location.lower() + else r"HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" + ) result = subprocess.run( - ["reg", "query", r"HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon", "/v", "version"], + ["reg", "query", registry_key, "/v", "version"], capture_output=True, text=True, timeout=5, ) else: result = subprocess.run( - ["google-chrome", "--version"], + [binary_location or "google-chrome", "--version"], capture_output=True, text=True, timeout=5, @@ -146,6 +151,14 @@ def get_undetected_chrome_driver( """ import undetected_chromedriver as uc + if binary_location is None: + binary_location = uc.find_chrome_executable() + if binary_location is None: + raise FileNotFoundError( + "Google Chrome was not found. Install Chrome, choose Brave with --browser brave, " + "or specify a Chromium executable with --binary-location." + ) + options = uc.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--log-level=3") @@ -153,8 +166,7 @@ def get_undetected_chrome_driver( options.add_argument("--disable-blink-features=AutomationControlled") if headless: options.add_argument("--headless=new") - if binary_location is not None: - options.binary_location = binary_location + options.binary_location = binary_location if user_data_dir is not None: options.add_argument(f"--user-data-dir={user_data_dir}") if profile_directory is not None: @@ -162,4 +174,4 @@ def get_undetected_chrome_driver( # undetected-chromedriver handles stealth automatically. # Detect the Chrome version ourselves since auto-detection can fail. - return uc.Chrome(options=options, version_main=_detect_chrome_version()) + return uc.Chrome(options=options, version_main=_detect_chrome_version(binary_location)) diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index aec5e6d7f..ab66ac33c 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -415,19 +415,42 @@ def fake_chrome(*, options, version_main): captured["version_main"] = version_main return object() - monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda: 120) + monkeypatch.setattr(webdrivers, "_detect_chrome_version", lambda _: 120) monkeypatch.setattr("undetected_chromedriver.Chrome", fake_chrome) webdrivers.get_undetected_chrome_driver( + binary_location="/tmp/chrome", user_data_dir="/tmp/chrome-data", profile_directory="Profile 7", ) + assert captured["options"].binary_location == "/tmp/chrome" assert "--user-data-dir=/tmp/chrome-data" in captured["options"].arguments assert "--profile-directory=Profile 7" in captured["options"].arguments assert captured["version_main"] == 120 +def test_detect_chrome_version_uses_selected_brave_browser(monkeypatch: pytest.MonkeyPatch) -> None: + captured = {} + + def fake_run(args, **kwargs): + captured["args"] = args + return SimpleNamespace(stdout="version REG_SZ 150.1.92.143") + + monkeypatch.setattr(webdrivers.sys, "platform", "win32") + monkeypatch.setattr(webdrivers.subprocess, "run", fake_run) + + assert webdrivers._detect_chrome_version(r"C:\Program Files\BraveSoftware\brave.exe") == 150 + assert r"BraveSoftware\Brave-Browser\BLBeacon" in captured["args"][2] + + +def test_get_undetected_chrome_driver_reports_missing_chrome(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr("undetected_chromedriver.find_chrome_executable", lambda: None) + + with pytest.raises(FileNotFoundError, match="Google Chrome was not found.*--browser brave"): + webdrivers.get_undetected_chrome_driver() + + @pytest.mark.parametrize("browser", constants.Browsers) def test_standard_driver_factories_accept_only_upstream_kwargs(browser: constants.Browsers) -> None: # Regression test: passing DTC-only kwargs (user_data_dir etc.) to the standard factories From a6ec4ad4e085f008182d29904c2a8822b8679eaa Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Fri, 24 Jul 2026 20:06:55 -0700 Subject: [PATCH 73/76] Document DriveThruCards desktop workflow --- .../docs/Desktop-Tool.wiki.addendum.md | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 desktop-tool/docs/Desktop-Tool.wiki.addendum.md diff --git a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md new file mode 100644 index 000000000..b45e6bceb --- /dev/null +++ b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md @@ -0,0 +1,110 @@ +# Desktop Tool Wiki Addendum + +Target page: + +These updates describe the DriveThruCards workflow in the `drivethrucards` branch. They can be copied into the wiki when PR [#367](https://github.com/chilli-axe/mpc-autofill/pull/367) is merged. + +## Overview + +Replace the opening with: + +> This tool ingests XML files generated with this project's web frontend, and: +> +> - Downloads the images in your order from Google Drive (into the directory `/cards` from the executable's location), +> - Uses Selenium (browser automation) for Chromium browsers to prepare an order with a supported card printer. +> +> For MakePlayingCards and PrinterStudio, the tool fills the site's card editor one image at a time. For DriveThruCards, it creates a print-ready PDF/X-1a file and uploads it through the publisher tools. +> +> Once the autofilling process completes, you can review and pay for your order. MakePlayingCards and PrinterStudio projects can also be saved to your account for later. +> +> **Note**: Automated Chromium browsers do not support signing in with Google accounts for security reasons. Create an account with the printing site directly and sign in with it. + +## Running the Tool + +Add this item after the current release download: + +> - To use DriveThruCards before PR [#367](https://github.com/chilli-axe/mpc-autofill/pull/367) is merged, download the [latest DTC release](https://github.com/bwsinger/mpc-autofill/releases/latest). It includes Windows, Linux, Apple silicon Mac, and Intel Mac builds. + +## DriveThruCards Automation + +Add this section after "MakePlayingCards Automation": + +> ### DriveThruCards Automation +> +> - The tool downloads the images in your order and creates a PDF for DriveThruCards' Premium Euro Poker size. +> - It converts the PDF to PDF/X-1a:2001 with [Ghostscript](https://ghostscript.com/) before opening the DriveThruCards publisher tools. +> - If your DriveThruCards account does not have publisher access, the tool completes the non-exclusive publisher setup. +> - You sign in yourself. The tool then creates the product, uploads the PDF, and opens the checkout page for your review. It never submits payment. +> - Multiple XML files use the same browser session, with a prompt between orders. +> +> DriveThruCards requires Ghostscript. When it is missing, the tool asks before trying to install it with Homebrew, winget, apt, dnf, or yum. If you decline, install Ghostscript yourself and return to the prompt. +> +> For more consistent print colours, the tool looks for Adobe's US Web Coated (SWOP) ICC profile in the usual system folders and in `~/.mpc-autofill/`. If it cannot find the profile, it can download Adobe's end-user profile bundle, verify its SHA-256 checksum, and cache the profile. You can decline and continue with Ghostscript's default colour conversion. + +## Specifying a Site to Autofill Into (`--site`) + +Add DriveThruCards to the list of supported sites: + +> - [DriveThruCards](https://www.drivethrucards.com) +> +> DriveThruCards uses Chrome or Brave. If you select another browser, the tool falls back to Chrome and tells you before continuing. + +## PDF and Download Arguments + +Replace "Exporting to PDF" with the following sections: + +> ### Exporting to PDF (`--exportpdf`) +> +> Use `--exportpdf` to create PDF files without opening a browser or starting an order. +> +> With `--site DriveThruCards`, this creates the same PDF/X-1a file used by the automated DTC workflow, then exits. Ghostscript is still required. +> +> ### Downloading Images Only (`--download-images-only`) +> +> Use `--download-images-only` to download the card images into `cards/` and exit without creating a PDF or opening a browser. +> +> ### Reusing PDF Exports (`--skip-pdf-if-exists`) +> +> Use `--skip-pdf-if-exists` to reuse existing PDF exports. If an image in `cards/` is newer than the PDF, the tool asks whether to rebuild it. DriveThruCards orders only reuse an existing `_pdfx.pdf` file. + +## DriveThruCards Arguments + +Add these sections under "Command-Line Arguments": + +> ### Reusing a Chromium Profile (`--browser-profile-path`, `--browser-profile-name`) +> +> Use `--browser-profile-path` to open DriveThruCards with an existing Chromium user data directory. This makes saved cookies and password managers available in the automated browser. Use `--browser-profile-name` to select a profile within that directory. The default profile name is `Default`. +> +> Close any regular browser windows using that profile before starting the tool. Chromium does not allow two browser processes to use the same profile at once. +> +> ### Skipping DriveThruCards Instructions (`--skip-dtc-instructions`) +> +> DriveThruCards normally opens a short instruction page before the sign-in page. Use `--skip-dtc-instructions` to open DriveThruCards immediately. +> +> ### Specifying a DriveThruCards ICC Profile (`--dtc-icc-profile`) +> +> Use `--dtc-icc-profile` to provide your own `.icc` profile for DriveThruCards PDF/X conversion. If you omit it, the tool looks for the US Web Coated (SWOP) profile or offers to download it from Adobe. + +## Running the Test Suite + +Replace the existing commands with: + +> From the `desktop-tool` directory, run: +> +> ```shell +> pytest . +> ``` +> +> On Linux, run the suite in a D-Bus session: +> +> ```shell +> dbus-run-session -- pytest . +> ``` +> +> Tests that need Google Drive credentials skip themselves when `client_secrets.json` is not available. + +## Building the Project + +Replace the Nuitka build command's hyphen with a colon, then add: + +> The dispatchable desktop build workflow produces one-file builds for Windows, Linux, Apple silicon Macs, and Intel Macs. Each build runs `--check-tls` and checks that `--help` lists both MakePlayingCards and DriveThruCards before GitHub stores the artifact. From ed0da9c44ac627c6c03de6c84564776f1d092e2e Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sat, 25 Jul 2026 02:04:06 -0700 Subject: [PATCH 74/76] Document fork CI credential behavior --- desktop-tool/docs/Desktop-Tool.wiki.addendum.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md index b45e6bceb..4ebc89492 100644 --- a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md +++ b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md @@ -4,6 +4,23 @@ Target page: These updates describe the DriveThruCards workflow in the `drivethrucards` branch. They can be copied into the wiki when PR [#367](https://github.com/chilli-axe/mpc-autofill/pull/367) is merged. +## Wiki Home + +On the wiki Home page: + +- Describe the desktop tool as preparing orders for supported card printers, including MakePlayingCards and DriveThruCards. +- Replace the Google Drive CI warning with: + +> - GitHub Actions runs the backend and desktop tool test suites. Tests that need Google Drive credentials use the `GOOGLE_DRIVE_API_KEY` repository secret. +> - Pull requests from forks cannot access secrets stored in this repository. The desktop tool workflow still runs without the secret and skips only the credential-backed tests. +> - To run the complete desktop tool suite in your fork, add `GOOGLE_DRIVE_API_KEY` to the fork and run the workflow there. The secret's value is the full Google service-account JSON document, not a plain API key. + +## GitHub Repo Configuration + +Replace the `GOOGLE_DRIVE_API_KEY` description with: + +> The full Google service-account JSON document, despite the historical name. Used by GitHub Actions for credential-backed backend and desktop tool tests, and built into the desktop tool binary. Pull requests from forks cannot read the upstream secret. The desktop workflow skips credential-backed tests when it is unavailable. Fork owners can add their own copy to run the complete suite in their fork. + ## Overview Replace the opening with: From 9a04f50b52b3d401817275280851ed7486144c99 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Thu, 30 Jul 2026 10:47:47 -0700 Subject: [PATCH 75/76] Use pinned Ghostscript installer on Windows --- .github/actions/test-desktop-tool/action.yml | 2 + desktop-tool/autofill.py | 81 +++++++++++++++----- desktop-tool/src/pdf_maker.py | 5 ++ desktop-tool/tests/test_desktop_tool.py | 79 ++++++++++++++++--- 4 files changed, 140 insertions(+), 27 deletions(-) diff --git a/.github/actions/test-desktop-tool/action.yml b/.github/actions/test-desktop-tool/action.yml index ccb1396ee..2b2df5dc3 100644 --- a/.github/actions/test-desktop-tool/action.yml +++ b/.github/actions/test-desktop-tool/action.yml @@ -39,6 +39,8 @@ runs: dir: "desktop-tool/" - name: Run tests working-directory: desktop-tool + env: + MPC_AUTOFILL_RELEASE_CHECKS: "1" run: | if [[ "$RUNNER_OS" == Linux ]]; then dbus-run-session -- pytest . diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 5194a5d27..97b33dd8c 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -85,6 +85,18 @@ def prune_stale_onefile_caches(extraction_directory: str) -> None: DEFAULT_AUTO_SAVE = True DEFAULT_IMAGE_POST_PROCESSING = True TLS_CHECK_URL = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json" +GHOSTSCRIPT_DOWNLOAD_PAGE = "https://ghostscript.com/releases/gsdnld.html" +GHOSTSCRIPT_VERSION = "10.07.1" +GHOSTSCRIPT_WINDOWS_INSTALLERS = { + "w32.exe": ( + "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10071/gs10071w32.exe", + "2dc44e339e2a50d96827e199a234713604ac04a5cec2e07bd452cc92e8d6f81b", + ), + "w64.exe": ( + "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10071/gs10071w64.exe", + "3a4c28d0aac47aa7cccd35a5932c55110376e9dbd966898dde388b7faba444a4", + ), +} def get_browser_picker_choices() -> list[str]: @@ -161,47 +173,80 @@ def wait_for_user_to_complete_order() -> None: ) -def _install_ghostscript() -> None: +def _install_ghostscript_windows() -> bool: + import hashlib + import tempfile + from urllib.request import Request, urlopen + + suffix = "w64.exe" if sys.maxsize > 2**32 else "w32.exe" + download_url, expected_digest = GHOSTSCRIPT_WINDOWS_INSTALLERS[suffix] + + logger.info(f"Downloading Ghostscript {GHOSTSCRIPT_VERSION} from Artifex...") + with tempfile.TemporaryDirectory() as directory: + installer_path = os.path.join(directory, os.path.basename(download_url)) + download = Request(download_url, headers={"User-Agent": "mpc-autofill"}) + with urlopen(download, timeout=60) as response, open(installer_path, "wb") as installer: + shutil.copyfileobj(response, installer) + with open(installer_path, "rb") as installer: + actual_digest = hashlib.file_digest(installer, "sha256").hexdigest() + if actual_digest != expected_digest: + raise RuntimeError("The downloaded Ghostscript installer failed checksum verification.") + + command = ( + "$process = Start-Process -FilePath $env:MPC_AUTOFILL_GS_INSTALLER " + "-Verb RunAs -Wait -PassThru; exit $process.ExitCode" + ) + environment = os.environ.copy() + environment["MPC_AUTOFILL_GS_INSTALLER"] = installer_path + return ( + subprocess.run( + ["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command], + check=False, + env=environment, + ).returncode + == 0 + ) + + +def _install_ghostscript() -> bool: """ - Attempt to install Ghostscript with the platform's package manager, logging (not raising) failures - - the caller re-checks whether Ghostscript is available afterwards. + Attempt to install Ghostscript, logging failures and returning whether the installer succeeded. """ if sys.platform.startswith("darwin"): if shutil.which("brew") is None: logger.info("Homebrew not found. Please install Homebrew, then re-run.") + return False else: logger.info("Installing Ghostscript via Homebrew...") result = subprocess.run(["brew", "install", "ghostscript"], check=False) if result.returncode != 0: logger.warning("Ghostscript installation via Homebrew failed.") + return result.returncode == 0 elif sys.platform.startswith("win"): - if shutil.which("winget") is None: - logger.info( - "winget not found. Please install Ghostscript from " - "https://ghostscript.com/releases/gsdnld.html and ensure it's on PATH." - ) - else: - logger.info("Installing Ghostscript via winget...") - result = subprocess.run( - ["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"], - check=False, - ) - if result.returncode != 0: - logger.warning("Ghostscript installation via winget failed.") + try: + if _install_ghostscript_windows(): + return True + except Exception as error: + logger.warning(f"Ghostscript installation failed: {error}") + logger.info(f"Please install Ghostscript from {GHOSTSCRIPT_DOWNLOAD_PAGE}.") + return False else: package_manager = next( (candidate for candidate in ["apt", "dnf", "yum"] if shutil.which(candidate) is not None), None ) if shutil.which("sudo") is None: logger.info("sudo not found. Please install Ghostscript with your package manager manually.") + return False elif package_manager is None: logger.info("No supported package manager found. Please install Ghostscript manually.") + return False else: logger.info(f"Installing Ghostscript via {package_manager}...") result = subprocess.run(["sudo", package_manager, "install", "-y", "ghostscript"], check=False) if result.returncode != 0: logger.warning(f"Ghostscript installation via {package_manager} failed.") + return result.returncode == 0 def ensure_ghostscript_available() -> str: @@ -221,8 +266,8 @@ def ensure_ghostscript_available() -> str: logger.info("DriveThruCards export requires Ghostscript for PDF/X-1a compliance.") if click.confirm("Is it okay if MPC Autofill tries to install Ghostscript now?", default=True): - _install_ghostscript() - continue + if _install_ghostscript(): + continue logger.info( "Please install Ghostscript, then return here to continue.\n" diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 489d95085..d728d7bfb 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -1,6 +1,7 @@ import os import shutil import subprocess +import sys import tempfile from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass @@ -44,6 +45,10 @@ def get_ghostscript_path(explicit_path: Optional[str] = None) -> Optional[str]: for candidate in ["gs", "gswin64c", "gswin32c"]: if resolved := shutil.which(candidate): return resolved + if sys.platform == "win32": + installs = list(Path(os.environ.get("ProgramFiles", r"C:\Program Files")).glob("gs/gs*/bin/gswin*c.exe")) + if installs: + return str(max(installs, key=os.path.getmtime)) return None diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index ab66ac33c..ba0e3750b 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -1,4 +1,5 @@ import gc +import hashlib import inspect import logging import os @@ -9,6 +10,7 @@ import time from concurrent.futures import ThreadPoolExecutor from contextlib import nullcontext +from io import BytesIO from itertools import groupby from pathlib import Path from queue import Queue @@ -44,6 +46,7 @@ PdfExporter, PdfXConversionConfig, convert_pdf_to_pdfx, + get_ghostscript_path, get_ghostscript_version, ) from src.processing import ImagePostProcessingConfig @@ -134,24 +137,35 @@ def fake_get_version(_path: str) -> str: assert called["version"] == 1 -def test_ensure_ghostscript_available_installs_with_winget_on_windows( +def test_ensure_ghostscript_available_installs_official_release_on_windows( monkeypatch: pytest.MonkeyPatch, ) -> None: paths = [None, "C:\\Program Files\\gs\\gswin64c.exe"] install_calls = [] + installer = b"official Ghostscript installer" + asset_name = "gs10071w64.exe" + download_url = f"https://example.test/{asset_name}" monkeypatch.setattr(autofill_cli.sys, "platform", "win32", raising=False) + monkeypatch.setattr(autofill_cli.sys, "maxsize", 2**63 - 1) + monkeypatch.setitem( + autofill_cli.GHOSTSCRIPT_WINDOWS_INSTALLERS, + "w64.exe", + (download_url, hashlib.sha256(installer).hexdigest()), + ) monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: paths.pop(0)) monkeypatch.setattr(autofill_cli, "get_ghostscript_version", lambda _path: "10.0.0") monkeypatch.setattr(autofill_cli.click, "confirm", lambda *_args, **_kwargs: True) monkeypatch.setattr("builtins.input", lambda *_args, **_kwargs: "\n") - monkeypatch.setattr( - autofill_cli.shutil, - "which", - lambda name: "winget" if name == "winget" else None, - ) - def fake_run(cmd, check=False): + def fake_urlopen(request, timeout): + assert request.full_url == download_url + return BytesIO(installer) + + monkeypatch.setattr("urllib.request.urlopen", fake_urlopen) + + def fake_run(cmd, check=False, env=None): + assert Path(env["MPC_AUTOFILL_GS_INSTALLER"]).read_bytes() == installer install_calls.append(cmd) return SimpleNamespace(returncode=0) @@ -160,7 +174,54 @@ def fake_run(cmd, check=False): resolved = autofill_cli.ensure_ghostscript_available() assert resolved == "C:\\Program Files\\gs\\gswin64c.exe" - assert install_calls == [["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"]] + assert len(install_calls) == 1 + assert install_calls[0][0] == "powershell.exe" + assert "Start-Process" in install_calls[0][-1] + + +def test_install_ghostscript_windows_rejects_wrong_checksum(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(autofill_cli.sys, "maxsize", 2**63 - 1) + monkeypatch.setitem( + autofill_cli.GHOSTSCRIPT_WINDOWS_INSTALLERS, + "w64.exe", + ("https://example.test/gs.exe", "0" * 64), + ) + monkeypatch.setattr("urllib.request.urlopen", lambda *_args, **_kwargs: BytesIO(b"modified")) + monkeypatch.setattr( + autofill_cli.subprocess, + "run", + lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("unverified installer must not run")), + ) + + with pytest.raises(RuntimeError, match="checksum"): + autofill_cli._install_ghostscript_windows() + + +@pytest.mark.skipif( + os.environ.get("MPC_AUTOFILL_RELEASE_CHECKS") != "1", + reason="live release dependency check", +) +@pytest.mark.parametrize( + "download_url", + [installer[0] for installer in autofill_cli.GHOSTSCRIPT_WINDOWS_INSTALLERS.values()], +) +def test_pinned_ghostscript_installers_are_available(download_url: str) -> None: + from urllib.request import Request, urlopen + + request = Request(download_url, method="HEAD", headers={"User-Agent": "mpc-autofill"}) + with urlopen(request, timeout=30) as response: + assert response.status == 200 + + +def test_get_ghostscript_path_finds_standard_windows_install(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + executable = tmp_path / "gs" / "gs10.07.1" / "bin" / "gswin64c.exe" + executable.parent.mkdir(parents=True) + executable.touch() + monkeypatch.setattr("src.pdf_maker.shutil.which", lambda _name: None) + monkeypatch.setattr("src.pdf_maker.sys.platform", "win32") + monkeypatch.setenv("ProgramFiles", str(tmp_path)) + + assert get_ghostscript_path() == str(executable) def test_ensure_ghostscript_available_installs_with_apt_on_linux( @@ -204,7 +265,7 @@ def fake_confirm(message: str, default: bool = True) -> bool: monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: paths.pop(0)) monkeypatch.setattr(autofill_cli, "get_ghostscript_version", lambda _path: "10.0.0") monkeypatch.setattr(autofill_cli.click, "confirm", fake_confirm) - monkeypatch.setattr(autofill_cli, "_install_ghostscript", lambda: None) + monkeypatch.setattr(autofill_cli, "_install_ghostscript", lambda: True) assert autofill_cli.ensure_ghostscript_available() == "/usr/local/bin/gs" assert "install Ghostscript now" in asked["message"] From 833ca4063718c743d8aea14df03341253c6233bb Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Thu, 30 Jul 2026 10:48:42 -0700 Subject: [PATCH 76/76] Parameterize desktop release version --- .github/workflows/build-desktop-tool.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop-tool.yml b/.github/workflows/build-desktop-tool.yml index 8f2176b5a..0ef8659ca 100644 --- a/.github/workflows/build-desktop-tool.yml +++ b/.github/workflows/build-desktop-tool.yml @@ -2,6 +2,11 @@ name: Desktop tool build on: workflow_dispatch: + inputs: + version: + description: Release version embedded in the executable (for example, 1.1.0) + required: true + type: string jobs: test: @@ -75,7 +80,7 @@ jobs: mode: onefile output-file: ${{ matrix.filename }} # required by the versioned onefile cache directory ({VERSION} in --onefile-tempdir-spec) - file-version: 1.0.${{ github.run_number }} + file-version: ${{ inputs.version }} macos-target-arch: ${{ matrix.macos-arch }} - name: Smoke test bundled executable shell: bash

g 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%0AjX_uhU=1YP`)2{^$H~tbd+yH`x>;KMP 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⁡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 z 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 diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 764c51a95..eefcc4c9c 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -3,8 +3,6 @@ # nuitka-project: --include-data-files=post-launch.html=post-launch.html # nuitka-project: --include-data-files=dtc-post-launch.html=dtc-post-launch.html # nuitka-project: --include-data-dir=assets=assets -# nuitka-project: --include-data-files=assets/icc/USWebCoatedSWOP.icc=assets/icc/USWebCoatedSWOP.icc -# nuitka-project: --include-data-files=assets/icc/Adobe-Color-Profile-EULA.pdf=assets/icc/Adobe-Color-Profile-EULA.pdf # nuitka-project: --noinclude-pytest-mode=nofollow # nuitka-project: --windows-icon-from-ico=favicon.ico # nuitka-project-if: {OS} == "Windows": @@ -27,7 +25,6 @@ from concurrent.futures import ThreadPoolExecutor from contextlib import nullcontext from glob import glob -from pathlib import Path from typing import Optional, Union import click @@ -44,6 +41,7 @@ from src.driver import AutofillDriver from src.exc import ValidationException from src.formatting import bold +from src.icc import find_or_download_dtc_icc_profile from src.io import ( DEFAULT_WORKING_DIRECTORY, create_image_directory_if_not_exists, @@ -72,14 +70,6 @@ def prompt_if_no_arguments(prompt: str) -> Union[str, bool]: return f"{prompt} (Press Enter if you're not sure.)" if len(sys.argv) == 1 else False -def get_default_dtc_icc_profile() -> Optional[str]: - if "__compiled__" in globals(): - candidate = Path(sys.argv[0]).resolve().parent / "assets/icc/USWebCoatedSWOP.icc" - else: - candidate = Path(__file__).resolve().parent / "assets/icc/USWebCoatedSWOP.icc" - return str(candidate) if candidate.is_file() else None - - def wait_for_user_to_complete_order() -> None: input( f"If this software has brought you joy and you'd like to throw a few bucks my way,\n" @@ -250,7 +240,7 @@ def get_dtc_pdf_paths_for_order( order: CardOrder, skip_pdf_if_exists: bool, working_directory: str, - resolved_icc_profile: str, + resolved_icc_profile: Optional[str], downscale_alg: str, ) -> list[str]: pdf_paths = maybe_reuse_existing_pdfs( @@ -396,7 +386,10 @@ def get_site_picker_choices() -> list[str]: @click.option( "--dtc-icc-profile", default=None, - help="Optional ICC profile path for DriveThruCards PDF/X conversion (defaults to bundled profile).", + help=( + "Optional ICC profile path for DriveThruCards PDF/X conversion " + "(by default, an installed US Web Coated (SWOP) profile is located or downloaded from Adobe)." + ), ) @click.option( "--combine-orders/--no-combine-orders", @@ -508,19 +501,18 @@ def main( "Please install it and re-run when ready." ) ensure_ghostscript_available(ask_for_install_confirmation=not gs_missing) - using_default_icc = dtc_icc_profile is None - resolved_icc_profile = dtc_icc_profile or get_default_dtc_icc_profile() - if resolved_icc_profile is None: + if dtc_icc_profile and not os.path.isfile(dtc_icc_profile): raise Exception( - "Default DriveThruCards ICC profile was not found. " - "Ensure assets/icc/USWebCoatedSWOP.icc is available or pass --dtc-icc-profile." + f"DriveThruCards ICC profile path does not exist or is not a file: {bold(dtc_icc_profile)}" ) - if resolved_icc_profile and not os.path.isfile(resolved_icc_profile): - raise Exception( - f"DriveThruCards ICC profile path does not exist or is not a file: {bold(resolved_icc_profile)}" + resolved_icc_profile = dtc_icc_profile or find_or_download_dtc_icc_profile() + if resolved_icc_profile is None: + logger.warning( + "No ICC profile is available - Ghostscript's default CMYK conversion will be used " + "instead. Print colours may differ from previous orders." ) - icc_source = "bundled default" if using_default_icc else "user-provided" - logger.info(f"DriveThruCards ICC profile ({icc_source}): {bold(resolved_icc_profile)}") + else: + logger.info(f"DriveThruCards ICC profile: {bold(resolved_icc_profile)}") orders = CardOrder.from_xmls_in_folder(working_directory=working_directory) dtc_driver: Optional[AutofillDriver] = None dtc_web_server: Optional[WebServer] = None diff --git a/desktop-tool/src/icc.py b/desktop-tool/src/icc.py new file mode 100644 index 000000000..19f526f85 --- /dev/null +++ b/desktop-tool/src/icc.py @@ -0,0 +1,91 @@ +""" +Locate or download the US Web Coated (SWOP) ICC profile used for DriveThruCards PDF/X-1a output. + +Adobe's Color Profile License Agreement permits using the profile and embedding it in image files, +but not redistributing it bundled with application software - so instead of shipping the profile, +we look for a copy already installed on this system and otherwise download Adobe's own end-user +bundle (with the user's consent, accepting Adobe's license terms directly). +""" + +import hashlib +import io +import os +import sys +import zipfile +from pathlib import Path +from typing import Optional + +import click +import requests + +from src.formatting import bold +from src.logging import logger + +ICC_PROFILE_FILENAME = "USWebCoatedSWOP.icc" +ADOBE_ICC_BUNDLE_URL = "https://download.adobe.com/pub/adobe/iccprofiles/win/AdobeICCProfilesCS4Win_end-user.zip" +ADOBE_ICC_BUNDLE_MEMBER = f"Adobe ICC Profiles (end-user)/CMYK/{ICC_PROFILE_FILENAME}" +ADOBE_ICC_LICENSE_URL = "https://www.adobe.com/support/downloads/iccprofiles/icc_eula_win_end.html" +ICC_PROFILE_SHA256 = "35f401731df11a4eba3502af632e51d68bc394bcb7d34632a331c1ba3f4a0bf6" + + +def get_profile_cache_path() -> Path: + return Path.home() / ".mpc-autofill" / ICC_PROFILE_FILENAME + + +def _candidate_profile_paths() -> list[Path]: + home = Path.home() + if sys.platform == "darwin": + directories = [ + Path("/Library/Application Support/Adobe/Color/Profiles/Recommended"), + Path("/Library/ColorSync/Profiles"), + home / "Library/ColorSync/Profiles", + ] + elif sys.platform == "win32": + system_root = Path(os.environ.get("SystemRoot", r"C:\Windows")) + program_files = Path(os.environ.get("ProgramFiles", r"C:\Program Files")) + directories = [ + system_root / "System32/spool/drivers/color", + program_files / "Common Files/Adobe/Color/Profiles/Recommended", + ] + else: + directories = [Path("/usr/share/color/icc"), home / ".local/share/icc", home / ".color/icc"] + return [directory / ICC_PROFILE_FILENAME for directory in directories] + [get_profile_cache_path()] + + +def _download_profile() -> Optional[str]: + logger.info(f"Downloading the Adobe ICC profile bundle from {bold(ADOBE_ICC_BUNDLE_URL)}...") + try: + response = requests.get(ADOBE_ICC_BUNDLE_URL, timeout=120) + response.raise_for_status() + profile_bytes = zipfile.ZipFile(io.BytesIO(response.content)).read(ADOBE_ICC_BUNDLE_MEMBER) + except Exception as exc: + logger.warning(f"Failed to download the ICC profile: {exc}") + return None + if hashlib.sha256(profile_bytes).hexdigest() != ICC_PROFILE_SHA256: + logger.warning("The downloaded ICC profile did not match the expected checksum - not using it.") + return None + cache_path = get_profile_cache_path() + cache_path.parent.mkdir(parents=True, exist_ok=True) + cache_path.write_bytes(profile_bytes) + logger.info(f"ICC profile saved to {bold(str(cache_path))}.") + return str(cache_path) + + +def find_or_download_dtc_icc_profile() -> Optional[str]: + """ + Return a path to the US Web Coated (SWOP) ICC profile, or None if it's unavailable + (not installed, and the user declined or the download failed). + """ + for candidate in _candidate_profile_paths(): + if candidate.is_file(): + return str(candidate) + logger.info( + "DriveThruCards colour conversion works best with the US Web Coated (SWOP) ICC profile, " + "which was not found on this system." + ) + if not click.confirm( + f"Download it from Adobe now? (Subject to Adobe's license terms: {ADOBE_ICC_LICENSE_URL})", + default=True, + ): + return None + return _download_profile() diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index c7709a930..3f7411542 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -418,7 +418,7 @@ def test_main_drive_thru_cards_exportpdf_generates_pdfs_without_browser_automati monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/opt/homebrew/bin/gs") monkeypatch.setattr(autofill_cli, "ensure_ghostscript_available", lambda **_kwargs: "/opt/homebrew/bin/gs") - monkeypatch.setattr(autofill_cli, "get_default_dtc_icc_profile", lambda: str(icc_path)) + monkeypatch.setattr(autofill_cli, "find_or_download_dtc_icc_profile", lambda: str(icc_path)) monkeypatch.setattr( autofill_cli.CardOrder, "from_xmls_in_folder", @@ -481,7 +481,7 @@ def test_main_drive_thru_cards_keeps_driver_alive_until_user_handoff( monkeypatch.setattr(autofill_cli, "keepawake", lambda **_kwargs: nullcontext()) monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/opt/homebrew/bin/gs") monkeypatch.setattr(autofill_cli, "ensure_ghostscript_available", lambda **_kwargs: "/opt/homebrew/bin/gs") - monkeypatch.setattr(autofill_cli, "get_default_dtc_icc_profile", lambda: str(icc_path)) + monkeypatch.setattr(autofill_cli, "find_or_download_dtc_icc_profile", lambda: str(icc_path)) monkeypatch.setattr( autofill_cli.CardOrder, "from_xmls_in_folder", @@ -596,6 +596,69 @@ def test_wiki_addendum_includes_cli_usage_updates() -> None: assert "confirmation before attempting Ghostscript installation" in wiki +# endregion + +# region ICC profile resolution + + +def test_find_or_download_dtc_icc_profile_prefers_installed_copy(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + installed_profile = tmp_path / "USWebCoatedSWOP.icc" + installed_profile.write_bytes(b"icc") + monkeypatch.setattr(src.icc, "_candidate_profile_paths", lambda: [tmp_path / "missing.icc", installed_profile]) + monkeypatch.setattr( + src.icc.requests, "get", lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("no download")) + ) + + assert src.icc.find_or_download_dtc_icc_profile() == str(installed_profile) + + +def test_find_or_download_dtc_icc_profile_returns_none_when_download_declined( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + monkeypatch.setattr(src.icc, "_candidate_profile_paths", lambda: [tmp_path / "missing.icc"]) + monkeypatch.setattr(src.icc.click, "confirm", lambda *_args, **_kwargs: False) + + assert src.icc.find_or_download_dtc_icc_profile() is None + + +def _fake_adobe_bundle_response(profile_bytes: bytes) -> SimpleNamespace: + import io as io_module + import zipfile + + buffer = io_module.BytesIO() + with zipfile.ZipFile(buffer, "w") as bundle: + bundle.writestr(src.icc.ADOBE_ICC_BUNDLE_MEMBER, profile_bytes) + return SimpleNamespace(content=buffer.getvalue(), raise_for_status=lambda: None) + + +def test_find_or_download_dtc_icc_profile_downloads_and_caches(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + import hashlib + + profile_bytes = b"fake icc profile" + monkeypatch.setattr(src.icc, "_candidate_profile_paths", lambda: [tmp_path / "missing.icc"]) + monkeypatch.setattr(src.icc, "get_profile_cache_path", lambda: tmp_path / "cache" / "USWebCoatedSWOP.icc") + monkeypatch.setattr(src.icc.click, "confirm", lambda *_args, **_kwargs: True) + monkeypatch.setattr(src.icc, "ICC_PROFILE_SHA256", hashlib.sha256(profile_bytes).hexdigest()) + monkeypatch.setattr(src.icc.requests, "get", lambda *_args, **_kwargs: _fake_adobe_bundle_response(profile_bytes)) + + resolved = src.icc.find_or_download_dtc_icc_profile() + + assert resolved == str(tmp_path / "cache" / "USWebCoatedSWOP.icc") + assert (tmp_path / "cache" / "USWebCoatedSWOP.icc").read_bytes() == profile_bytes + + +def test_find_or_download_dtc_icc_profile_rejects_checksum_mismatch( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + monkeypatch.setattr(src.icc, "_candidate_profile_paths", lambda: [tmp_path / "missing.icc"]) + monkeypatch.setattr(src.icc, "get_profile_cache_path", lambda: tmp_path / "cache" / "USWebCoatedSWOP.icc") + monkeypatch.setattr(src.icc.click, "confirm", lambda *_args, **_kwargs: True) + monkeypatch.setattr(src.icc.requests, "get", lambda *_args, **_kwargs: _fake_adobe_bundle_response(b"tampered")) + + assert src.icc.find_or_download_dtc_icc_profile() is None + assert not (tmp_path / "cache" / "USWebCoatedSWOP.icc").exists() + + # endregion # region PDF/X conversion From 5c0f2709e9a8fdb9e83696d7e21c531f5969de53 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 01:58:28 -0700 Subject: [PATCH 47/76] Collapse Ghostscript setup to one consent-gated install prompt The DriveThruCards branch previously combined confirm_ghostscript_ install_for_dtc, a gs_missing flag and ensure_ghostscript_available's ask_for_install_confirmation parameter, producing a confusing double prompt. Now a single detect -> ask -> install -> re-check loop asks for explicit permission before any package manager runs; declining prints per-platform install instructions instead. --- desktop-tool/autofill.py | 126 +++++++++++------------- desktop-tool/tests/test_desktop_tool.py | 14 ++- 2 files changed, 66 insertions(+), 74 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index eefcc4c9c..089b95959 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -78,15 +78,54 @@ def wait_for_user_to_complete_order() -> None: ) -def confirm_ghostscript_install_for_dtc() -> bool: - return click.confirm( - "DriveThruCards requires Ghostscript for PDF/X-1a export. " - "Is it okay if MPC Autofill tries to install Ghostscript now?", - default=True, - ) +def _install_ghostscript() -> None: + """ + Attempt to install Ghostscript with the platform's package manager, logging (not raising) failures - + the caller re-checks whether Ghostscript is available afterwards. + """ + + if sys.platform.startswith("darwin"): + if shutil.which("brew") is None: + logger.info("Homebrew not found. Please install Homebrew, then re-run.") + else: + logger.info("Installing Ghostscript via Homebrew...") + result = subprocess.run(["brew", "install", "ghostscript"], check=False) + if result.returncode != 0: + logger.warning("Ghostscript installation via Homebrew failed.") + elif sys.platform.startswith("win"): + if shutil.which("winget") is None: + logger.info( + "winget not found. Please install Ghostscript from " + "https://ghostscript.com/releases/gsdnld.html and ensure it's on PATH." + ) + else: + logger.info("Installing Ghostscript via winget...") + result = subprocess.run( + ["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"], + check=False, + ) + if result.returncode != 0: + logger.warning("Ghostscript installation via winget failed.") + else: + package_manager = next( + (candidate for candidate in ["apt", "dnf", "yum"] if shutil.which(candidate) is not None), None + ) + if shutil.which("sudo") is None: + logger.info("sudo not found. Please install Ghostscript with your package manager manually.") + elif package_manager is None: + logger.info("No supported package manager found. Please install Ghostscript manually.") + else: + logger.info(f"Installing Ghostscript via {package_manager}...") + result = subprocess.run(["sudo", package_manager, "install", "-y", "ghostscript"], check=False) + if result.returncode != 0: + logger.warning(f"Ghostscript installation via {package_manager} failed.") -def ensure_ghostscript_available(ask_for_install_confirmation: bool = True) -> str: +def ensure_ghostscript_available() -> str: + """ + Detect Ghostscript, offering to install it (with the user's explicit consent) until it's available. + """ + while True: gs_path = get_ghostscript_path() if gs_path: @@ -98,61 +137,16 @@ def ensure_ghostscript_available(ask_for_install_confirmation: bool = True) -> s return gs_path logger.info("DriveThruCards export requires Ghostscript for PDF/X-1a compliance.") - - should_install = True - if ask_for_install_confirmation: - should_install = click.confirm("Install Ghostscript now?", default=True) - if should_install: - if sys.platform.startswith("darwin"): - if shutil.which("brew") is None: - logger.info("Homebrew not found. Please install Homebrew, then re-run.") - else: - logger.info("Installing Ghostscript via Homebrew...") - result = subprocess.run(["brew", "install", "ghostscript"], check=False) - if result.returncode != 0: - logger.warning("Ghostscript installation via Homebrew failed.") - elif sys.platform.startswith("win"): - if shutil.which("winget") is None: - logger.info( - "winget not found. Please install Ghostscript from " - "https://ghostscript.com/releases/gsdnld.html and ensure it's on PATH." - ) - else: - logger.info("Installing Ghostscript via winget...") - result = subprocess.run( - ["winget", "install", "--id", "ArtifexSoftware.Ghostscript", "--accept-source-agreements"], - check=False, - ) - if result.returncode != 0: - logger.warning("Ghostscript installation via winget failed.") - else: - if shutil.which("sudo") is None: - logger.info("sudo not found. Please install Ghostscript with your package manager manually.") - elif shutil.which("apt") is not None: - logger.info("Installing Ghostscript via apt...") - result = subprocess.run(["sudo", "apt", "install", "-y", "ghostscript"], check=False) - if result.returncode != 0: - logger.warning("Ghostscript installation via apt failed.") - elif shutil.which("dnf") is not None: - logger.info("Installing Ghostscript via dnf...") - result = subprocess.run(["sudo", "dnf", "install", "-y", "ghostscript"], check=False) - if result.returncode != 0: - logger.warning("Ghostscript installation via dnf failed.") - elif shutil.which("yum") is not None: - logger.info("Installing Ghostscript via yum...") - result = subprocess.run(["sudo", "yum", "install", "-y", "ghostscript"], check=False) - if result.returncode != 0: - logger.warning("Ghostscript installation via yum failed.") - else: - logger.info("No supported package manager found. Please install Ghostscript manually.") - else: - logger.info( - "Please install Ghostscript, then return here to continue.\n" - "macOS: brew install ghostscript\n" - "Windows: https://ghostscript.com/releases/gsdnld.html\n" - "Linux: use your package manager (e.g., apt install ghostscript)." - ) - + if click.confirm("Is it okay if MPC Autofill tries to install Ghostscript now?", default=True): + _install_ghostscript() + continue + + logger.info( + "Please install Ghostscript, then return here to continue.\n" + "macOS: brew install ghostscript\n" + "Windows: https://ghostscript.com/releases/gsdnld.html\n" + "Linux: use your package manager (e.g., apt install ghostscript)." + ) input("Press Enter to re-check for Ghostscript, or Ctrl+C to exit.") @@ -494,13 +488,7 @@ def main( download_images_for_orders(orders=orders, post_processing_config=post_processing_config) return if target_site == TargetSites.DriveThruCards: - gs_missing = get_ghostscript_path() is None - if gs_missing and not confirm_ghostscript_install_for_dtc(): - raise Exception( - "Ghostscript is required for DriveThruCards exports. " - "Please install it and re-run when ready." - ) - ensure_ghostscript_available(ask_for_install_confirmation=not gs_missing) + ensure_ghostscript_available() if dtc_icc_profile and not os.path.isfile(dtc_icc_profile): raise Exception( f"DriveThruCards ICC profile path does not exist or is not a file: {bold(dtc_icc_profile)}" diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index 3f7411542..a3e317b09 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -179,7 +179,8 @@ def fake_run(cmd, check=False): assert install_calls == [["sudo", "apt", "install", "-y", "ghostscript"]] -def test_confirm_ghostscript_install_for_dtc_uses_requirement_prompt(monkeypatch: pytest.MonkeyPatch) -> None: +def test_ensure_ghostscript_available_asks_permission_before_installing(monkeypatch: pytest.MonkeyPatch) -> None: + paths = [None, "/usr/local/bin/gs"] asked = {"message": None, "default": None} def fake_confirm(message: str, default: bool = True) -> bool: @@ -187,14 +188,17 @@ def fake_confirm(message: str, default: bool = True) -> bool: asked["default"] = default return True + monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: paths.pop(0)) + monkeypatch.setattr(autofill_cli, "get_ghostscript_version", lambda _path: "10.0.0") monkeypatch.setattr(autofill_cli.click, "confirm", fake_confirm) + monkeypatch.setattr(autofill_cli, "_install_ghostscript", lambda: None) - assert autofill_cli.confirm_ghostscript_install_for_dtc() is True - assert "DriveThruCards requires Ghostscript" in asked["message"] + assert autofill_cli.ensure_ghostscript_available() == "/usr/local/bin/gs" + assert "install Ghostscript now" in asked["message"] assert asked["default"] is True -def test_ensure_ghostscript_available_skips_install_confirmation_when_preapproved( +def test_ensure_ghostscript_available_does_not_prompt_when_already_installed( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr(autofill_cli, "get_ghostscript_path", lambda: "/usr/local/bin/gs") @@ -205,7 +209,7 @@ def test_ensure_ghostscript_available_skips_install_confirmation_when_preapprove lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("should not prompt")), ) - assert autofill_cli.ensure_ghostscript_available(ask_for_install_confirmation=False) == "/usr/local/bin/gs" + assert autofill_cli.ensure_ghostscript_available() == "/usr/local/bin/gs" def test_maybe_reuse_existing_pdfs_returns_none_when_skip_disabled(tmp_path) -> None: From 70b1db2eb89875b07c8284533990fcf754a07657 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 01:59:47 -0700 Subject: [PATCH 48/76] Remove dead code - save_processed_image_to_bytes: never called - MM_PER_INCH: never read - unused io import in pdf_maker - DriveThruCardsSelectors upload/quantity/cart/continue fields and DriveThruCardsSite.base_url: never read - merge _resolve_ghostscript_path into get_ghostscript_path (the public function was a pass-through wrapper) --- desktop-tool/src/constants.py | 13 +------------ desktop-tool/src/pdf_maker.py | 9 ++------- desktop-tool/src/processing.py | 16 ---------------- desktop-tool/tests/test_desktop_tool.py | 6 +++--- 4 files changed, 6 insertions(+), 38 deletions(-) diff --git a/desktop-tool/src/constants.py b/desktop-tool/src/constants.py index 7b964f6d0..d007b82f0 100644 --- a/desktop-tool/src/constants.py +++ b/desktop-tool/src/constants.py @@ -155,11 +155,6 @@ def accept_settings_url(self) -> str: @attr.s class DriveThruCardsSelectors: product_url: str = attr.ib() - pdf_upload_input_selector: str = attr.ib(default="input[type='file']") - pdf_upload_input_index: int = attr.ib(default=0) - quantity_selector: str = attr.ib(default="") - add_to_cart_selector: str = attr.ib(default="") - continue_selector: str = attr.ib(default="") # Opening the login modal lets the user choose sign-in or account creation. login_button_selector: str = attr.ib(default="button[data-cy='login']") authenticated_indicator_selector: str = attr.ib( @@ -171,7 +166,6 @@ class DriveThruCardsSelectors: @attr.s class DriveThruCardsSite: - base_url: str = attr.ib(default="https://www.drivethrucards.com") selectors: DriveThruCardsSelectors = attr.ib( default=attr.Factory(lambda: DriveThruCardsSelectors(product_url="https://www.drivethrucards.com")) ) @@ -243,12 +237,7 @@ class TargetSites(Enum): Cardstocks.P10: "Plastique (100%)", }, ) - DriveThruCards = DriveThruCardsSite( - selectors=DriveThruCardsSelectors( - product_url="https://www.drivethrucards.com", - pdf_upload_input_selector="input[type='file']", - ) - ) + DriveThruCards = DriveThruCardsSite() DPI_HEIGHT_RATIO = 300 / 1110 # TODO: share this between desktop tool and backend diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 946246ea5..50eec5444 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -1,4 +1,3 @@ -import io import os import shutil import subprocess @@ -33,7 +32,7 @@ class PdfXConversionConfig: ghostscript_path: Optional[str] = None -def _resolve_ghostscript_path(explicit_path: Optional[str]) -> Optional[str]: +def get_ghostscript_path(explicit_path: Optional[str] = None) -> Optional[str]: if explicit_path: return explicit_path for candidate in ["gs", "gswin64c", "gswin32c"]: @@ -42,10 +41,6 @@ def _resolve_ghostscript_path(explicit_path: Optional[str]) -> Optional[str]: return None -def get_ghostscript_path(explicit_path: Optional[str] = None) -> Optional[str]: - return _resolve_ghostscript_path(explicit_path) - - def get_ghostscript_version(gs_path: str) -> Optional[str]: try: result = subprocess.run([gs_path, "-version"], capture_output=True, text=True, check=False) @@ -113,7 +108,7 @@ def convert_pdf_to_pdfx( output_path: str, config: PdfXConversionConfig, ) -> bool: - gs_path = _resolve_ghostscript_path(config.ghostscript_path) + gs_path = get_ghostscript_path(config.ghostscript_path) if not gs_path: logger.warning("Ghostscript was not found. Skipping PDF/X-1a conversion.") return False diff --git a/desktop-tool/src/processing.py b/desktop-tool/src/processing.py index 86061c4ca..f7ec0266e 100644 --- a/desktop-tool/src/processing.py +++ b/desktop-tool/src/processing.py @@ -13,7 +13,6 @@ # DriveThruCards requires 2.73" x 3.71" which includes bleed area DTC_CARD_WIDTH_INCHES = 2.73 DTC_CARD_HEIGHT_INCHES = 3.71 -MM_PER_INCH = 25.4 def calculate_dtc_target_pixel_size(target_dpi: int) -> Tuple[int, int]: @@ -111,21 +110,6 @@ def save_processed_image( img.save(file_path, **_build_save_kwargs(config=config, icc_profile_bytes=icc_profile_bytes)) -def save_processed_image_to_bytes( - img: "Image", - config: ImagePostProcessingConfig, - icc_profile_bytes: Optional[bytes] = None, -) -> bytes: - # Remove XMP data if it's present in the image info to avoid "XMP data is too long" error. - if "xmp" in img.info: - img.info.pop("xmp") - - output = io.BytesIO() - img.save(output, **_build_save_kwargs(config=config, icc_profile_bytes=icc_profile_bytes)) - output.seek(0) - return output.read() - - def _build_save_kwargs( config: ImagePostProcessingConfig, icc_profile_bytes: Optional[bytes], diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index a3e317b09..a8e027b22 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -678,7 +678,7 @@ def fake_run(cmd, capture_output=True, text=True): Path(output_arg.split("=", 1)[1]).write_bytes(b"%PDF-1.3 (PDF/X-1a:2001) /OutputIntents") return SimpleNamespace(returncode=0, stdout="", stderr="") - monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") + monkeypatch.setattr("src.pdf_maker.get_ghostscript_path", lambda _path=None: "/opt/homebrew/bin/gs") monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) assert convert_pdf_to_pdfx( @@ -703,7 +703,7 @@ def fake_run(cmd, capture_output=True, text=True): Path(output_arg.split("=", 1)[1]).write_bytes(b"partial") return SimpleNamespace(returncode=1, stdout="bad", stderr="worse") - monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") + monkeypatch.setattr("src.pdf_maker.get_ghostscript_path", lambda _path=None: "/opt/homebrew/bin/gs") monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) assert not convert_pdf_to_pdfx( @@ -729,7 +729,7 @@ def fake_run(cmd, capture_output=True, text=True): Path(output_arg.split("=", 1)[1]).write_bytes(b"%PDF-1.3 plain") return SimpleNamespace(returncode=0, stdout="", stderr="") - monkeypatch.setattr("src.pdf_maker._resolve_ghostscript_path", lambda _path: "/opt/homebrew/bin/gs") + monkeypatch.setattr("src.pdf_maker.get_ghostscript_path", lambda _path=None: "/opt/homebrew/bin/gs") monkeypatch.setattr("src.pdf_maker.subprocess.run", fake_run) assert not convert_pdf_to_pdfx( From d703bcf6fcf894d661ded4dba2539c830bd583e8 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 02:03:41 -0700 Subject: [PATCH 49/76] Simplify DTC helpers and fix multi-order edge cases - Extract a no_implicit_wait contextmanager, replacing the implicitly_wait(0)/implicitly_wait(5) dance repeated across six methods; delete the unused wait_for_selector helper. - Share export-directory derivation between PdfExporter and the --skip-pdf-if-exists reuse path via pdf_maker.get_export_directory so they can't drift; drop get_site_picker_choices (enum order already lists DriveThruCards last). - --skip-pdf-if-exists staleness now compares card images against the PDF/X output specifically, so a stale _pdfx.pdf is not silently reused when some other PDF is newer. - A DriveThruCards order whose PDF/X conversion produced no output now aborts the run instead of logging and exiting successfully, and the tip-jar handoff only shows when a browser session actually ran. --- desktop-tool/autofill.py | 38 ++++++--------- desktop-tool/src/driver.py | 64 +++++++++---------------- desktop-tool/src/pdf_maker.py | 11 +++-- desktop-tool/tests/test_desktop_tool.py | 43 +++++++++++++++++ 4 files changed, 86 insertions(+), 70 deletions(-) diff --git a/desktop-tool/autofill.py b/desktop-tool/autofill.py index 089b95959..02452475b 100644 --- a/desktop-tool/autofill.py +++ b/desktop-tool/autofill.py @@ -52,6 +52,7 @@ from src.pdf_maker import ( PdfExporter, PdfXConversionConfig, + get_export_directory, get_ghostscript_path, get_ghostscript_version, ) @@ -150,16 +151,8 @@ def ensure_ghostscript_available() -> str: input("Press Enter to re-check for Ghostscript, or Ctrl+C to exit.") -def get_export_directory_for_order(order_name: Optional[str]) -> str: - basename = os.path.basename(str(order_name)) - if not basename: - basename = "cards.xml" - file_name = os.path.splitext(basename)[0] - return os.path.join("export", file_name) - - def get_existing_pdf_paths(order_name: Optional[str]) -> list[str]: - export_directory = get_export_directory_for_order(order_name=order_name) + export_directory = get_export_directory(order_name=order_name) return sorted([path for path in glob(os.path.join(export_directory, "**", "*.pdf"), recursive=True)]) @@ -199,11 +192,16 @@ def maybe_reuse_existing_pdfs( if not existing_pdf_paths: return None - if require_pdfx and not any(path.endswith("_pdfx.pdf") for path in existing_pdf_paths): - logger.info("Existing PDF files were found, but no PDF/X-1a output was found. Recreating PDF export.") - return None + # When a PDF/X-1a file is required, it's also the file whose freshness matters - + # a stale _pdfx.pdf must not be reused just because some other PDF is newer. + relevant_pdf_paths = existing_pdf_paths + if require_pdfx: + relevant_pdf_paths = [path for path in existing_pdf_paths if path.endswith("_pdfx.pdf")] + if not relevant_pdf_paths: + logger.info("Existing PDF files were found, but no PDF/X-1a output was found. Recreating PDF export.") + return None - if existing_pdfs_are_stale(existing_pdf_paths=existing_pdf_paths, cards_directory=cards_directory): + if existing_pdfs_are_stale(existing_pdf_paths=relevant_pdf_paths, cards_directory=cards_directory): recreate_pdf = click.confirm( "Existing PDF export found, but images in cards/ are newer. Recreate PDF now?", default=True, @@ -260,12 +258,6 @@ def get_dtc_pdf_paths_for_order( return exporter.execute(post_processing_config=dtc_post_processing_config) -def get_site_picker_choices() -> list[str]: - site_names = [site.name for site in TargetSites] - # Keep DriveThruCards available but place it last in the picker list. - return [name for name in site_names if name != TargetSites.DriveThruCards.name] + [TargetSites.DriveThruCards.name] - - @click.command(context_settings={"show_default": True}) @click.option("-d", "--directory", default=None, help="The directory to search for order XML files.") @click.option( @@ -308,7 +300,8 @@ def get_site_picker_choices() -> list[str]: "--site", prompt=prompt_if_no_arguments("Which site should the tool auto-fill your project into?"), default=TargetSites.MakePlayingCards.name, - type=click.Choice(get_site_picker_choices(), case_sensitive=False), + # enum order intentionally lists DriveThruCards last in the picker + type=click.Choice([site.name for site in TargetSites], case_sensitive=False), help="The card printing site into which your order should be auto-filled.", ) @click.option( @@ -517,11 +510,10 @@ def main( # Only use the Ghostscript PDF/X-1a output - no fallback dtc_pdf_path = next((path for path in reversed(pdf_paths) if path.endswith("_pdfx.pdf")), None) if dtc_pdf_path is None: - logger.error( + raise Exception( "Ghostscript PDF/X-1a conversion failed. Cannot proceed with DriveThruCards upload.\n" "Please fix the Ghostscript conversion issue and try again." ) - continue if dtc_driver is None: starting_url = target_site.value.starting_url if not skip_dtc_instructions: @@ -541,7 +533,7 @@ def main( dtc_driver.execute_drive_thru_cards_order(order=order, pdf_path=dtc_pdf_path) if i < len(orders): input(f"Press {bold('Enter')} to continue with the next DriveThruCards order.\n") - if not exportpdf: + if dtc_driver is not None: wait_for_user_to_complete_order() elif exportpdf: order = CardOrder.from_xmls_in_folder(working_directory=working_directory)[0] diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index 75f15142d..deb8e4b12 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -249,10 +249,17 @@ def wait(self) -> bool: logger.debug(e) return False - def wait_for_selector(self, selector: str, timeout_seconds: int = 30) -> None: - WebDriverWait(self.driver, timeout_seconds, poll_frequency=0.2).until( - presence_of_element_located((By.CSS_SELECTOR, selector)) - ) + @contextmanager + def no_implicit_wait(self) -> Generator[None, None, None]: + """ + Temporarily disable the driver's implicit wait so absent-element checks poll fast. + """ + + self.driver.implicitly_wait(0) + try: + yield + finally: + self.driver.implicitly_wait(5) def set_state(self, state: str, action: Optional[str] = None) -> None: self.state = state @@ -383,9 +390,7 @@ def _try_click_turnstile_checkbox(self) -> bool: Returns True if checkbox was found and clicked, False otherwise. """ try: - # Disable implicit wait for fast polling - self.driver.implicitly_wait(0) - try: + with self.no_implicit_wait(): # Turnstile is rendered in an iframe - find it by common attributes iframe_selectors = [ "iframe[src*='challenges.cloudflare.com']", @@ -428,8 +433,6 @@ def _try_click_turnstile_checkbox(self) -> bool: self.driver.switch_to.default_content() return False - finally: - self.driver.implicitly_wait(5) except Exception as e: logger.debug(f"Error clicking Turnstile checkbox: {e}") # Ensure we're back in main content even on error @@ -442,19 +445,12 @@ def _try_click_turnstile_checkbox(self) -> bool: def _is_cloudflare_challenge_active(self) -> bool: """Check if a Cloudflare challenge page is currently displayed.""" try: - title = self.driver.title.lower() - if "just a moment" in title: + if "just a moment" in self.driver.title.lower(): return True - # Temporarily disable implicit wait for fast polling - self.driver.implicitly_wait(0) - try: - # Also check for challenge body text + # Also check for challenge body text + with self.no_implicit_wait(): body_text = self.driver.find_element(By.TAG_NAME, "body").text.lower() - if "verifying you are human" in body_text or "checking your browser" in body_text: - return True - return False - finally: - self.driver.implicitly_wait(5) + return "verifying you are human" in body_text or "checking your browser" in body_text except Exception: return False @@ -462,20 +458,14 @@ def _is_site_loaded(self) -> bool: """Check if the actual site content has loaded (past Cloudflare).""" selectors = self.target_site.value.selectors try: - title = self.driver.title.lower() - if "just a moment" in title: + if "just a moment" in self.driver.title.lower(): return False - # Temporarily disable implicit wait for fast polling - self.driver.implicitly_wait(0) - try: - # Check for logged-out, basic-account, or publisher navigation. + # Check for logged-out, basic-account, or publisher navigation. + with self.no_implicit_wait(): login_btns = self.driver.find_elements(By.CSS_SELECTOR, selectors.login_button_selector) authenticated = self.driver.find_elements(By.CSS_SELECTOR, selectors.authenticated_indicator_selector) publisher_ready = self.driver.find_elements(By.CSS_SELECTOR, selectors.publisher_ready_selector) return bool(login_btns or authenticated or publisher_ready) - finally: - # Restore implicit wait - self.driver.implicitly_wait(5) except Exception: return False @@ -522,14 +512,11 @@ def is_dtc_publisher_ready(self) -> bool: """Return whether the publisher-only Publish navigation link is visible.""" selectors = self.target_site.value.selectors try: - self.driver.implicitly_wait(0) - try: + with self.no_implicit_wait(): return any( element.is_displayed() for element in self.driver.find_elements(By.CSS_SELECTOR, selectors.publisher_ready_selector) ) - finally: - self.driver.implicitly_wait(5) except Exception as exc: logger.debug(f"Error checking publisher status: {exc}") return False @@ -538,8 +525,7 @@ def is_dtc_user_authenticated(self) -> bool: """Check for a basic signed-in account independently of publisher access.""" selectors = self.target_site.value.selectors try: - self.driver.implicitly_wait(0) - try: + with self.no_implicit_wait(): authenticated_elements = self.driver.find_elements( By.CSS_SELECTOR, selectors.authenticated_indicator_selector ) @@ -559,8 +545,6 @@ def is_dtc_user_authenticated(self) -> bool: element.is_displayed() for element in self.driver.find_elements(By.CSS_SELECTOR, selectors.publisher_ready_selector) ) - finally: - self.driver.implicitly_wait(5) except Exception as exc: logger.debug(f"Error checking auth status: {exc}") return False @@ -593,9 +577,7 @@ def click_element_polling(self, by: By, selector: str, timeout: int = 30) -> boo No fixed waits - keeps trying until success or timeout. """ start = time.time() - # Disable implicit wait for true aggressive polling - self.driver.implicitly_wait(0) - try: + with self.no_implicit_wait(): while time.time() - start < timeout: try: elements = self.driver.find_elements(by, selector) @@ -606,8 +588,6 @@ def click_element_polling(self, by: By, selector: str, timeout: int = 30) -> boo pass time.sleep(0.1) # Small delay to avoid CPU spinning return False - finally: - self.driver.implicitly_wait(5) def _click_dtc_login_button(self) -> bool: """Click the DriveThruCards login button to open the login modal.""" diff --git a/desktop-tool/src/pdf_maker.py b/desktop-tool/src/pdf_maker.py index 50eec5444..fbe7839f2 100644 --- a/desktop-tool/src/pdf_maker.py +++ b/desktop-tool/src/pdf_maker.py @@ -32,6 +32,11 @@ class PdfXConversionConfig: ghostscript_path: Optional[str] = None +def get_export_directory(order_name: Optional[str]) -> str: + basename = os.path.basename(str(order_name)) or "cards.xml" + return os.path.join("export", os.path.splitext(basename)[0]) + + def get_ghostscript_path(explicit_path: Optional[str] = None) -> Optional[str]: if explicit_path: return explicit_path @@ -261,11 +266,7 @@ def ask_questions(self) -> None: ) def generate_file_path(self) -> None: - basename = os.path.basename(str(self.order.name)) - if not basename: - basename = "cards.xml" - file_name = os.path.splitext(basename)[0] - self.save_path = f"export/{file_name}/" + self.save_path = get_export_directory(self.order.name) + "/" os.makedirs(self.save_path, exist_ok=True) if self.separate_faces: for face in ["backs", "fronts"]: diff --git a/desktop-tool/tests/test_desktop_tool.py b/desktop-tool/tests/test_desktop_tool.py index a8e027b22..1e2821d29 100644 --- a/desktop-tool/tests/test_desktop_tool.py +++ b/desktop-tool/tests/test_desktop_tool.py @@ -212,6 +212,49 @@ def test_ensure_ghostscript_available_does_not_prompt_when_already_installed( assert autofill_cli.ensure_ghostscript_available() == "/usr/local/bin/gs" +def test_maybe_reuse_existing_pdfs_detects_stale_pdfx_even_when_another_pdf_is_newer( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + order_name = "example.xml" + export_dir = tmp_path / "export" / "example" + export_dir.mkdir(parents=True) + cards_dir = tmp_path / "cards" + cards_dir.mkdir() + + # The PDF/X output is older than the card images, but a plain PDF is newer than both. + (export_dir / "1_pdfx.pdf").write_bytes(b"pdfx") + os.utime(export_dir / "1_pdfx.pdf", (1_000, 1_000)) + (cards_dir / "card.jpg").write_bytes(b"jpg") + os.utime(cards_dir / "card.jpg", (2_000, 2_000)) + (export_dir / "1.pdf").write_bytes(b"pdf") + os.utime(export_dir / "1.pdf", (3_000, 3_000)) + + prompts = {"count": 0} + + def fake_confirm(*_args, **_kwargs) -> bool: + prompts["count"] += 1 + return True # recreate the PDF export + + monkeypatch.setattr(autofill_cli.click, "confirm", fake_confirm) + + cwd_before = os.getcwd() + os.chdir(tmp_path) + try: + assert ( + autofill_cli.maybe_reuse_existing_pdfs( + order_name=order_name, + skip_pdf_if_exists=True, + cards_directory=str(cards_dir), + require_pdfx=True, + ) + is None + ) + finally: + os.chdir(cwd_before) + + assert prompts["count"] == 1 + + def test_maybe_reuse_existing_pdfs_returns_none_when_skip_disabled(tmp_path) -> None: order_name = "example.xml" export_dir = tmp_path / "export" / "example" From f822aa1867c5d14bb8b75cb75f257ebe15764ce8 Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 02:10:31 -0700 Subject: [PATCH 50/76] Match repo style conventions - processing.py: PEP 585 builtin generics instead of typing.Tuple/Dict - driver.py: hoist the function-local os imports to module level - web_server.py: direct attribute access instead of getattr - order.py: move is_image_valid/remove_if_exists from nested closures to module level --- desktop-tool/src/driver.py | 5 +---- desktop-tool/src/order.py | 36 ++++++++++++++++++---------------- desktop-tool/src/processing.py | 14 ++++++------- desktop-tool/src/web_server.py | 2 +- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/desktop-tool/src/driver.py b/desktop-tool/src/driver.py index deb8e4b12..2fbcfa7f3 100644 --- a/desktop-tool/src/driver.py +++ b/desktop-tool/src/driver.py @@ -1,4 +1,5 @@ import datetime as dt +import os import re import textwrap import time @@ -735,8 +736,6 @@ def fill_dtc_product_form(self, order: CardOrder) -> None: """ Fill out the DriveThruCards product setup form (first page). """ - import os - self.set_state(States.defining_order, "Filling product form") # Get the placeholder cover image path (bundled with assets) @@ -811,8 +810,6 @@ def select_card_type_and_upload_pdf(self, pdf_path: str) -> None: Select 'Premium Euro Poker Card(s)' from dropdown and upload the PDF. Waits for elements to be available instead of using fixed sleeps. """ - import os - self.set_state(States.inserting_fronts, "Selecting card type") # Wait for and select the Euro Poker card option from the dropdown. diff --git a/desktop-tool/src/order.py b/desktop-tool/src/order.py index 024a6a4aa..502dad9bc 100644 --- a/desktop-tool/src/order.py +++ b/desktop-tool/src/order.py @@ -36,6 +36,25 @@ from src.utils import unpack_element +def is_image_valid(file_path: str) -> bool: + try: + from PIL import Image + + with Image.open(file_path) as img: + img.verify() + return True + except Exception: + return False + + +def remove_if_exists(file_path: str) -> None: + try: + if os.path.isfile(file_path): + os.remove(file_path) + except Exception: + pass + + @attr.s class CardImage: drive_id: str = attr.ib(default="") @@ -184,23 +203,6 @@ def download_image( post_processing_config: Optional[ImagePostProcessingConfig], ) -> None: try: - def is_image_valid(file_path: str) -> bool: - try: - from PIL import Image - - with Image.open(file_path) as img: - img.verify() - return True - except Exception: - return False - - def remove_if_exists(file_path: str) -> None: - try: - if os.path.isfile(file_path): - os.remove(file_path) - except Exception: - pass - source_path = self.file_path if post_processing_config and self.file_path: self.file_path = get_post_processed_path(self.file_path, post_processing_config) diff --git a/desktop-tool/src/processing.py b/desktop-tool/src/processing.py index f7ec0266e..234662881 100644 --- a/desktop-tool/src/processing.py +++ b/desktop-tool/src/processing.py @@ -1,7 +1,7 @@ import io import os from dataclasses import dataclass -from typing import TYPE_CHECKING, Optional, Tuple, Dict, Any +from typing import TYPE_CHECKING, Any, Optional from src.constants import DPI_HEIGHT_RATIO, ImageResizeMethods from src.logging import logger @@ -15,7 +15,7 @@ DTC_CARD_HEIGHT_INCHES = 3.71 -def calculate_dtc_target_pixel_size(target_dpi: int) -> Tuple[int, int]: +def calculate_dtc_target_pixel_size(target_dpi: int) -> tuple[int, int]: """ Calculate the target pixel dimensions for DriveThruCards at the specified DPI. Card size is 2.73" x 3.71" (Premium Euro Poker with bleed). @@ -35,7 +35,7 @@ class ImagePostProcessingConfig: icc_profile_path: Optional[str] = None output_directory: Optional[str] = None jpeg_quality: int = 95 - target_pixel_size: Optional[Tuple[int, int]] = None + target_pixel_size: Optional[tuple[int, int]] = None embed_dpi_metadata: bool = False @@ -48,7 +48,7 @@ def get_post_processed_path(file_path: str, config: ImagePostProcessingConfig) - def _apply_color_processing( img: "Image", config: ImagePostProcessingConfig -) -> Tuple["Image", Optional[bytes]]: +) -> tuple["Image", Optional[bytes]]: icc_profile_bytes = None if config.convert_to_cmyk: if img.mode in ("RGBA", "LA"): @@ -74,7 +74,7 @@ def _apply_color_processing( return img, icc_profile_bytes -def post_process_image(raw_image: bytes, config: ImagePostProcessingConfig) -> Tuple["Image", Optional[bytes]]: +def post_process_image(raw_image: bytes, config: ImagePostProcessingConfig) -> tuple["Image", Optional[bytes]]: from PIL import Image img = Image.open(io.BytesIO(raw_image)) @@ -113,8 +113,8 @@ def save_processed_image( def _build_save_kwargs( config: ImagePostProcessingConfig, icc_profile_bytes: Optional[bytes], -) -> Dict[str, Any]: - save_kwargs: Dict[str, Any] = {} +) -> dict[str, Any]: + save_kwargs: dict[str, Any] = {} if config.output_format: save_kwargs["format"] = config.output_format if config.output_format and config.output_format.upper() == "JPEG": diff --git a/desktop-tool/src/web_server.py b/desktop-tool/src/web_server.py index b40e2b1b3..44c500b7c 100644 --- a/desktop-tool/src/web_server.py +++ b/desktop-tool/src/web_server.py @@ -17,7 +17,7 @@ def do_GET(self) -> None: self.send_response(200) self.send_header("Content-Type", "text/html") self.end_headers() - self.wfile.write(getattr(self.server, "html_path").read_bytes()) + self.wfile.write(self.server.html_path.read_bytes()) # type: ignore[attr-defined] def log_request(self, code: Union[int, str] = "-", size: Union[int, str] = "-") -> None: # Silence the request log. From 1a0327a81f27839b9d63f37ac70b10aa95af73cb Mon Sep 17 00:00:00 2001 From: Bradley Singer Date: Sun, 19 Jul 2026 02:10:42 -0700 Subject: [PATCH 51/76] PR hygiene: CI, assets and repo files - CI: run the desktop tool test suite unconditionally; tests requiring the Google Drive API key now skip themselves (via a client_secrets skipif marker) instead of the whole workflow being skipped on fork pull requests where the secret is unavailable. - Replace placeholder_cover.png (was JPEG bytes with a .png extension and old Photoshop metadata, unknown provenance) with a project-owned Pillow-generated PNG; record its origin in assets/README.md. - Move the wiki addendum and publisher-workflow HTML out of the tree (they belong in the PR description / upstream wiki) and drop the test that asserted their contents. - Revert the .python-version patch-level pin and trim .gitignore to build outputs (drop contributor-specific AI-instruction/plan-file ignores). --- .github/actions/test-desktop-tool/action.yml | 6 +- .github/workflows/test-desktop-tool.yml | 7 +- .gitignore | 18 +- .python-version | 2 +- desktop-tool/assets/README.md | 4 + desktop-tool/assets/placeholder_cover.png | Bin 51796 -> 11993 bytes .../docs/Desktop-Tool.wiki.addendum.md | 41 --- .../drivethrucards-publisher-workflow.html | 291 ------------------ desktop-tool/tests/test_desktop_tool.py | 30 +- 9 files changed, 26 insertions(+), 373 deletions(-) create mode 100644 desktop-tool/assets/README.md delete mode 100644 desktop-tool/docs/Desktop-Tool.wiki.addendum.md delete mode 100644 desktop-tool/docs/drivethrucards-publisher-workflow.html diff --git a/.github/actions/test-desktop-tool/action.yml b/.github/actions/test-desktop-tool/action.yml index f412652b4..04d43a26b 100644 --- a/.github/actions/test-desktop-tool/action.yml +++ b/.github/actions/test-desktop-tool/action.yml @@ -2,8 +2,9 @@ name: Desktop tool tests description: Test the MPC Autofill CLI / desktop tool inputs: google-drive-api-key: - description: Your Google Drive API key, required for running the database crawler - required: true + description: Your Google Drive API key. When empty, tests requiring it skip themselves. + required: false + default: "" runs: using: composite steps: @@ -30,6 +31,7 @@ runs: pip install -r requirements.txt shell: bash - name: Write Google Drive API credentials to file + if: inputs.google-drive-api-key != '' uses: jsdaniell/create-json@v1.2.3 with: name: "client_secrets.json" diff --git a/.github/workflows/test-desktop-tool.yml b/.github/workflows/test-desktop-tool.yml index d49f19c1b..4a43ba15a 100644 --- a/.github/workflows/test-desktop-tool.yml +++ b/.github/workflows/test-desktop-tool.yml @@ -18,11 +18,12 @@ jobs: # - os: ubuntu-latest # https://github.com/browser-actions/setup-edge/issues/516 steps: - uses: actions/checkout@v6 + # Tests requiring the Google Drive API key skip themselves when it's absent (fork pull + # requests), so the rest of the suite still runs. - name: Run desktop tool tests - if: env.GOOGLE_DRIVE_API_KEY != '' uses: ./.github/actions/test-desktop-tool with: google-drive-api-key: ${{ env.GOOGLE_DRIVE_API_KEY }} - - name: Skip secret-backed tests for fork pull requests + - name: Note skipped secret-backed tests for fork pull requests if: env.GOOGLE_DRIVE_API_KEY == '' - run: echo "::notice::Desktop tool tests require GOOGLE_DRIVE_API_KEY, which is unavailable to fork pull requests." + run: echo "::notice::Tests requiring GOOGLE_DRIVE_API_KEY were skipped because the secret is unavailable to fork pull requests." diff --git a/.gitignore b/.gitignore index b4268ae6e..dc49a7eaf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Ignore XML files we might use for debugging cards.xml **/cards/* -test_order.xml # Ignore pyinstaller stuff __pycache__/* @@ -69,7 +68,6 @@ coverage.xml desktop-tool/export/ desktop-tool/tests/export/ desktop-tool/tests/cards/ -desktop-tool/TODO.md cover/ @@ -128,7 +126,7 @@ celerybeat.pid # Environments .env -.venv*/ +.venv env/ venv/ ENV/ @@ -160,7 +158,6 @@ dmypy.json cython_debug/ .idea -.cursor/ .DS_Store MPCAutofill/card_db.db MPCAutofill/build @@ -194,19 +191,6 @@ frontend/public/mockServiceWorker.js /image-cdn/node_modules/* /image-cdn/.wrangler/* -# Plan files -*.plan.md - -# AI instruction files -**/[Cc][Ll][Aa][Uu][Dd][Ee].md -**/[Gg][Pp][Tt].md -**/[Gg][Ee][Mm][Ii][Nn][Ii].md -**/[Aa][Gg][Ee][Nn][Tt][Ss].md -**/.cursorrules -**/.github/copilot-instructions.md -**/.github/instructions/*.instructions.md -**/.cursor/rules/*.mdc - # Playwright node_modules/ /test-results/ diff --git a/.python-version b/.python-version index 2c45fe3a7..24ee5b1be 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13.11 +3.13 diff --git a/desktop-tool/assets/README.md b/desktop-tool/assets/README.md new file mode 100644 index 000000000..ace4fe4bc --- /dev/null +++ b/desktop-tool/assets/README.md @@ -0,0 +1,4 @@ +# Assets + +- `placeholder_cover.png`: generated by this project with Pillow (solid background + text) as a + placeholder product cover for DriveThruCards listings. Licensed under this repository's license. diff --git a/desktop-tool/assets/placeholder_cover.png b/desktop-tool/assets/placeholder_cover.png index 72d708f4508bcd70a042596010ec16d48f597e02..5989e1f2679b15149a86ff1e94bd483d58f419ee 100644 GIT binary patch literal 11993 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 diff --git a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md b/desktop-tool/docs/Desktop-Tool.wiki.addendum.md deleted file mode 100644 index a2fe6c155..000000000 --- a/desktop-tool/docs/Desktop-Tool.wiki.addendum.md +++ /dev/null @@ -1,41 +0,0 @@ -# Desktop Tool Wiki Addendum (PR Patch) - -This file contains only the incremental wiki updates introduced in this PR. -Target page: - -## Insert Under Existing "Useful CLI Flags" Section - -### PDF/Export Flags (add these bullets if missing) - -- `--skip-pdf-if-exists` - - Reuse existing exported PDFs when present for the order. - - If files in `cards/` are newer than existing PDFs, the CLI prompts whether to recreate. - - For DriveThruCards, existing files are reused only when a `_pdfx.pdf` output exists. - -- `--download-images-only` - - Download all card images to `cards/` and exit. - - Skips PDF creation and browser automation. - -- `--dtc-icc-profile` - - Override the default bundled ICC profile used during DriveThruCards PDF/X conversion. - - Useful if your print workflow requires a specific ICC profile. - -### Browser/Profile Flags (add these bullets if missing) - -- `--browser-profile-path` - - Use an existing Chromium user data directory (cookies/session/password managers). - -- `--browser-profile-name` - - Select the profile directory inside the user data folder (default: `Default`). - -### Logging Flag (add subsection if missing) - -- `--log-level` - - Global CLI verbosity control. - - Use `DEBUG` to show detailed Selenium step-by-step logs. - -## Insert Under Existing Notes/Behavior Section - -- During PDF/X conversion, CLI status reports progress per file (`Converting PDF to PDF/X-1a (n/total)`). -- Build/bundled desktop artifacts include the full `assets/` directory. -- DriveThruCards now explicitly asks for confirmation before attempting Ghostscript installation. diff --git a/desktop-tool/docs/drivethrucards-publisher-workflow.html b/desktop-tool/docs/drivethrucards-publisher-workflow.html deleted file mode 100644 index 6ee595beb..000000000 --- a/desktop-tool/docs/drivethrucards-publisher-workflow.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - DriveThruCards Publisher Workflow - - - -