From b2d8c4b67e5c390697d1f87819c0d84880c32776 Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Sun, 28 Jan 2024 00:28:32 -0800 Subject: [PATCH 1/3] Bump Firefox version in Chrome Driver User-Agent --- safeway_coupons/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/safeway_coupons/session.py b/safeway_coupons/session.py index 8e4e452..f59411b 100644 --- a/safeway_coupons/session.py +++ b/safeway_coupons/session.py @@ -32,8 +32,8 @@ def __init__( class BaseSession: USER_AGENT = ( - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:103.0) " - "Gecko/20100101 Firefox/103.0" + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) " + "Gecko/20100101 Firefox/122.0" ) @property From 60eba185dec5f83fc45b4d933acd9b3948d505e3 Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Sun, 28 Jan 2024 00:28:09 -0800 Subject: [PATCH 2/3] Separate Chrome Driver context manager to new file --- safeway_coupons/chrome_driver.py | 25 +++++++++++++++++++++++++ safeway_coupons/session.py | 20 +++----------------- 2 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 safeway_coupons/chrome_driver.py diff --git a/safeway_coupons/chrome_driver.py b/safeway_coupons/chrome_driver.py new file mode 100644 index 0000000..afb57f5 --- /dev/null +++ b/safeway_coupons/chrome_driver.py @@ -0,0 +1,25 @@ +import contextlib +from typing import Iterator + +import undetected_chromedriver as uc # type: ignore + + +@contextlib.contextmanager +def chrome_driver(headless: bool = True) -> Iterator[uc.Chrome]: + options = uc.ChromeOptions() + options.headless = headless + for option in [ + "--incognito", + "--no-sandbox", + "--disable-extensions", + "--disable-application-cache", + "--disable-gpu", + "--disable-setuid-sandbox", + "--disable-dev-shm-usage", + ]: + options.add_argument(option) + if headless: + options.add_argument("--headless=new") + driver = uc.Chrome(options=options) + yield driver + driver.quit() diff --git a/safeway_coupons/session.py b/safeway_coupons/session.py index f59411b..99c74c6 100644 --- a/safeway_coupons/session.py +++ b/safeway_coupons/session.py @@ -17,6 +17,7 @@ from selenium.webdriver.support.wait import WebDriverWait from .accounts import Account +from .chrome_driver import chrome_driver from .errors import AuthenticationFailure @@ -64,23 +65,9 @@ def __init__(self, account: Account, debug_dir: Optional[Path]) -> None: @contextlib.contextmanager def _chrome_driver(self, headless: bool = True) -> Iterator[uc.Chrome]: - options = uc.ChromeOptions() - options.headless = headless - for option in [ - "--incognito", - "--no-sandbox", - "--disable-extensions", - "--disable-application-cache", - "--disable-gpu", - "--disable-setuid-sandbox", - "--disable-dev-shm-usage", - ]: - options.add_argument(option) - if headless: - options.add_argument("--headless=new") - driver = uc.Chrome(options=options) try: - yield driver + with chrome_driver(headless=headless) as driver: + yield driver except WebDriverException as e: attachments: List[Path] = [] if self.debug_dir: @@ -91,7 +78,6 @@ def _chrome_driver(self, headless: bool = True) -> Iterator[uc.Chrome]: raise ExceptionWithAttachments( f"[{type(e).__name__}] {e}", attachments=attachments ) from e - driver.quit() @staticmethod def _sign_in_success(driver: uc.Chrome) -> bool: From 8375843dd3c30bda04d2507123149430d0882825 Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Sun, 28 Jan 2024 00:41:24 -0800 Subject: [PATCH 3/3] Include undetected_chromedriver binary in Docker image --- Dockerfile | 1 + pyproject.toml | 1 + safeway_coupons/chrome_driver.py | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/Dockerfile b/Dockerfile index f61776d..af9def6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,7 @@ COPY docker/entrypoint / COPY . /python-build RUN python3 -m pip install /python-build && rm -rf /python-build +RUN safeway-coupons-init-chromedriver ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/entrypoint"] diff --git a/pyproject.toml b/pyproject.toml index 16d307b..e5db4c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ responses = "*" [tool.poetry.scripts] safeway-coupons = "safeway_coupons.app:main" +safeway-coupons-init-chromedriver = "safeway_coupons.chrome_driver:init" [tool.poetry-dynamic-versioning] enable = true diff --git a/safeway_coupons/chrome_driver.py b/safeway_coupons/chrome_driver.py index afb57f5..e3b9838 100644 --- a/safeway_coupons/chrome_driver.py +++ b/safeway_coupons/chrome_driver.py @@ -1,8 +1,23 @@ import contextlib +import subprocess +import sys +from pathlib import Path from typing import Iterator import undetected_chromedriver as uc # type: ignore +CHROMEDRIVER_PATH = ( + Path.home() + / ".local" + / "share" + / "undetected_chromedriver" + / "undetected_chromedriver" +) + + +class ChromeDriverDoesNotExist(Exception): + pass + @contextlib.contextmanager def chrome_driver(headless: bool = True) -> Iterator[uc.Chrome]: @@ -23,3 +38,25 @@ def chrome_driver(headless: bool = True) -> Iterator[uc.Chrome]: driver = uc.Chrome(options=options) yield driver driver.quit() + + +def chrome_driver_version() -> str: + if not CHROMEDRIVER_PATH.is_file(): + raise ChromeDriverDoesNotExist( + f"Error: {CHROMEDRIVER_PATH} does not exist" + ) + cmd = [str(CHROMEDRIVER_PATH), "--version"] + print(f"+ {' '.join(cmd)}", file=sys.stderr) + result = subprocess.run(cmd, capture_output=True) + return result.stdout.decode() + + +def init() -> None: + with contextlib.suppress(ChromeDriverDoesNotExist): + print(chrome_driver_version()) + return + print("Initializing Chrome Driver") + with chrome_driver() as driver: + print("Connect to example.com") + driver.get("https://example.com") + print(chrome_driver_version())