Merge pull request #6 from ThePhaseless/chromium

Chromium
This commit is contained in:
Jakub Orchowski 2024-10-18 18:35:05 +02:00 committed by GitHub
commit 0fa7209311
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 633 additions and 933 deletions

View File

@ -1,8 +1,27 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bullseye FROM python:3.12
# Inspired by https://github.com/Hudrolax/uc-docker-alpine/
# Install build dependencies
RUN apt update && apt upgrade -y && apt install -y\
curl \
wget \
unzip \
gnupg \
bash \
stow
# Install dependencies
RUN apt install -y \
xvfb \
x11vnc \
fluxbox \
xterm \
git \
ca-certificates \
pipx \
chromium
USER vscode
RUN pipx install poetry RUN pipx install poetry
RUN poetry config virtualenvs.in-project true ENV DISPLAY=:0
RUN sudo apt update && sudo apt upgrade -y # RUN poetry config virtualenvs.in-project true
RUN sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN sudo apt install -y ./google-chrome-stable_current_amd64.deb

View File

@ -6,7 +6,12 @@
"build": { "build": {
"dockerfile": "Dockerfile" "dockerfile": "Dockerfile"
}, },
"runArgs": ["-p", "8181:8191"], "runArgs": [
"-p",
"8181:8191",
"--cap-add",
"SYS_ADMIN"
],
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [
@ -19,19 +24,17 @@
} }
} }
}, },
"postStartCommand": "poetry install", "postStartCommand": "./entrypoint.sh",
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
// "features": {}, // "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [6080], "forwardPorts": [
"features": { 5900
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, ]
"ghcr.io/devcontainers/features/desktop-lite:1": {}
}
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt", // "postCreateCommand": "pip3 install --user -r requirements.txt",
// Configure tool-specific properties. // Configure tool-specific properties.
// "customizations": {}, // "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root" // "remoteUser": "root"
} }

View File

