Compare commits

..

No commits in common. "master" and "v3.3.18" have entirely different histories.

16 changed files with 96 additions and 153 deletions

View File

@ -29,13 +29,6 @@ body:
options: options:
- label: I have read the Discussions - label: I have read the Discussions
required: true required: true
- type: input
attributes:
label: Have you ACTUALLY checked all these?
description: Please do not waste our time and yours; these checks are there for a reason, it is not just so you can tick boxes for fun. If you type <b>YES</b> and it is clear you did not or have put in no effort, your issue will be closed and locked without comment. If you type <b>NO</b> but still open this issue, you will be permanently blocked for timewasting.
placeholder: YES or NO
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: Environment label: Environment

View File

@ -4,64 +4,50 @@ on:
push: push:
tags: tags:
- 'v*.*.*' - 'v*.*.*'
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
build-docker-images: build-docker-images:
if: ${{ !github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout -
uses: actions/checkout@v4 name: Checkout
uses: actions/checkout@v3
- name: Downcase repo -
name: Downcase repo
run: echo REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV run: echo REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
-
- name: Docker meta name: Docker meta
id: docker_meta id: docker_meta
uses: docker/metadata-action@v5 uses: crazy-max/ghaction-docker-meta@v3
with: with:
images: | images: ${{ env.REPOSITORY }},ghcr.io/${{ env.REPOSITORY }}
${{ env.REPOSITORY }},enable=${{ github.event_name != 'pull_request' }} tag-sha: false
ghcr.io/${{ env.REPOSITORY }} -
tags: | name: Set up QEMU
type=semver,pattern={{version}},prefix=v uses: docker/setup-qemu-action@v2
type=ref,event=pr -
flavor: | name: Set up Docker Buildx
latest=auto uses: docker/setup-buildx-action@v2
-
- name: Set up QEMU name: Login to DockerHub
uses: docker/setup-qemu-action@v3 uses: docker/login-action@v2
- 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: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }} password: ${{ secrets.GH_PAT }}
-
- name: Build and push name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v3
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8
push: true push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}

View File

@ -1,31 +1,5 @@
# Changelog # Changelog
## v3.3.24 (2025/06/04)
* Remove hidden character
## v3.3.23 (2025/06/04)
* Update base image to bookworm. Thanks @rwjack
## v3.3.22 (2025/06/03)
* Disable search engine choice screen
* Fix headless=false stalling. Thanks @MAKMED1337
* Change from click to keys. Thanks @sh4dowb
* Don't open devtools
* Bump Chromium to v137 for build
* Bump requirements
## 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) ## v3.3.18 (2024/05/20)
* Fix LANG ENV for Linux * Fix LANG ENV for Linux

View File

