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):