@ -31,6 +31,13 @@ jobs:
# with sigstore/fulcio when running outside of PRs. # with sigstore/fulcio when running outside of PRs.
id-token: write id-token: write
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -81,13 +88,19 @@ jobs:
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- name: Upload error logs
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: error-screenshots
path: ./errors/*
# Sign the resulting Docker image digest except on PRs. # Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker # This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish # repository is public to avoid leaking data. If you would like to publish

View File

@ -1,4 +1,37 @@
FROM python:3.12 FROM python:3.12-alpine
# Inspired by https://github.com/Hudrolax/uc-docker-alpine/
# Install build dependencies
RUN apk update && apk upgrade && \
apk add --no-cache --virtual .build-deps \
alpine-sdk \
curl \
wget \
unzip \
gnupg
# Install dependencies
RUN apk add --no-cache \
xvfb \
x11vnc \
fluxbox \
xterm \
libffi-dev \
openssl-dev \
zlib-dev \
bzip2-dev \
readline-dev \
git \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont \
pipx \
chromium \
chromium-chromedriver
WORKDIR /app WORKDIR /app
EXPOSE 8191 EXPOSE 8191
@ -11,26 +44,15 @@ ENV \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
# do not ask any interactive question # do not ask any interactive question
POETRY_NO_INTERACTION=1 \ POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=true POETRY_VIRTUALENVS_IN_PROJECT=true \
DISPLAY=:0
RUN apt update && apt upgrade -y
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN apt install -y --no-install-recommends --no-install-suggests ./google-chrome-stable_current_amd64.deb && rm ./google-chrome-stable_current_amd64.deb
RUN apt install pipx -y
RUN pipx ensurepath
RUN pipx install poetry RUN pipx install poetry
ENV PATH="/root/.local/bin:$PATH" ENV PATH="/root/.local/bin:$PATH"
COPY pyproject.toml poetry.lock ./ COPY pyproject.toml poetry.lock ./
RUN poetry install RUN poetry install
ENV INSTALL_NOVNC=false
COPY novnc.sh .
RUN ./novnc.sh
ENV DISPLAY=:1.0
COPY fix_nodriver.py ./ COPY fix_nodriver.py ./
RUN . /app/.venv/bin/activate && python fix_nodriver.py RUN . /app/.venv/bin/activate && python fix_nodriver.py
COPY . . COPY . .
RUN /usr/local/share/desktop-init.sh && poetry run pytest CMD ["./entrypoint.sh", "&&", ".", "/app/.venv/bin/activate", "&&", "python", "main.py"]
CMD /usr/local/share/desktop-init.sh && . /app/.venv/bin/activate && python main.py

View File

@ -4,7 +4,6 @@ An alternative to [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) a
> [!IMPORTANT] > [!IMPORTANT]
> Currenly, due to [bug in nodriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver/issues/1954), if you want to run this project ouside of prebuild container, you have to run `python fix_nodriver.py` after creating venv to patch the library. > Currenly, due to [bug in nodriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver/issues/1954), if you want to run this project ouside of prebuild container, you have to run `python fix_nodriver.py` after creating venv to patch the library.
> [!NOTE] > [!NOTE]
> Thanks to FastAPI implementation, now you can also see the API documentation at `/docs` or `/` (redirect to `/docs`) endpoints. > Thanks to FastAPI implementation, now you can also see the API documentation at `/docs` or `/` (redirect to `/docs`) endpoints.
@ -15,14 +14,16 @@ Long story short, I created it in like 3 days, so if you get any bugs/hangs etc.
I focus maily on Cloudflare, which is tested daily, any other anti-bot challenges should pass out of the box, but if any issues, please report these providers with an example website ❤️ I focus maily on Cloudflare, which is tested daily, any other anti-bot challenges should pass out of the box, but if any issues, please report these providers with an example website ❤️
## Troubleshooting ## Troubleshooting
1. Clone repo to the host that has the container has issues on. 1. Clone repo to the host that has the container has issues on.
2. Using vscode and `SSH extention`, connect to the host and open repo in it. 2. Using vscode and `SSH extention`, connect to the host and open repo in it.
3. Download `devcontainers` extention and reopen repo in container (with `CTRL + SHIFT + P` -> `Reopen in devcontainer`) 3. Download `devcontainers` extention and reopen repo in container (with `CTRL + SHIFT + P` -> `Reopen in devcontainer`)
4. Open forwarded port from `Ports` tab in your browser to see emulated display 4. Write down the port in `Ports` tab in vscode
5. Check if `chrome` works by running in VNCs terminal command `chrome --no-sandbox` 5. Open <https://novnc.com/noVNC/vnc.html> in your pc's browser and using settings on left under websocket, set host to `localhost` nad port to the port you wrote down
6. If chrome works, run API by pressing F5 in vscode 6. Check if `chromium` works by running in VNCs terminal command `chromium --no-sandbox`
7. In Prowlarr (or target client) change port byparrs port to `8181` instead of `8191` (Port opened by and pointing to devcontainer) 7. If chrome works, run API by pressing F5 in vscode
8. Check if everything works by testing byparr and observing VNC in browser 8. In Prowlarr (or target client) change port byparr's port to `8181` instead of `8191` (Port opened by and pointing to devcontainer)
9. Check if everything works by testing byparr and observing VNC in browser
## Usage ## Usage

15
entrypoint.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/sh
rm -f /tmp/.X0-lock
# Run Xvfb on dispaly 0.
Xvfb :0 -screen 0 1280x720x16 &
# Run fluxbox windows manager on display 0.
fluxbox -display :0 &
# Run x11vnc on display 0
x11vnc -display :0 -forever -ncache 10 &
# Add delay
sleep 5

View File

@ -5,11 +5,13 @@ from __future__ import annotations
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from platform import python_version
env_path = os.getenv("VIRTUAL_ENV") env_path = os.getenv("VIRTUAL_ENV")
if env_path is None: if env_path is None:
env_path = Path(os.__file__).parent.parent.parent.as_posix() env_path = Path(os.__file__).parent.parent.parent.as_posix()
nodriver_path = Path(env_path + "/lib/python3.12/site-packages/nodriver/cdp/network.py") python_version = python_version().split(".")[0:2]
nodriver_path = Path(env_path + f"/lib/python{'.'.join(python_version)}/site-packages/nodriver/cdp/network.py")
if not nodriver_path.exists(): if not nodriver_path.exists():
msg = f"{nodriver_path} not found" msg = f"{nodriver_path} not found"
raise FileNotFoundError(msg) raise FileNotFoundError(msg)

10
main.py
View File

@ -6,7 +6,7 @@ import time
import uvicorn import uvicorn
import uvicorn.config import uvicorn.config
from fastapi import FastAPI from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from src.models.requests import LinkRequest, LinkResponse from src.models.requests import LinkRequest, LinkResponse
@ -36,8 +36,12 @@ async def read_item(request: LinkRequest):
timeout = request.maxTimeout timeout = request.maxTimeout
if timeout == 0: if timeout == 0:
timeout = None timeout = None
try:
challenged = await asyncio.wait_for(bypass_cloudflare(page), timeout=timeout) challenged = await asyncio.wait_for(bypass_cloudflare(page), timeout=timeout)
except Exception as e:
logger.error(await page.get_content())
logger.fatal("Element is a string, please report this to Byparr dev")
raise HTTPException(detail="Couldn't bypass", status_code=408) from e
logger.info(f"Got webpage: {request.url}") logger.info(f"Got webpage: {request.url}")

429
novnc.sh
View File

@ -1,429 +0,0 @@
#!/usr/bin/env bash
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
#
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/desktop-lite.md
# Maintainer: The VS Code and Codespaces Teams
NOVNC_VERSION="${NOVNCVERSION:-"1.2.0"}" # TODO: Add in a 'latest' auto-detect and swap name to 'version'
VNC_PASSWORD=${PASSWORD:-"vscode"}
if [ "$VNC_PASSWORD" = "noPassword" ]; then
unset VNC_PASSWORD
fi
NOVNC_PORT="${WEBPORT:-6080}"
VNC_PORT="${VNCPORT:-5901}"
INSTALL_NOVNC="${INSTALL_NOVNC:-"true"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
WEBSOCKETIFY_VERSION=0.10.0
package_list="
tigervnc-standalone-server \
tigervnc-common \
fluxbox \
dbus-x11 \
x11-utils \
x11-xserver-utils \
xdg-utils \
fbautostart \
at-spi2-core \
eterm \
gnome-keyring \
libx11-dev \
libxkbfile-dev \
libsecret-1-dev \
libgbm-dev \
libnotify4 \
libnss3 \
libxss1 \
fonts-wqy-microhei \
fonts-droid-fallback \
htop \
ncdu \
curl \
ca-certificates\
unzip \
nano \
locales"
# Packages to attempt to install if essential tools are missing (ie: vncpasswd).
# This is useful, at least, for Ubuntu 22.04 (jammy)
package_list_additional="
tigervnc-tools"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
if [ "$(id -u)" -ne 0 ]; then
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
exit 1
fi
# Determine the appropriate non-root user
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
USERNAME=""
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
USERNAME=${CURRENT_USER}
break
fi
done
if [ "${USERNAME}" = "" ]; then
USERNAME=root
fi
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
USERNAME=root
fi
# Add default Fluxbox config files if none are already present
fluxbox_apps="$(cat \
<< 'EOF'
[transient] (role=GtkFileChooserDialog)
[Dimensions] {70% 70%}
[Position] (CENTER) {0 0}
[end]
EOF
)"
fluxbox_init="$(cat \
<< 'EOF'
session.configVersion: 13
session.menuFile: ~/.fluxbox/menu
session.keyFile: ~/.fluxbox/keys
session.styleFile: /usr/share/fluxbox/styles/qnx-photon
session.screen0.workspaces: 1
session.screen0.workspacewarping: false
session.screen0.toolbar.widthPercent: 100
session.screen0.strftimeFormat: %a %l:%M %p
session.screen0.toolbar.tools: RootMenu, clock, iconbar, systemtray
session.screen0.workspaceNames: One,
EOF
)"
fluxbox_menu="$(cat \
<< 'EOF'
[begin] ( Application Menu )
[exec] (File Manager) { nautilus ~ } <>
[exec] (Text Editor) { mousepad } <>
[exec] (Terminal) { tilix -w ~ -e $(readlink -f /proc/$$/exe) -il } <>
[exec] (Web Browser) { x-www-browser --disable-dev-shm-usage } <>
[submenu] (System) {}
[exec] (Set Resolution) { tilix -t "Set Resolution" -e bash /usr/local/bin/set-resolution } <>
[exec] (Edit Application Menu) { mousepad ~/.fluxbox/menu } <>
[exec] (Passwords and Keys) { seahorse } <>
[exec] (Top Processes) { tilix -t "Top" -e htop } <>
[exec] (Disk Utilization) { tilix -t "Disk Utilization" -e ncdu / } <>
[exec] (Editres) {editres} <>
[exec] (Xfontsel) {xfontsel} <>
[exec] (Xkill) {xkill} <>
[exec] (Xrefresh) {xrefresh} <>
[end]
[config] (Configuration)
[workspaces] (Workspaces)
[end]
EOF
)"
# Copy config files if the don't already exist
copy_fluxbox_config() {
local target_dir="$1"
mkdir -p "${target_dir}/.fluxbox"
touch "${target_dir}/.Xmodmap"
if [ ! -e "${target_dir}/.fluxbox/apps" ]; then
echo "${fluxbox_apps}" > "${target_dir}/.fluxbox/apps"
fi
if [ ! -e "${target_dir}/.fluxbox/init" ]; then
echo "${fluxbox_init}" > "${target_dir}/.fluxbox/init"
fi
if [ ! -e "${target_dir}/.fluxbox/menu" ]; then
echo "${fluxbox_menu}" > "${target_dir}/.fluxbox/menu"
fi
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
fi
}
# Checks if packages are installed and installs them if not
check_packages() {
if ! dpkg -s "$@" > /dev/null 2>&1; then
apt_get_update
apt-get -y install --no-install-recommends "$@"
fi
}
##########################
# Install starts here #
##########################
# Ensure apt is in non-interactive to avoid prompts
export DEBIAN_FRONTEND=noninteractive
apt_get_update
# On older Ubuntu, Tilix is in a PPA. on Debian stretch its in backports.
# if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then
# . /etc/os-release
# if [ "${ID}" = "ubuntu" ]; then
# check_packages apt-transport-https software-properties-common
# add-apt-repository -y ppa:webupd8team/terminix
# elif [ "${VERSION_CODENAME}" = "stretch" ]; then
# echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/stretch-backports.list
# fi
# apt-get update
# if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then
# echo "(!) WARNING: Tilix not available on ${ID} ${VERSION_CODENAME} architecture $(uname -m). Skipping."
# else
# package_list="${package_list} tilix"
# fi
# else
# package_list="${package_list} tilix"
# fi
# Install X11, fluxbox and VS Code dependencies
check_packages ${package_list}
# if Ubuntu-24.04, noble(numbat) found, then will install libasound2-dev instead of libasound2.
# this change is temporary, https://packages.ubuntu.com/noble/libasound2 will switch to libasound2 once it is available for Ubuntu-24.04, noble(numbat)
# . /etc/os-release
# if [ "${ID}" = "ubuntu" ] && [ "${VERSION_CODENAME}" = "noble" ]; then
# echo "Ubuntu 24.04, Noble(Numbat) detected. Installing libasound2-dev package..."
# check_packages "libasound2-dev"
# else
# check_packages "libasound2"
# fi
# On newer versions of Ubuntu (22.04),
# we need an additional package that isn't provided in earlier versions
if ! type vncpasswd > /dev/null 2>&1; then
check_packages ${package_list_additional}
fi
# Install Emoji font if available in distro - Available in Debian 10+, Ubuntu 18.04+
# if dpkg-query -W fonts-noto-color-emoji > /dev/null 2>&1 && ! dpkg -s fonts-noto-color-emoji > /dev/null 2>&1; then
# apt-get -y install --no-install-recommends fonts-noto-color-emoji
# fi
# Check at least one locale exists
if ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
fi
# Install the Cascadia Code fonts - https://github.com/microsoft/cascadia-code
# if [ ! -d "/usr/share/fonts/truetype/cascadia" ]; then
# curl -sSL https://github.com/microsoft/cascadia-code/releases/download/v2008.25/CascadiaCode-2008.25.zip -o /tmp/cascadia-fonts.zip
# unzip /tmp/cascadia-fonts.zip -d /tmp/cascadia-fonts
# mkdir -p /usr/share/fonts/truetype/cascadia
# mv /tmp/cascadia-fonts/ttf/* /usr/share/fonts/truetype/cascadia/
# rm -rf /tmp/cascadia-fonts.zip /tmp/cascadia-fonts
# fi
# Install noVNC
if [ "${INSTALL_NOVNC}" = "true" ] && [ ! -d "/usr/local/novnc" ]; then
mkdir -p /usr/local/novnc
curl -sSL https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.zip -o /tmp/novnc-install.zip
unzip /tmp/novnc-install.zip -d /usr/local/novnc
cp /usr/local/novnc/noVNC-${NOVNC_VERSION}/vnc.html /usr/local/novnc/noVNC-${NOVNC_VERSION}/index.html
curl -sSL https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.zip -o /tmp/websockify-install.zip
unzip /tmp/websockify-install.zip -d /usr/local/novnc
ln -s /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION} /usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/websockify
rm -f /tmp/websockify-install.zip /tmp/novnc-install.zip
# Install noVNC dependencies and use them.
check_packages python3-minimal python3-numpy
sed -i -E 's/^python /python3 /' /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION}/run
fi
# Set up folders for scripts and init files
mkdir -p /var/run/dbus /usr/local/etc/vscode-dev-containers/
# Script to change resolution of desktop
cat << EOF > /usr/local/bin/set-resolution
#!/bin/bash
RESOLUTION=\${1:-\${VNC_RESOLUTION:-1920x1080}}
DPI=\${2:-\${VNC_DPI:-96}}
IGNORE_ERROR=\${3:-"false"}
if [ -z "\$1" ]; then
echo -e "**Current Settings **\n"
xrandr
echo -n -e "\nEnter new resolution (WIDTHxHEIGHT, blank for \${RESOLUTION}, Ctrl+C to abort).\n> "
read NEW_RES
if [ "\${NEW_RES}" != "" ]; then
RESOLUTION=\${NEW_RES}
fi
if ! echo "\${RESOLUTION}" | grep -E '[0-9]+x[0-9]+' > /dev/null; then
echo -e "\nInvalid resolution format!\n"
exit 1
fi
if [ -z "\$2" ]; then
echo -n -e "\nEnter new DPI (blank for \${DPI}, Ctrl+C to abort).\n> "
read NEW_DPI
if [ "\${NEW_DPI}" != "" ]; then
DPI=\${NEW_DPI}
fi
fi
fi
xrandr --fb \${RESOLUTION} --dpi \${DPI} > /dev/null 2>&1
if [ \$? -ne 0 ] && [ "\${IGNORE_ERROR}" != "true" ]; then
echo -e "\nFAILED TO SET RESOLUTION!\n"
exit 1
fi
echo -e "\nSuccess!\n"
EOF
# Container ENTRYPOINT script
cat << EOF > /usr/local/share/desktop-init.sh
#!/bin/bash
user_name="${USERNAME}"
group_name="$(id -gn ${USERNAME})"
LOG=/tmp/container-init.log
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-"autolaunch:"}"
export DISPLAY="${DISPLAY:-:1}"
export VNC_RESOLUTION="${VNC_RESOLUTION:-1440x768x16}"
export LANG="${LANG:-"en_US.UTF-8"}"
export LANGUAGE="${LANGUAGE:-"en_US.UTF-8"}"
# Execute the command it not already running
startInBackgroundIfNotRunning()
{
log "Starting \$1."
echo -e "\n** \$(date) **" | sudoIf tee -a /tmp/\$1.log > /dev/null
if ! pgrep -x \$1 > /dev/null; then
keepRunningInBackground "\$@"
while ! pgrep -x \$1 > /dev/null; do
sleep 1
done
log "\$1 started."
else
echo "\$1 is already running." | sudoIf tee -a /tmp/\$1.log > /dev/null
log "\$1 is already running."
fi
}
# Keep command running in background
keepRunningInBackground()
{
(\$2 bash -c "while :; do echo [\\\$(date)] Process started.; \$3; echo [\\\$(date)] Process exited!; sleep 5; done 2>&1" | sudoIf tee -a /tmp/\$1.log > /dev/null & echo "\$!" | sudoIf tee /tmp/\$1.pid > /dev/null)
}
# Use sudo to run as root when required
sudoIf()
{
if [ "\$(id -u)" -ne 0 ]; then
sudo "\$@"
else
"\$@"
fi
}
# Use sudo to run as non-root user if not already running
sudoUserIf()
{
if [ "\$(id -u)" -eq 0 ] && [ "\${user_name}" != "root" ]; then
sudo -u \${user_name} "\$@"
else
"\$@"
fi
}
# Log messages
log()
{
echo -e "[\$(date)] \$@" | sudoIf tee -a \$LOG > /dev/null
}
log "** SCRIPT START **"
# Start dbus.
log 'Running "/etc/init.d/dbus start".'
if [ -f "/var/run/dbus/pid" ] && ! pgrep -x dbus-daemon > /dev/null; then
sudoIf rm -f /var/run/dbus/pid
fi
sudoIf /etc/init.d/dbus start 2>&1 | sudoIf tee -a /tmp/dbus-daemon-system.log > /dev/null
while ! pgrep -x dbus-daemon > /dev/null; do
sleep 1
done
# Startup tigervnc server and fluxbox
sudoIf rm -rf /tmp/.X11-unix /tmp/.X*-lock
mkdir -p /tmp/.X11-unix
sudoIf chmod 1777 /tmp/.X11-unix
sudoIf chown root:\${group_name} /tmp/.X11-unix
if [ "\$(echo "\${VNC_RESOLUTION}" | tr -cd 'x' | wc -c)" = "1" ]; then VNC_RESOLUTION=\${VNC_RESOLUTION}x16; fi
screen_geometry="\${VNC_RESOLUTION%*x*}"
screen_depth="\${VNC_RESOLUTION##*x}"
# Check if VNC_PASSWORD is set and use the appropriate command
common_options="tigervncserver \${DISPLAY} -geometry \${screen_geometry} -depth \${screen_depth} -rfbport ${VNC_PORT} -dpi \${VNC_DPI:-96} -localhost -desktop fluxbox -fg"
if [ -n "\${VNC_PASSWORD+x}" ]; then
startInBackgroundIfNotRunning "Xtigervnc" sudoUserIf "\${common_options} -passwd /usr/local/etc/vscode-dev-containers/vnc-passwd"
else
startInBackgroundIfNotRunning "Xtigervnc" sudoUserIf "\${common_options} -SecurityTypes None"
fi
# Spin up noVNC if installed and not running.
if [ -d "/usr/local/novnc" ] && [ "\$(ps -ef | grep /usr/local/novnc/noVNC*/utils/launch.sh | grep -v grep)" = "" ]; then
keepRunningInBackground "noVNC" sudoIf "/usr/local/novnc/noVNC*/utils/launch.sh --listen ${NOVNC_PORT} --vnc localhost:${VNC_PORT}"
log "noVNC started."
else
log "noVNC is already running or not installed."
fi
# Run whatever was passed in
log "Executing \"\$@\"."
exec "\$@"
log "** SCRIPT EXIT **"
EOF
if [ -n "${VNC_PASSWORD+x}" ]; then
echo "${VNC_PASSWORD}" | vncpasswd -f > /usr/local/etc/vscode-dev-containers/vnc-passwd
fi
chmod +x /usr/local/share/desktop-init.sh /usr/local/bin/set-resolution
# Set up fluxbox config
copy_fluxbox_config "/root"
if [ "${USERNAME}" != "root" ]; then
copy_fluxbox_config "/home/${USERNAME}"
chown -R ${USERNAME} /home/${USERNAME}/.Xmodmap /home/${USERNAME}/.fluxbox
fi
# Clean up
rm -rf /var/lib/apt/lists/*
# Determine the message based on whether VNC_PASSWORD is set
if [ -n "${VNC_PASSWORD+x}" ]; then
PASSWORD_MESSAGE="In both cases, use the password \"${VNC_PASSWORD}\" when connecting"
else
PASSWORD_MESSAGE="In both cases, no password is required."
fi
# Display the message
cat << EOF
You now have a working desktop! Connect to in one of the following ways:
- Forward port ${NOVNC_PORT} and use a web browser to start the noVNC client (recommended)
- Forward port ${VNC_PORT} using VS Code client and connect using a VNC Viewer
${PASSWORD_MESSAGE}
(*) Done!
EOF

912
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -48,3 +48,6 @@ ignore = [
] ]
select = ["ALL"] select = ["ALL"]
extend-safe-fixes = ["D415"] extend-safe-fixes = ["D415"]
[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "function"

3
pytest.sh Executable file
View File

@ -0,0 +1,3 @@
if [ $(arch) = "x86_64" ]; then
./entrypoint.sh && . ./.venv/bin/activate && poetry run pytest
fi

View File

@ -23,8 +23,9 @@ async def new_browser():
Any exceptions that may occur during the creation of the browser instance. Any exceptions that may occur during the creation of the browser instance.
""" """
config: webdriver.Config = webdriver.Config() config: webdriver.Config = webdriver.Config(
config.sandbox = False browser_executable_path="/usr/bin/chromium", sandbox=True
)
config.add_argument(f"--load-extension={','.join(downloaded_extentions)}") config.add_argument(f"--load-extension={','.join(downloaded_extentions)}")
return await webdriver.start(config=config) return await webdriver.start(config=config)
@ -65,24 +66,26 @@ async def bypass_cloudflare(page: webdriver.Tab):
if not challenged: if not challenged:
logger.info("Found challenge") logger.info("Found challenge")
challenged = True challenged = True
loaded = False
try: try:
elem = await page.find( elem = await page.find("lds-ring", timeout=3)
"Verify you are human by completing the action below.", parent = elem.parent
timeout=3, if not isinstance(parent, Element) or parent.attributes is None:
) continue
# If challenge solves by itself for attr in parent.attributes:
if attr == "display: none; visibility: hidden;":
loaded = True
except asyncio.TimeoutError: except asyncio.TimeoutError:
if page.target.title not in CHALLENGE_TITLES: logger.debug("Challenge loaded")
return challenged else:
if not loaded:
if elem is None: logger.debug("Challenge still loading")
logger.debug("Couldn't find the title, trying other method...") continue
continue
if not isinstance(elem, Element):
logger.fatal("Element is a string, please report this to Byparr dev")
raise InvalidElementError
await page
logger.debug("Couldn't find the title, trying other method...")
elem = await page.find("input") elem = await page.find("input")
elem = elem.parent elem = elem.parent
# Get the element containing the shadow root # Get the element containing the shadow root
@ -92,10 +95,12 @@ async def bypass_cloudflare(page: webdriver.Tab):
if isinstance(inner_elem, Element): if isinstance(inner_elem, Element):
logger.debug("Clicking element") logger.debug("Clicking element")
await inner_elem.mouse_click() await inner_elem.mouse_click()
await asyncio.sleep(3)
else: else:
logger.warning( logger.warning(
"Element is a string, please report this to Byparr dev" "Element is a string, please report this to Byparr dev"
) # I really hope this never happens ) # I really hope this never happens
logger.warning(inner_elem)
else: else:
logger.warning("Coulnd't find checkbox, trying again...") logger.warning("Coulnd't find checkbox, trying again...")

View File

@ -77,7 +77,12 @@ def download_extentions():
logger.error(f"Error downloading {extention_name}, using local copy") logger.error(f"Error downloading {extention_name}, using local copy")
downloaded_extentions.append(path.as_posix()) downloaded_extentions.append(path.as_posix())
continue continue
zip_file = requests.get(extention.browser_download_url, timeout=10) try:
zip_file = requests.get(extention.browser_download_url, timeout=10)
except UnboundLocalError as e:
logger.error(f"Error downloading {extention_name}, skipping")
logger.error(e)
continue
Path(EXTENTIONS_PATH).mkdir(exist_ok=True) Path(EXTENTIONS_PATH).mkdir(exist_ok=True)
with ZipFile(io.BytesIO(zip_file.content)) as zip_obj: with ZipFile(io.BytesIO(zip_file.content)) as zip_obj:
zip_obj.extractall(f"{EXTENTIONS_PATH}/{extention_name}") zip_obj.extractall(f"{EXTENTIONS_PATH}/{extention_name}")

View File

@ -1,4 +1,5 @@
from http import HTTPStatus from http import HTTPStatus
from time import sleep
import pytest import pytest
from starlette.testclient import TestClient from starlette.testclient import TestClient
@ -11,17 +12,20 @@ client = TestClient(app)
test_websites = [ test_websites = [
"https://ext.to/", "https://ext.to/",
"https://btmet.com/", "https://btmet.com/",
# "https://extratorrent.st/", # github is blocking these "https://extratorrent.st/", # github is blocking these
# "https://idope.se/", # github is blocking these "https://idope.se/", # github is blocking these
"https://www.ygg.re/",
] ]
@pytest.mark.parametrize("website", test_websites) @pytest.mark.parametrize("website", test_websites)
def test_bypass(website: str): def test_bypass(website: str):
sleep(3)
response = client.post( response = client.post(
"/v1", "/v1",
json=LinkRequest( json=LinkRequest(url=website, maxTimeout=30, cmd="request.get").model_dump(),
url=website, maxTimeout=60 * len(test_websites), cmd="request.get"
).model_dump(),
) )
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
# if rate limited
assert True
assert response.status_code == HTTPStatus.OK assert response.status_code == HTTPStatus.OK