@ -1,4 +1,4 @@
FROM python:3.11-slim-bookworm as builder FROM python:3.11-slim-bullseye as builder
# Build dummy packages to skip installing them and their dependencies # Build dummy packages to skip installing them and their dependencies
RUN apt-get update \ RUN apt-get update \
@ -12,7 +12,7 @@ RUN apt-get update \
&& equivs-build adwaita-icon-theme \ && equivs-build adwaita-icon-theme \
&& mv adwaita-icon-theme_*.deb /adwaita-icon-theme.deb && mv adwaita-icon-theme_*.deb /adwaita-icon-theme.deb
FROM python:3.11-slim-bookworm FROM python:3.11-slim-bullseye
# Copy dummy packages # Copy dummy packages
COPY --from=builder /*.deb / COPY --from=builder /*.deb /
@ -62,17 +62,17 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/usr/local/bin/python", "-u", "/app/flaresolverr.py"] CMD ["/usr/local/bin/python", "-u", "/app/flaresolverr.py"]
# Local build # Local build
# docker build -t ngosang/flaresolverr:3.3.24 . # docker build -t ngosang/flaresolverr:3.3.18 .
# docker run -p 8191:8191 ngosang/flaresolverr:3.3.24 # docker run -p 8191:8191 ngosang/flaresolverr:3.3.18
# Multi-arch build # Multi-arch build
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker buildx create --use # docker buildx create --use
# docker buildx build -t ngosang/flaresolverr:3.3.24 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 . # docker buildx build -t ngosang/flaresolverr:3.3.18 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 .
# add --push to publish in DockerHub # add --push to publish in DockerHub
# Test multi-arch build # Test multi-arch build
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker buildx create --use # docker buildx create --use
# docker buildx build -t ngosang/flaresolverr:3.3.24 --platform linux/arm/v7 --load . # docker buildx build -t ngosang/flaresolverr:3.3.18 --platform linux/arm/v7 --load .
# docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.24 # docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.18

View File

@ -87,10 +87,10 @@ This is the recommended way for Windows users.
### From source code (FreeBSD/TrueNAS CORE) ### From source code (FreeBSD/TrueNAS CORE)
* Run `pkg install chromium python311 py311-pip xorg-vfbserver` command to install the required dependencies. * 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. * Clone this repository and open a shell in that path.
* Run `python3.11 -m pip install -r requirements.txt` command to install FlareSolverr dependencies. * Run `python3.9 -m pip install -r requirements.txt` command to install FlareSolverr dependencies.
* Run `python3.11 src/flaresolverr.py` command to start FlareSolverr. * Run `python3.9 src/flaresolverr.py` command to start FlareSolverr.
### Systemd service ### Systemd service
@ -317,7 +317,7 @@ solve a captcha.
If this is the case, FlareSolverr will return the error `Captcha detected but no automatic solver is configured.` If this is the case, FlareSolverr will return the error `Captcha detected but no automatic solver is configured.`
FlareSolverr can be customized to solve the CAPTCHA automatically by setting the environment variable `CAPTCHA_SOLVER` FlareSolverr can be customized to solve the CAPTCHA automatically by setting the environment variable `CAPTCHA_SOLVER`
to the file name of one of the adapters inside the `/captcha` directory. to the file name of one of the adapters inside the [/captcha](src/captcha) directory.
## Related projects ## Related projects

View File

@ -1,19 +0,0 @@
[Unit]
Description=FlareSolverr
After=network.target
[Service]
SyslogIdentifier=flaresolverr
Restart=always
RestartSec=5
Type=simple
User=flaresolverr
Group=flaresolverr
Environment="LOG_LEVEL=info"
Environment="CAPTCHA_SOLVER=none"
WorkingDirectory=/opt/flaresolverr
ExecStart=/opt/flaresolverr/flaresolverr
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target

View File

@ -1,6 +1,6 @@
{ {
"name": "flaresolverr", "name": "flaresolverr",
"version": "3.3.24", "version": "3.3.18",
"description": "Proxy server to bypass Cloudflare protection", "description": "Proxy server to bypass Cloudflare protection",
"author": "Diego Heras (ngosang / ngosang@hotmail.es)", "author": "Diego Heras (ngosang / ngosang@hotmail.es)",
"license": "MIT" "license": "MIT"

View File

@ -1,11 +1,11 @@
bottle==0.12.25 bottle==0.12.25
waitress==3.0.1 waitress==2.1.2
selenium==4.15.2 selenium==4.15.2
func-timeout==4.3.5 func-timeout==4.3.5
prometheus-client==0.17.1 prometheus-client==0.17.1
# required by undetected_chromedriver # required by undetected_chromedriver
requests==2.32.3 requests==2.31.0
certifi==2024.07.04 certifi==2023.7.22
websockets==11.0.3 websockets==11.0.3
# only required for linux and macos # only required for linux and macos
xvfbwrapper==0.2.9; platform_system != "Windows" xvfbwrapper==0.2.9; platform_system != "Windows"

View File

@ -25,7 +25,7 @@ def clean_files():
def download_chromium(): def download_chromium():
# https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/ # https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/
revision = "1453032" if os.name == 'nt' else '1453031' revision = "1260008" if os.name == 'nt' else '1260015'
arch = 'Win_x64' if os.name == 'nt' else 'Linux_x64' arch = 'Win_x64' if os.name == 'nt' else 'Linux_x64'
dl_file = 'chrome-win' if os.name == 'nt' else 'chrome-linux' 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') dl_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, 'dist_chrome')

View File

@ -101,9 +101,6 @@ if __name__ == "__main__":
logging.info(f'FlareSolverr {utils.get_flaresolverr_version()}') logging.info(f'FlareSolverr {utils.get_flaresolverr_version()}')
logging.debug('Debug log enabled') logging.debug('Debug log enabled')
# Get current OS for global variable
utils.get_current_platform()
# test browser installation # test browser installation
flaresolverr_service.test_browser_installation() flaresolverr_service.test_browser_installation()

View File

@ -3,14 +3,12 @@ import platform
import sys import sys
import time import time
from datetime import timedelta from datetime import timedelta
from html import escape from urllib.parse import unquote
from urllib.parse import unquote, quote
from func_timeout import FunctionTimedOut, func_timeout from func_timeout import FunctionTimedOut, func_timeout
from selenium.common import TimeoutException from selenium.common import TimeoutException
from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.expected_conditions import ( from selenium.webdriver.support.expected_conditions import (
presence_of_element_located, staleness_of, title_is) presence_of_element_located, staleness_of, title_is)
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
@ -42,7 +40,7 @@ CHALLENGE_TITLES = [
] ]
CHALLENGE_SELECTORS = [ CHALLENGE_SELECTORS = [
# Cloudflare # Cloudflare
'#cf-challenge-running', '.ray_id', '.attack-box', '#cf-please-wait', '#challenge-spinner', '#trk_jschal_js', '#turnstile-wrapper', '.lds-ring', '#cf-challenge-running', '.ray_id', '.attack-box', '#cf-please-wait', '#challenge-spinner', '#trk_jschal_js',
# Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands # Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
'td.info #js_info', 'td.info #js_info',
# Fairlane / pararius.com # Fairlane / pararius.com
@ -121,7 +119,7 @@ def _controller_v1_handler(req: V1RequestBase) -> V1ResponseBase:
logging.warning("Request parameter 'userAgent' was removed in FlareSolverr v2.") logging.warning("Request parameter 'userAgent' was removed in FlareSolverr v2.")
# set default values # set default values
if req.maxTimeout is None or int(req.maxTimeout) < 1: if req.maxTimeout is None or req.maxTimeout < 1:
req.maxTimeout = 60000 req.maxTimeout = 60000
# execute the command # execute the command
@ -222,7 +220,7 @@ def _cmd_sessions_destroy(req: V1RequestBase) -> V1ResponseBase:
def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT: def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT:
timeout = int(req.maxTimeout) / 1000 timeout = req.maxTimeout / 1000
driver = None driver = None
try: try:
if req.session: if req.session:
@ -247,7 +245,6 @@ def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT:
raise Exception('Error solving the challenge. ' + str(e).replace('\n', '\\n')) raise Exception('Error solving the challenge. ' + str(e).replace('\n', '\\n'))
finally: finally:
if not req.session and driver is not None: if not req.session and driver is not None:
if utils.PLATFORM_VERSION == "nt":
driver.close() driver.close()
driver.quit() driver.quit()
logging.debug('A used instance of webdriver has been destroyed') logging.debug('A used instance of webdriver has been destroyed')
@ -256,8 +253,17 @@ def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT:
def click_verify(driver: WebDriver): def click_verify(driver: WebDriver):
try: try:
logging.debug("Try to find the Cloudflare verify checkbox...") logging.debug("Try to find the Cloudflare verify checkbox...")
iframe = driver.find_element(By.XPATH, "//iframe[starts-with(@id, 'cf-chl-widget-')]")
driver.switch_to.frame(iframe)
checkbox = driver.find_element(
by=By.XPATH,
value='//*[@id="challenge-stage"]/div/label/input',
)
if checkbox:
actions = ActionChains(driver) actions = ActionChains(driver)
actions.pause(5).send_keys(Keys.TAB).pause(1).send_keys(Keys.SPACE).perform() actions.move_to_element_with_offset(checkbox, 5, 7)
actions.click(checkbox)
actions.perform()
logging.debug("Cloudflare verify checkbox found and clicked!") logging.debug("Cloudflare verify checkbox found and clicked!")
except Exception: except Exception:
logging.debug("Cloudflare verify checkbox not found on the page.") logging.debug("Cloudflare verify checkbox not found on the page.")
@ -282,6 +288,22 @@ def click_verify(driver: WebDriver):
time.sleep(2) 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: def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> ChallengeResolutionT:
res = ChallengeResolutionT({}) res = ChallengeResolutionT({})
res.status = STATUS_OK res.status = STATUS_OK
@ -293,7 +315,8 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge
if method == 'POST': if method == 'POST':
_post_request(req, driver) _post_request(req, driver)
else: else:
driver.get(req.url) access_page(driver, req.url)
driver = get_correct_window(driver)
# set cookies if required # set cookies if required
if req.cookies is not None and len(req.cookies) > 0: if req.cookies is not None and len(req.cookies) > 0:
@ -305,7 +328,8 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge
if method == 'POST': if method == 'POST':
_post_request(req, driver) _post_request(req, driver)
else: else:
driver.get(req.url) access_page(driver, req.url)
driver = get_correct_window(driver)
# wait for the page # wait for the page
if utils.get_config_log_html(): if utils.get_config_log_html():
@ -414,7 +438,7 @@ def _post_request(req: V1RequestBase, driver: WebDriver):
value = unquote(parts[1]) value = unquote(parts[1])
except Exception: except Exception:
value = parts[1] value = parts[1]
post_form += f'<input type="text" name="{escape(quote(name))}" value="{escape(quote(value))}"><br>' post_form += f'<input type="text" name="{name}" value="{value}"><br>'
post_form += '</form>' post_form += '</form>'
html_content = f""" html_content = f"""
<!DOCTYPE html> <!DOCTYPE html>
@ -424,4 +448,6 @@ def _post_request(req: V1RequestBase, driver: WebDriver):
<script>document.getElementById('hackForm').submit();</script> <script>document.getElementById('hackForm').submit();</script>
</body> </body>
</html>""" </html>"""
driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content)) driver.get("data:text/html;charset=utf-8," + html_content)
driver.start_session()
driver.start_session() # required to bypass Cloudflare

View File

@ -66,7 +66,6 @@ class SessionsStorage:
return False return False
session = self.sessions.pop(session_id) session = self.sessions.pop(session_id)
if utils.PLATFORM_VERSION == "nt":
session.driver.close() session.driver.close()
session.driver.quit() session.driver.quit()
return True return True

View File

@ -507,6 +507,8 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
"Page.addScriptToEvaluateOnNewDocument", "Page.addScriptToEvaluateOnNewDocument",
{ {
"source": """ "source": """
Object.defineProperty(window, "navigator", {
Object.defineProperty(window, "navigator", { Object.defineProperty(window, "navigator", {
value: new Proxy(navigator, { value: new Proxy(navigator, {
has: (target, key) => (key === "webdriver" ? false : key in target), has: (target, key) => (key === "webdriver" ? false : key in target),

View File

@ -2,7 +2,6 @@ import asyncio
from collections.abc import Mapping from collections.abc import Mapping
from collections.abc import Sequence from collections.abc import Sequence
from functools import wraps from functools import wraps
import os
import logging import logging
import threading import threading
import time import time
@ -188,6 +187,5 @@ def test():
time.sleep(10) time.sleep(10)
if os.name == "nt":
driver.close() driver.close()
driver.quit() driver.quit()

View File

@ -68,10 +68,8 @@ class Patcher(object):
# check if version_main_int is less than or equal to e.g 114 # 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 self.is_old_chromedriver = version_main and version_main_int <= 114
except (ValueError,TypeError): except (ValueError,TypeError):
# Check not running inside Docker # If the conversion fails, print an error message
if not os.path.exists("/app/chromedriver"): print("version_main cannot be converted to an integer")
# 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 # Set self.is_old_chromedriver to False if the conversion fails
self.is_old_chromedriver = False self.is_old_chromedriver = False

View File

@ -1,18 +1,16 @@
import json import json
import logging import logging
import os import os
import platform
import re import re
import shutil import shutil
import sys
import tempfile
import urllib.parse import urllib.parse
import tempfile
import sys
from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.chrome.webdriver import WebDriver
import undetected_chromedriver as uc import undetected_chromedriver as uc
FLARESOLVERR_VERSION = None FLARESOLVERR_VERSION = None
PLATFORM_VERSION = None
CHROME_EXE_PATH = None CHROME_EXE_PATH = None
CHROME_MAJOR_VERSION = None CHROME_MAJOR_VERSION = None
USER_AGENT = None USER_AGENT = None
@ -40,13 +38,6 @@ def get_flaresolverr_version() -> str:
FLARESOLVERR_VERSION = json.loads(f.read())['version'] FLARESOLVERR_VERSION = json.loads(f.read())['version']
return FLARESOLVERR_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: def create_proxy_extension(proxy: dict) -> str:
parsed_url = urllib.parse.urlparse(proxy['url']) parsed_url = urllib.parse.urlparse(proxy['url'])
@ -130,15 +121,12 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
options = uc.ChromeOptions() options = uc.ChromeOptions()
options.add_argument('--no-sandbox') options.add_argument('--no-sandbox')
options.add_argument('--window-size=1920,1080') options.add_argument('--window-size=1920,1080')
options.add_argument('--disable-search-engine-choice-screen')
# todo: this param shows a warning in chrome head-full # todo: this param shows a warning in chrome head-full
options.add_argument('--disable-setuid-sandbox') options.add_argument('--disable-setuid-sandbox')
options.add_argument('--disable-dev-shm-usage') options.add_argument('--disable-dev-shm-usage')
# this option removes the zygote sandbox (it seems that the resolution is a bit faster) # this option removes the zygote sandbox (it seems that the resolution is a bit faster)
options.add_argument('--no-zygote') options.add_argument('--no-zygote')
# attempt to fix Docker ARM32 build # attempt to fix Docker ARM32 build
IS_ARMARCH = platform.machine().startswith(('arm', 'aarch'))
if IS_ARMARCH:
options.add_argument('--disable-gpu-sandbox') options.add_argument('--disable-gpu-sandbox')
options.add_argument('--disable-software-rasterizer') options.add_argument('--disable-software-rasterizer')
options.add_argument('--ignore-certificate-errors') options.add_argument('--ignore-certificate-errors')
@ -177,6 +165,8 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
# For normal headless mode: # For normal headless mode:
# options.add_argument('--headless') # options.add_argument('--headless')
options.add_argument("--auto-open-devtools-for-tabs")
# if we are inside the Docker container, we avoid downloading the driver # if we are inside the Docker container, we avoid downloading the driver
driver_exe_path = None driver_exe_path = None
version_main = None version_main = None
@ -324,7 +314,6 @@ def get_user_agent(driver=None) -> str:
raise Exception("Error getting browser User-Agent. " + str(e)) raise Exception("Error getting browser User-Agent. " + str(e))
finally: finally:
if driver is not None: if driver is not None:
if PLATFORM_VERSION == "nt":
driver.close() driver.close()
driver.quit() driver.quit()