From 71bfa80778e3020810d4fb5f94b6caabef6f0be1 Mon Sep 17 00:00:00 2001 From: dablenparty Date: Wed, 1 Oct 2025 11:34:07 -0400 Subject: [PATCH 1/4] Format files --- app/index.py | 25 ++++++++++++++----------- app/portable.py | 25 ++++++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/app/index.py b/app/index.py index 6086b2e..27e6b0e 100644 --- a/app/index.py +++ b/app/index.py @@ -1,36 +1,39 @@ +from urllib.parse import urljoin, urlparse + import flask import requests -from flask import request from bs4 import BeautifulSoup -from urllib.parse import urlparse, urljoin +from flask import request app = flask.Flask(__name__) googlebot_headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.119 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" } + def add_base_tag(html_content, original_url): - soup = BeautifulSoup(html_content, 'html.parser') + soup = BeautifulSoup(html_content, "html.parser") parsed_url = urlparse(original_url) base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/" - + # Handle paths that are not root, e.g., "https://x.com/some/path/w.html" - if parsed_url.path and not parsed_url.path.endswith('/'): - base_url = urljoin(base_url, parsed_url.path.rsplit('/', 1)[0] + '/') - base_tag = soup.find('base') - + if parsed_url.path and not parsed_url.path.endswith("/"): + base_url = urljoin(base_url, parsed_url.path.rsplit("/", 1)[0] + "/") + base_tag = soup.find("base") + print(base_url) if not base_tag: - new_base_tag = soup.new_tag('base', href=base_url) + new_base_tag = soup.new_tag("base", href=base_url) if soup.head: soup.head.insert(0, new_base_tag) else: - head_tag = soup.new_tag('head') + head_tag = soup.new_tag("head") head_tag.insert(0, new_base_tag) soup.insert(0, head_tag) - + return str(soup) + def bypass_paywall(url): """ Bypass paywall for a given url diff --git a/app/portable.py b/app/portable.py index 7449035..f970851 100644 --- a/app/portable.py +++ b/app/portable.py @@ -1,8 +1,9 @@ +from urllib.parse import urljoin, urlparse + import flask import requests -from flask import request from bs4 import BeautifulSoup -from urllib.parse import urlparse, urljoin +from flask import request app = flask.Flask(__name__) googlebot_headers = { @@ -187,28 +188,30 @@ html = """ """ + def add_base_tag(html_content, original_url): - soup = BeautifulSoup(html_content, 'html.parser') + soup = BeautifulSoup(html_content, "html.parser") parsed_url = urlparse(original_url) base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/" - + # Handle paths that are not root, e.g., "https://x.com/some/path/w.html" - if parsed_url.path and not parsed_url.path.endswith('/'): - base_url = urljoin(base_url, parsed_url.path.rsplit('/', 1)[0] + '/') - base_tag = soup.find('base') - + if parsed_url.path and not parsed_url.path.endswith("/"): + base_url = urljoin(base_url, parsed_url.path.rsplit("/", 1)[0] + "/") + base_tag = soup.find("base") + print(base_url) if not base_tag: - new_base_tag = soup.new_tag('base', href=base_url) + new_base_tag = soup.new_tag("base", href=base_url) if soup.head: soup.head.insert(0, new_base_tag) else: - head_tag = soup.new_tag('head') + head_tag = soup.new_tag("head") head_tag.insert(0, new_base_tag) soup.insert(0, head_tag) - + return str(soup) + def bypass_paywall(url): """ Bypass paywall for a given url From 53cf57ec6045e5c4847bd9205b471978a7d4fc53 Mon Sep 17 00:00:00 2001 From: dablenparty Date: Wed, 1 Oct 2025 11:58:25 -0400 Subject: [PATCH 2/4] Add gunicorn production server --- Dockerfile | 4 ++-- README.md | 20 ++++++++++++-------- app/gunicorn.conf.py | 4 ++++ app/portable.py | 3 ++- app/requirements.txt | 5 +++-- requirements.txt | 5 +++-- 6 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 app/gunicorn.conf.py diff --git a/Dockerfile b/Dockerfile index 8dd99e3..5bb5c22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,5 +22,5 @@ COPY . . RUN pip install -r requirements.txt WORKDIR /app EXPOSE 5000 -ENTRYPOINT [ "python" ] -CMD [ "portable.py" ] \ No newline at end of file +ENTRYPOINT [ "gunicorn" ] +CMD [ "portable:app" ] diff --git a/README.md b/README.md index b60af76..23bcea5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ It pretends to be GoogleBot (Google's web crawler) and gets the same content tha ### Using Docker Requirements: + - docker - Docker Compose (available as `docker compose`) @@ -44,7 +45,7 @@ If that doesn't work retry but replace `python` with `py`, then try `python3`, t Then run `portable.py`, click [this link](https://realpython.com/run-python-scripts/) for a tutorial on how to run python scripts. ```sh -python portable.py +python -m gunicorn 'portable:app' ``` Then open the link shown in the terminal in the browser and you'll be able to use this @@ -58,26 +59,29 @@ python -m pip install -r requirements.txt FLASK_APP=app/portable.py flask run --host=127.0.0.1 --port=9982 ``` - ## Using as a Bookmarklet in Chrome: You can create a bookmarklet that performs the URL transformation by writing a small JavaScript snippet. Below is the JavaScript code for your bookmarklet: + ```javascript -javascript:(function(){window.location.href='https://13ft.wasimaster.me/'+encodeURIComponent(window.location.href);})(); +javascript: (function () { + window.location.href = + "https://13ft.wasimaster.me/" + encodeURIComponent(window.location.href); +})(); ``` + You can replace https://13ft.wasimaster.me with your own 13ft instance if desired. Steps: + 1. Open Bookmarks Manager: 2. Click on the three dots (menu) in the top-right corner of Chrome. -Go to Bookmarks > Bookmark manager, or simply press Ctrl+Shift+O on Windows/Linux or Cmd+Option+B on Mac. -Create a New Bookmark: + Go to Bookmarks > Bookmark manager, or simply press Ctrl+Shift+O on Windows/Linux or Cmd+Option+B on Mac. + Create a New Bookmark: 3. In the Bookmark Manager, click the three-dot menu in the top-right corner of the window and select Add new bookmark. -Enter Bookmark Details: - - Name: Enter a name for your bookmarklet, such as "13ft-ize". This name will show as a bookmark title in the bookmarks bar - - URL: Paste the JavaScript code provided above into the URL field. + Enter Bookmark Details: - Name: Enter a name for your bookmarklet, such as "13ft-ize". This name will show as a bookmark title in the bookmarks bar - URL: Paste the JavaScript code provided above into the URL field. 4. Click Save. Using the Bookmarklet: diff --git a/app/gunicorn.conf.py b/app/gunicorn.conf.py new file mode 100644 index 0000000..3cd32cd --- /dev/null +++ b/app/gunicorn.conf.py @@ -0,0 +1,4 @@ +from os import environ + +# https://docs.gunicorn.org/en/stable/settings.html#settings +bind = f"0.0.0.0:{environ.get('PORT', 5000)}" diff --git a/app/portable.py b/app/portable.py index f970851..acfa0f5 100644 --- a/app/portable.py +++ b/app/portable.py @@ -260,4 +260,5 @@ def get_article(path): return "Invalid URL", 400 -app.run(host="0.0.0.0", port=5000, debug=False) +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=False) diff --git a/app/requirements.txt b/app/requirements.txt index b2d4e9d..d3adff4 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -1,3 +1,4 @@ -flask -requests bs4 +flask +gunicorn +requests diff --git a/requirements.txt b/requirements.txt index b2d4e9d..d3adff4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -flask -requests bs4 +flask +gunicorn +requests From 1ce4a31908e54388616a06854a67d65fa4236be2 Mon Sep 17 00:00:00 2001 From: dablenparty Date: Wed, 1 Oct 2025 12:00:41 -0400 Subject: [PATCH 3/4] Add docker-compose.dev.yaml Use this to test out the Dockerfile: docker compose -f docker-compose.dev.yaml up --build --- docker-compose.dev.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docker-compose.dev.yaml diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000..1c13b5c --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,9 @@ +services: + 13ft: + container_name: 13ft + hostname: 13ft + build: + context: . + restart: unless-stopped + ports: + - "5000:5000" From 95d512de7eb375a5d3a6182012ac072fd2d4e20e Mon Sep 17 00:00:00 2001 From: dablenparty Date: Wed, 1 Oct 2025 14:15:50 -0400 Subject: [PATCH 4/4] Refactor Dockerfile to not run as root https://flask.palletsprojects.com/en/stable/deploying/gunicorn/#binding-externally --- Dockerfile | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5bb5c22..7471eb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,12 @@ +# syntax=docker/dockerfile:1 FROM python:3.13.7-alpine +ARG UID=1000 +ARG GID=1000 + +RUN addgroup -g $GID thirteener +RUN adduser -D -g '' -u $UID -G thirteener thirteener + # Generic labels LABEL maintainer="Arian Mollik Wasi " LABEL version="0.3.4" @@ -18,9 +25,13 @@ LABEL org.opencontainers.image.description="My own custom 12ft.io replacement" LABEL org.opencontainers.image.documentation="https://github.com/wasi-master/13ft/blob/main/README.md" LABEL org.opencontainers.image.licenses=MIT +RUN python -m pip install --upgrade pip + +USER thirteener COPY . . -RUN pip install -r requirements.txt +RUN --mount=type=cache,mode=0755,target=/home/thirteener/.cache/pip \ + pip install -U -r requirements.txt + WORKDIR /app EXPOSE 5000 -ENTRYPOINT [ "gunicorn" ] -CMD [ "portable:app" ] +CMD [ "python", "-m", "gunicorn", "portable:app" ]