diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index e5dbd1e..71fe5a1 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -4,50 +4,64 @@ on: push: tags: - 'v*.*.*' + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build-docker-images: + if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-22.04 steps: - - - name: Checkout - uses: actions/checkout@v3 - - - name: Downcase repo + - name: Checkout + uses: actions/checkout@v4 + + - name: Downcase repo run: echo REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV - - - name: Docker meta + + - name: Docker meta id: docker_meta - uses: crazy-max/ghaction-docker-meta@v3 + uses: docker/metadata-action@v5 with: - images: ${{ env.REPOSITORY }},ghcr.io/${{ env.REPOSITORY }} - tag-sha: false - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 + images: | + ${{ env.REPOSITORY }},enable=${{ github.event_name != 'pull_request' }} + ghcr.io/${{ env.REPOSITORY }} + tags: | + type=semver,pattern={{version}},prefix=v + type=ref,event=pr + flavor: | + latest=auto + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + if: github.event_name != 'pull_request' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v3 + password: ${{ secrets.GH_PAT }} + + - name: Build and push + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 - push: ${{ github.event_name != 'pull_request' }} + push: true tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a18f2..a02e8d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## v3.3.21 (2024/06/26) +* Add challenge selector to catch reloading page on non-English systems +* Escape values for generated form used in request.post. Thanks @mynameisbogdan + +## v3.3.20 (2024/06/21) +* maxTimeout should always be int +* Check not running in Docker before logging version_main error +* Update Cloudflare challenge and checkbox selectors. Thanks @tenettow & @21hsmw + +## v3.3.19 (2024/05/23) +* Fix occasional headless issue on Linux when set to "false". Thanks @21hsmw + +## v3.3.18 (2024/05/20) + +* Fix LANG ENV for Linux +* Fix Chrome v124+ not closing on Windows. Thanks @RileyXX + +## v3.3.17 (2024/04/09) + +* Fix file descriptor leak in service on quit(). Thanks @zkulis + +## v3.3.16 (2024/02/28) + +* Fix of the subprocess.STARTUPINFO() call. Thanks @ceconelo +* Add FreeBSD support. Thanks @Asthowen +* Use headless configuration properly. Thanks @hashworks + +## v3.3.15 (2024/02/20) + +* Fix looping challenges + +## v3.3.14-hotfix2 (2024/02/17) + +* Hotfix 2 - bad Chromium build, instances failed to terminate + +## v3.3.14-hotfix (2024/02/17) + +* Hotfix for Linux build - some Chrome files no longer exist + +## v3.3.14 (2024/02/17) + +* Update Chrome downloads. Thanks @opemvbs + ## v3.3.13 (2024/01/07) * Fix too many open files error diff --git a/Dockerfile b/Dockerfile index 791e144..3d8ea35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,17 +62,17 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["/usr/local/bin/python", "-u", "/app/flaresolverr.py"] # Local build -# docker build -t ngosang/flaresolverr:3.3.13 . -# docker run -p 8191:8191 ngosang/flaresolverr:3.3.13 +# docker build -t ngosang/flaresolverr:3.3.21 . +# docker run -p 8191:8191 ngosang/flaresolverr:3.3.21 # Multi-arch build # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker buildx create --use -# docker buildx build -t ngosang/flaresolverr:3.3.13 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 . +# docker buildx build -t ngosang/flaresolverr:3.3.21 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 . # add --push to publish in DockerHub # Test multi-arch build # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker buildx create --use -# docker buildx build -t ngosang/flaresolverr:3.3.13 --platform linux/arm/v7 --load . -# docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.13 +# docker buildx build -t ngosang/flaresolverr:3.3.21 --platform linux/arm/v7 --load . +# docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.21 diff --git a/README.md b/README.md index 7e73376..5f5ee9a 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ Supported architectures are: | ARM32 | linux/arm/v7 | | ARM64 | linux/arm64 | -We provide a `docker-compose.yml` configuration file. Clone this repository and execute `docker-compose up -d` to start +We provide a `docker-compose.yml` configuration file. Clone this repository and execute +`docker-compose up -d` _(Compose V1)_ or `docker compose up -d` _(Compose V2)_ to start the container. If you prefer the `docker cli` execute the following command. @@ -78,11 +79,19 @@ This is the recommended way for Windows users. * Install [Python 3.11](https://www.python.org/downloads/). * Install [Chrome](https://www.google.com/intl/en_us/chrome/) (all OS) or [Chromium](https://www.chromium.org/getting-involved/download-chromium/) (just Linux, it doesn't work in Windows) web browser. -* (Only in Linux / macOS) Install [Xvfb](https://en.wikipedia.org/wiki/Xvfb) package. +* (Only in Linux) Install [Xvfb](https://en.wikipedia.org/wiki/Xvfb) package. +* (Only in macOS) Install [XQuartz](https://www.xquartz.org/) package. * Clone this repository and open a shell in that path. * Run `pip install -r requirements.txt` command to install FlareSolverr dependencies. * Run `python src/flaresolverr.py` command to start FlareSolverr. +### From source code (FreeBSD/TrueNAS CORE) + +* Run `pkg install chromium python39 py39-pip xorg-vfbserver` command to install the required dependencies. +* Clone this repository and open a shell in that path. +* Run `python3.9 -m pip install -r requirements.txt` command to install FlareSolverr dependencies. +* Run `python3.9 src/flaresolverr.py` command to start FlareSolverr. + ### Systemd service We provide an example Systemd unit file `flaresolverr.service` as reference. You have to modify the file to suit your needs: paths, user and environment variables. @@ -95,7 +104,7 @@ curl -L -X POST 'http://localhost:8191/v1' \ -H 'Content-Type: application/json' \ --data-raw '{ "cmd": "request.get", - "url":"http://www.google.com/", + "url": "http://www.google.com/", "maxTimeout": 60000 }' ``` @@ -115,6 +124,17 @@ response = requests.post(url, headers=headers, json=data) print(response.text) ``` +Example PowerShell request: +```ps1 +$body = @{ + cmd = "request.get" + url = "http://www.google.com/" + maxTimeout = 60000 +} | ConvertTo-Json + +irm -UseBasicParsing 'http://localhost:8191/v1' -Headers @{"Content-Type"="application/json"} -Method Post -Body $body +``` + ### Commands #### + `sessions.create` @@ -259,8 +279,8 @@ This is the same as `request.get` but it takes one more param: Environment variables are set differently depending on the operating system. Some examples: * Docker: Take a look at the Docker section in this document. Environment variables can be set in the `docker-compose.yml` file or in the Docker CLI command. -* Linux: Run `export LOG_LEVEL=debug` and then start FlareSolverr in the same shell. -* Windows: Open `cmd.exe`, run `set LOG_LEVEL=debug` and then start FlareSolverr in the same shell. +* Linux: Run `export LOG_LEVEL=debug` and then run `flaresolverr` in the same shell. +* Windows: Open `cmd.exe`, run `set LOG_LEVEL=debug` and then run `flaresolverr.exe` in the same shell. ## Prometheus exporter diff --git a/package.json b/package.json index 531f810..525abd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flaresolverr", - "version": "3.3.13", + "version": "3.3.21", "description": "Proxy server to bypass Cloudflare protection", "author": "Diego Heras (ngosang / ngosang@hotmail.es)", "license": "MIT" diff --git a/requirements.txt b/requirements.txt index 212df01..d633aeb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,10 @@ selenium==4.15.2 func-timeout==4.3.5 prometheus-client==0.17.1 # required by undetected_chromedriver -requests==2.31.0 -certifi==2023.7.22 +requests==2.32.3 +certifi==2024.07.04 websockets==11.0.3 -# only required for linux -xvfbwrapper==0.2.9 +# only required for linux and macos +xvfbwrapper==0.2.9; platform_system != "Windows" # only required for windows -pefile==2023.2.7 +pefile==2023.2.7; platform_system == "Windows" diff --git a/src/build_package.py b/src/build_package.py index db85a30..5ff4956 100644 --- a/src/build_package.py +++ b/src/build_package.py @@ -25,7 +25,7 @@ def clean_files(): def download_chromium(): # https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/ - revision = "1140001" if os.name == 'nt' else '1140000' + revision = "1260008" if os.name == 'nt' else '1260015' arch = 'Win_x64' if os.name == 'nt' else 'Linux_x64' dl_file = 'chrome-win' if os.name == 'nt' else 'chrome-linux' dl_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, 'dist_chrome') @@ -59,8 +59,7 @@ def download_chromium(): # Give executable permissions for *nix # file * | grep executable | cut -d: -f1 print("Giving executable permissions...") - execs = ['chrome', 'chrome_crashpad_handler', 'chrome_sandbox', 'chrome-wrapper', 'nacl_helper', - 'nacl_helper_bootstrap', 'nacl_irt_x86_64.nexe', 'xdg-mime', 'xdg-settings'] + execs = ['chrome', 'chrome_crashpad_handler', 'chrome_sandbox', 'chrome-wrapper', 'xdg-mime', 'xdg-settings'] for exec_file in execs: exec_path = os.path.join(chrome_path, exec_file) os.chmod(exec_path, 0o755) diff --git a/src/flaresolverr.py b/src/flaresolverr.py index 9e5f8de..3596fe1 100644 --- a/src/flaresolverr.py +++ b/src/flaresolverr.py @@ -101,6 +101,9 @@ if __name__ == "__main__": logging.info(f'FlareSolverr {utils.get_flaresolverr_version()}') logging.debug('Debug log enabled') + # Get current OS for global variable + utils.get_current_platform() + # test browser installation flaresolverr_service.test_browser_installation() diff --git a/src/flaresolverr_service.py b/src/flaresolverr_service.py index 09c01f9..3042c3e 100644 --- a/src/flaresolverr_service.py +++ b/src/flaresolverr_service.py @@ -3,7 +3,8 @@ import platform import sys import time from datetime import timedelta -from urllib.parse import unquote +from html import escape +from urllib.parse import unquote, quote from func_timeout import FunctionTimedOut, func_timeout from selenium.common import TimeoutException @@ -40,7 +41,7 @@ CHALLENGE_TITLES = [ ] CHALLENGE_SELECTORS = [ # Cloudflare - '#cf-challenge-running', '.ray_id', '.attack-box', '#cf-please-wait', '#challenge-spinner', '#trk_jschal_js', + '#cf-challenge-running', '.ray_id', '.attack-box', '#cf-please-wait', '#challenge-spinner', '#trk_jschal_js', '#turnstile-wrapper', '.lds-ring', # Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands 'td.info #js_info', # Fairlane / pararius.com @@ -119,7 +120,7 @@ def _controller_v1_handler(req: V1RequestBase) -> V1ResponseBase: logging.warning("Request parameter 'userAgent' was removed in FlareSolverr v2.") # set default values - if req.maxTimeout is None or req.maxTimeout < 1: + if req.maxTimeout is None or int(req.maxTimeout) < 1: req.maxTimeout = 60000 # execute the command @@ -220,7 +221,7 @@ def _cmd_sessions_destroy(req: V1RequestBase) -> V1ResponseBase: def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT: - timeout = req.maxTimeout / 1000 + timeout = int(req.maxTimeout) / 1000 driver = None try: if req.session: @@ -245,6 +246,8 @@ def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT: raise Exception('Error solving the challenge. ' + str(e).replace('\n', '\\n')) finally: if not req.session and driver is not None: + if utils.PLATFORM_VERSION == "nt": + driver.close() driver.quit() logging.debug('A used instance of webdriver has been destroyed') @@ -256,7 +259,7 @@ def click_verify(driver: WebDriver): driver.switch_to.frame(iframe) checkbox = driver.find_element( by=By.XPATH, - value='//*[@id="challenge-stage"]/div/label/input', + value='//*[@id="content"]/div/div/label/input', ) if checkbox: actions = ActionChains(driver) @@ -287,20 +290,35 @@ def click_verify(driver: WebDriver): time.sleep(2) +def get_correct_window(driver: WebDriver) -> WebDriver: + if len(driver.window_handles) > 1: + for window_handle in driver.window_handles: + driver.switch_to.window(window_handle) + current_url = driver.current_url + if not current_url.startswith("devtools://devtools"): + return driver + return driver + + +def access_page(driver: WebDriver, url: str) -> None: + driver.get(url) + driver.start_session() + driver.start_session() # required to bypass Cloudflare + + def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> ChallengeResolutionT: res = ChallengeResolutionT({}) res.status = STATUS_OK res.message = "" + # navigate to the page logging.debug(f'Navigating to... {req.url}') - driver.get(req.url) - driver.start_session() # required to bypass Cloudflare if method == 'POST': _post_request(req, driver) else: - driver.get(req.url) - driver.start_session() # required to bypass Cloudflare + access_page(driver, req.url) + driver = get_correct_window(driver) # set cookies if required if req.cookies is not None and len(req.cookies) > 0: @@ -312,8 +330,8 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge if method == 'POST': _post_request(req, driver) else: - driver.get(req.url) - driver.start_session() # required to bypass Cloudflare + access_page(driver, req.url) + driver = get_correct_window(driver) # wait for the page if utils.get_config_log_html(): @@ -427,7 +445,7 @@ def _post_request(req: V1RequestBase, driver: WebDriver): value = unquote(parts[1]) except Exception: value = parts[1] - post_form += f'
' + post_form += f'
' post_form += '' html_content = f""" @@ -437,5 +455,6 @@ def _post_request(req: V1RequestBase, driver: WebDriver): """ - driver.get("data:text/html;charset=utf-8," + html_content) + driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content)) + driver.start_session() driver.start_session() # required to bypass Cloudflare diff --git a/src/sessions.py b/src/sessions.py index 1a635e0..30bb3c1 100644 --- a/src/sessions.py +++ b/src/sessions.py @@ -66,6 +66,8 @@ class SessionsStorage: return False session = self.sessions.pop(session_id) + if utils.PLATFORM_VERSION == "nt": + session.driver.close() session.driver.quit() return True diff --git a/src/undetected_chromedriver/__init__.py b/src/undetected_chromedriver/__init__.py index 9bd9823..4e7fa1a 100644 --- a/src/undetected_chromedriver/__init__.py +++ b/src/undetected_chromedriver/__init__.py @@ -17,7 +17,7 @@ by UltrafunkAmsterdam (https://github.com/ultrafunkamsterdam) from __future__ import annotations -__version__ = "3.5.4" +__version__ = "3.5.5" import json import logging @@ -451,8 +451,10 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver): options.binary_location, *options.arguments ) else: - startupinfo = subprocess.STARTUPINFO() + startupinfo = None if os.name == 'nt' and windows_headless: + # STARTUPINFO() is Windows only + startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW browser = subprocess.Popen( [options.binary_location, *options.arguments], @@ -769,7 +771,9 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver): def quit(self): try: + self.service.stop() self.service.process.kill() + self.command_executor.close() self.service.process.wait(5) logger.debug("webdriver process ended") except (AttributeError, RuntimeError, OSError): @@ -784,15 +788,6 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver): logger.debug("gracefully closed browser") except Exception as e: # noqa pass - # Force kill Chrome process in Windows - # https://github.com/FlareSolverr/FlareSolverr/issues/772 - if os.name == 'nt': - try: - subprocess.call(['taskkill', '/f', '/pid', str(self.browser_pid)], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - except Exception: - pass if ( hasattr(self, "keep_user_data_dir") and hasattr(self, "user_data_dir") diff --git a/src/undetected_chromedriver/devtool.py b/src/undetected_chromedriver/devtool.py index 30e7c08..915d417 100644 --- a/src/undetected_chromedriver/devtool.py +++ b/src/undetected_chromedriver/devtool.py @@ -2,6 +2,7 @@ import asyncio from collections.abc import Mapping from collections.abc import Sequence from functools import wraps +import os import logging import threading import time @@ -187,4 +188,6 @@ def test(): time.sleep(10) + if os.name == "nt": + driver.close() driver.quit() diff --git a/src/undetected_chromedriver/patcher.py b/src/undetected_chromedriver/patcher.py index 78b7617..e8a0e96 100644 --- a/src/undetected_chromedriver/patcher.py +++ b/src/undetected_chromedriver/patcher.py @@ -21,7 +21,7 @@ from multiprocessing import Lock logger = logging.getLogger(__name__) -IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2")) +IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2", "freebsd")) class Patcher(object): @@ -68,8 +68,10 @@ class Patcher(object): # check if version_main_int is less than or equal to e.g 114 self.is_old_chromedriver = version_main and version_main_int <= 114 except (ValueError,TypeError): - # If the conversion fails, print an error message - print("version_main cannot be converted to an integer") + # Check not running inside Docker + if not os.path.exists("/app/chromedriver"): + # If the conversion fails, log an error message + logging.info("version_main cannot be converted to an integer") # Set self.is_old_chromedriver to False if the conversion fails self.is_old_chromedriver = False @@ -80,9 +82,14 @@ class Patcher(object): os.makedirs(self.data_path, exist_ok=True) if not executable_path: - self.executable_path = os.path.join( - self.data_path, "_".join([prefix, self.exe_name]) - ) + if sys.platform.startswith("freebsd"): + self.executable_path = os.path.join( + self.data_path, self.exe_name + ) + else: + self.executable_path = os.path.join( + self.data_path, "_".join([prefix, self.exe_name]) + ) if not IS_POSIX: if executable_path: @@ -127,6 +134,9 @@ class Patcher(object): else: self.platform_name = "mac-x64" self.exe_name %= "" + if self.platform.startswith("freebsd"): + self.platform_name = "freebsd" + self.exe_name %= "" def auto(self, executable_path=None, force=False, version_main=None, _=None): """ @@ -166,26 +176,56 @@ class Patcher(object): if force is True: self.force = force - try: - os.unlink(self.executable_path) - except PermissionError: - if self.force: - self.force_kill_instances(self.executable_path) - return self.auto(force=not self.force) - try: - if self.is_binary_patched(): - # assumes already running AND patched - return True - except PermissionError: - pass - # return False - except FileNotFoundError: - pass - release = self.fetch_release_number() - self.version_main = release.version[0] - self.version_full = release - self.unzip_package(self.fetch_package()) + if self.platform_name == "freebsd": + chromedriver_path = shutil.which("chromedriver") + + if not os.path.isfile(chromedriver_path) or not os.access(chromedriver_path, os.X_OK): + logging.error("Chromedriver not installed!") + return + + version_path = os.path.join(os.path.dirname(self.executable_path), "version.txt") + + process = os.popen(f'"{chromedriver_path}" --version') + chromedriver_version = process.read().split(' ')[1].split(' ')[0] + process.close() + + current_version = None + if os.path.isfile(version_path) or os.access(version_path, os.X_OK): + with open(version_path, 'r') as f: + current_version = f.read() + + if current_version != chromedriver_version: + logging.info("Copying chromedriver executable...") + shutil.copy(chromedriver_path, self.executable_path) + os.chmod(self.executable_path, 0o755) + + with open(version_path, 'w') as f: + f.write(chromedriver_version) + + logging.info("Chromedriver executable copied!") + else: + try: + os.unlink(self.executable_path) + except PermissionError: + if self.force: + self.force_kill_instances(self.executable_path) + return self.auto(force=not self.force) + try: + if self.is_binary_patched(): + # assumes already running AND patched + return True + except PermissionError: + pass + # return False + except FileNotFoundError: + pass + + release = self.fetch_release_number() + self.version_main = release.version[0] + self.version_full = release + self.unzip_package(self.fetch_package()) + return self.patch() def driver_binary_in_use(self, path: str = None) -> bool: @@ -290,7 +330,7 @@ class Patcher(object): download_url = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, zip_name) else: zip_name = zip_name.replace("_", "-", 1) - download_url = "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/%s/%s/%s" + download_url = "https://storage.googleapis.com/chrome-for-testing-public/%s/%s/%s" download_url %= (self.version_full.vstring, self.platform_name, zip_name) logger.debug("downloading from %s" % download_url) diff --git a/src/utils.py b/src/utils.py index 3672511..a5bd1ef 100644 --- a/src/utils.py +++ b/src/utils.py @@ -5,11 +5,13 @@ import re import shutil import urllib.parse import tempfile +import sys from selenium.webdriver.chrome.webdriver import WebDriver import undetected_chromedriver as uc FLARESOLVERR_VERSION = None +PLATFORM_VERSION = None CHROME_EXE_PATH = None CHROME_MAJOR_VERSION = None USER_AGENT = None @@ -37,6 +39,13 @@ def get_flaresolverr_version() -> str: FLARESOLVERR_VERSION = json.loads(f.read())['version'] return FLARESOLVERR_VERSION +def get_current_platform() -> str: + global PLATFORM_VERSION + if PLATFORM_VERSION is not None: + return PLATFORM_VERSION + PLATFORM_VERSION = os.name + return PLATFORM_VERSION + def create_proxy_extension(proxy: dict) -> str: parsed_url = urllib.parse.urlparse(proxy['url']) @@ -138,7 +147,7 @@ def get_webdriver(proxy: dict = None) -> WebDriver: language = os.environ.get('LANG', None) if language is not None: - options.add_argument('--lang=%s' % language) + options.add_argument('--accept-lang=%s' % language) # Fix for Chrome 117 | https://github.com/FlareSolverr/FlareSolverr/issues/910 if USER_AGENT is not None: @@ -164,6 +173,8 @@ def get_webdriver(proxy: dict = None) -> WebDriver: # For normal headless mode: # options.add_argument('--headless') + options.add_argument("--auto-open-devtools-for-tabs") + # if we are inside the Docker container, we avoid downloading the driver driver_exe_path = None version_main = None @@ -180,9 +191,12 @@ def get_webdriver(proxy: dict = None) -> WebDriver: # downloads and patches the chromedriver # if we don't set driver_executable_path it downloads, patches, and deletes the driver each time - driver = uc.Chrome(options=options, browser_executable_path=browser_executable_path, - driver_executable_path=driver_exe_path, version_main=version_main, - windows_headless=windows_headless, headless=windows_headless) + try: + driver = uc.Chrome(options=options, browser_executable_path=browser_executable_path, + driver_executable_path=driver_exe_path, version_main=version_main, + windows_headless=windows_headless, headless=get_config_headless()) + except Exception as e: + logging.error("Error starting Chrome: %s" % e) # save the patched driver to avoid re-downloads if driver_exe_path is None: @@ -308,6 +322,8 @@ def get_user_agent(driver=None) -> str: raise Exception("Error getting browser User-Agent. " + str(e)) finally: if driver is not None: + if PLATFORM_VERSION == "nt": + driver.close() driver.quit()