feat: Add alternative route for fonts
All checks were successful
Check Code Quality / RuffCheck (push) Successful in 2m28s
Build Docker / BuildImage (push) Successful in 2m31s

This commit is contained in:
2025-11-15 14:38:12 +11:00
parent e1ff6e42a5
commit 06526ca92d

110
server.py
View File

@@ -18,9 +18,20 @@ import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_H from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_H
from ansi2html import Ansi2HTMLConverter from ansi2html import Ansi2HTMLConverter
from PIL import Image from PIL import Image
# Import blueprints # Import blueprints
from blueprints import now, blog, wellknown, api, podcast, acme, spotify from blueprints import now, blog, wellknown, api, podcast, acme, spotify
from tools import isCLI, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getHandshakeScript, get_tools_data from tools import (
isCLI,
isCrawler,
getAddress,
getFilePath,
error_response,
getClientIP,
json_response,
getHandshakeScript,
get_tools_data,
)
from curl import curl_response from curl import curl_response
app = Flask(__name__) app = Flask(__name__)
@@ -50,18 +61,14 @@ REDIRECT_ROUTES = {
"/meeting": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", "/meeting": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr",
"/appointment": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", "/appointment": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr",
} }
DOWNLOAD_ROUTES = { DOWNLOAD_ROUTES = {"pgp": "data/nathanwoodburn.asc"}
"pgp": "data/nathanwoodburn.asc"
}
SITES = [] SITES = []
if os.path.isfile("data/sites.json"): if os.path.isfile("data/sites.json"):
with open("data/sites.json") as file: with open("data/sites.json") as file:
SITES = json.load(file) SITES = json.load(file)
# Remove any sites that are not enabled # Remove any sites that are not enabled
SITES = [ SITES = [site for site in SITES if "enabled" not in site or site["enabled"]]
site for site in SITES if "enabled" not in site or site["enabled"]
]
PROJECTS = [] PROJECTS = []
PROJECTS_UPDATED = 0 PROJECTS_UPDATED = 0
@@ -114,6 +121,13 @@ def asset(path):
return error_response(request) return error_response(request)
@app.route("/fonts/<path:path>")
def fonts(path):
if os.path.isfile("templates/assets/fonts/" + path):
return send_from_directory("templates/assets/fonts", path)
return error_response(request)
@app.route("/sitemap") @app.route("/sitemap")
@app.route("/sitemap.xml") @app.route("/sitemap.xml")
def sitemap(): def sitemap():
@@ -153,6 +167,7 @@ def download(path):
return error_response(request, message="File not found") return error_response(request, message="File not found")
# endregion # endregion
# region PWA routes # region PWA routes
@@ -177,6 +192,7 @@ def manifest():
def serviceWorker(): def serviceWorker():
return send_from_directory("pwa", "sw.js") return send_from_directory("pwa", "sw.js")
# endregion # endregion
@@ -185,12 +201,14 @@ def serviceWorker():
def links(): def links():
return render_template("link.html") return render_template("link.html")
@app.route("/actions.json") @app.route("/actions.json")
def sol_actions(): def sol_actions():
return jsonify( return jsonify(
{"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]} {"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]}
) )
@app.route("/api/<path:function>") @app.route("/api/<path:function>")
def api_legacy(function): def api_legacy(function):
# Check if function is in api blueprint # Check if function is in api blueprint
@@ -200,6 +218,7 @@ def api_legacy(function):
return redirect(f"/api/v1/{function}", code=301) return redirect(f"/api/v1/{function}", code=301)
return error_response(request, message="404 Not Found", code=404) return error_response(request, message="404 Not Found", code=404)
# endregion # endregion
# region Main routes # region Main routes
@@ -263,9 +282,11 @@ def index():
print(f"Error getting git data: {e}") print(f"Error getting git data: {e}")
# Get only repo names for the newest updates # Get only repo names for the newest updates
if PROJECTS == [] or PROJECTS_UPDATED < (datetime.datetime.now() - datetime.timedelta( if (
hours=2 PROJECTS == []
)).timestamp(): or PROJECTS_UPDATED
< (datetime.datetime.now() - datetime.timedelta(hours=2)).timestamp()
):
projectsreq = requests.get( projectsreq = requests.get(
"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos" "https://git.woodburn.au/api/v1/users/nathanwoodburn/repos"
) )
@@ -288,11 +309,9 @@ def index():
or project["avatar_url"] == "" or project["avatar_url"] == ""
): ):
project["avatar_url"] = "/favicon.png" project["avatar_url"] = "/favicon.png"
project["name"] = project["name"].replace( project["name"] = project["name"].replace("_", " ").replace("-", " ")
"_", " ").replace("-", " ")
# Sort by last updated # Sort by last updated
projectsList = sorted( projectsList = sorted(PROJECTS, key=lambda x: x["updated_at"], reverse=True)
PROJECTS, key=lambda x: x["updated_at"], reverse=True)
PROJECTS = [] PROJECTS = []
projectNames = [] projectNames = []
projectNum = 0 projectNum = 0
@@ -305,8 +324,7 @@ def index():
custom = "" custom = ""
# Check for downtime # Check for downtime
uptime = requests.get( uptime = requests.get("https://uptime.woodburn.au/api/status-page/main/badge")
"https://uptime.woodburn.au/api/status-page/main/badge")
uptime = uptime.content.count(b"Up") > 1 uptime = uptime.content.count(b"Up") > 1
if uptime: if uptime:
@@ -375,6 +393,7 @@ def index():
return resp return resp
# region Donate # region Donate
@@ -451,7 +470,7 @@ def donate():
if not token: if not token:
cryptoHTML += f"<br>Donate with {coinNames[crypto] if crypto in coinNames else crypto}:" cryptoHTML += f"<br>Donate with {coinNames[crypto] if crypto in coinNames else crypto}:"
else: else:
cryptoHTML += f'<br>Donate with {token["name"]} {"("+token["symbol"]+") " if token["symbol"] != token["name"] else ""}on {crypto}:' cryptoHTML += f"<br>Donate with {token['name']} {'(' + token['symbol'] + ') ' if token['symbol'] != token['name'] else ''}on {crypto}:"
cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>' cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>'
if proof: if proof:
@@ -459,12 +478,12 @@ def donate():
elif token: elif token:
if "address" in token: if "address" in token:
address = token["address"] address = token["address"]
cryptoHTML += f'<br>Donate with {token["name"]} {"("+token["symbol"]+")" if token["symbol"] != token["name"] else ""}{" on "+crypto if crypto != "NULL" else ""}:' cryptoHTML += f"<br>Donate with {token['name']} {'(' + token['symbol'] + ')' if token['symbol'] != token['name'] else ''}{' on ' + crypto if crypto != 'NULL' else ''}:"
cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>' cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>'
if proof: if proof:
cryptoHTML += proof cryptoHTML += proof
else: else:
cryptoHTML += f'<br>Invalid offchain token: {token["symbol"]}<br>' cryptoHTML += f"<br>Invalid offchain token: {token['symbol']}<br>"
else: else:
cryptoHTML += f"<br>Invalid chain: {crypto}<br>" cryptoHTML += f"<br>Invalid chain: {crypto}<br>"
@@ -520,27 +539,30 @@ def qraddress(address):
@app.route("/qrcode/<path:data>") @app.route("/qrcode/<path:data>")
@app.route("/qr/<path:data>") @app.route("/qr/<path:data>")
def qrcodee(data): def qrcodee(data):
qr = qrcode.QRCode( qr = qrcode.QRCode(error_correction=ERROR_CORRECT_H, box_size=10, border=2)
error_correction=ERROR_CORRECT_H, box_size=10, border=2)
qr.add_data(data) qr.add_data(data)
qr.make() qr.make()
qr_image: Image.Image = qr.make_image( qr_image: Image.Image = qr.make_image(
fill_color="black", back_color="white").convert('RGB') # type: ignore fill_color="black", back_color="white"
).convert("RGB") # type: ignore
# Add logo # Add logo
logo = Image.open("templates/assets/img/favicon/logo.png") logo = Image.open("templates/assets/img/favicon/logo.png")
basewidth = qr_image.size[0]//3 basewidth = qr_image.size[0] // 3
wpercent = (basewidth / float(logo.size[0])) wpercent = basewidth / float(logo.size[0])
hsize = int((float(logo.size[1]) * float(wpercent))) hsize = int((float(logo.size[1]) * float(wpercent)))
logo = logo.resize((basewidth, hsize), Image.Resampling.LANCZOS) logo = logo.resize((basewidth, hsize), Image.Resampling.LANCZOS)
pos = ((qr_image.size[0] - logo.size[0]) // 2, pos = (
(qr_image.size[1] - logo.size[1]) // 2) (qr_image.size[0] - logo.size[0]) // 2,
(qr_image.size[1] - logo.size[1]) // 2,
)
qr_image.paste(logo, pos, mask=logo) qr_image.paste(logo, pos, mask=logo)
qr_image.save("/tmp/qr_code.png") qr_image.save("/tmp/qr_code.png")
return send_file("/tmp/qr_code.png", mimetype="image/png") return send_file("/tmp/qr_code.png", mimetype="image/png")
# endregion # endregion
@@ -580,15 +602,18 @@ def hosting_post():
# Check email rate limit # Check email rate limit
if email in EMAIL_REQUEST_COUNT: if email in EMAIL_REQUEST_COUNT:
if (current_time - EMAIL_REQUEST_COUNT[email]["last_reset"]) > RATE_LIMIT_WINDOW: if (
current_time - EMAIL_REQUEST_COUNT[email]["last_reset"]
) > RATE_LIMIT_WINDOW:
# Reset counter if the time window has passed # Reset counter if the time window has passed
EMAIL_REQUEST_COUNT[email] = { EMAIL_REQUEST_COUNT[email] = {"count": 1, "last_reset": current_time}
"count": 1, "last_reset": current_time}
else: else:
# Increment counter # Increment counter
EMAIL_REQUEST_COUNT[email]["count"] += 1 EMAIL_REQUEST_COUNT[email]["count"] += 1
if EMAIL_REQUEST_COUNT[email]["count"] > EMAIL_RATE_LIMIT: if EMAIL_REQUEST_COUNT[email]["count"] > EMAIL_RATE_LIMIT:
return json_response(request, "Rate limit exceeded. Please try again later.", 429) return json_response(
request, "Rate limit exceeded. Please try again later.", 429
)
else: else:
# First request for this email # First request for this email
EMAIL_REQUEST_COUNT[email] = {"count": 1, "last_reset": current_time} EMAIL_REQUEST_COUNT[email] = {"count": 1, "last_reset": current_time}
@@ -602,7 +627,9 @@ def hosting_post():
# Increment counter # Increment counter
IP_REQUEST_COUNT[ip]["count"] += 1 IP_REQUEST_COUNT[ip]["count"] += 1
if IP_REQUEST_COUNT[ip]["count"] > IP_RATE_LIMIT: if IP_REQUEST_COUNT[ip]["count"] > IP_RATE_LIMIT:
return json_response(request, "Rate limit exceeded. Please try again later.", 429) return json_response(
request, "Rate limit exceeded. Please try again later.", 429
)
else: else:
# First request for this IP # First request for this IP
IP_REQUEST_COUNT[ip] = {"count": 1, "last_reset": current_time} IP_REQUEST_COUNT[ip] = {"count": 1, "last_reset": current_time}
@@ -661,12 +688,13 @@ def hosting_post():
return json_response(request, "Failed to send enquiry", 500) return json_response(request, "Failed to send enquiry", 500)
return json_response(request, "Enquiry sent", 200) return json_response(request, "Enquiry sent", 200)
@app.route("/resume") @app.route("/resume")
def resume(): def resume():
# Check if arg for support is passed # Check if arg for support is passed
support = request.args.get("support") support = request.args.get("support")
return render_template( return render_template("resume.html", support=support)
"resume.html", support=support)
@app.route("/resume.pdf") @app.route("/resume.pdf")
def resume_pdf(): def resume_pdf():
@@ -683,13 +711,14 @@ def tools():
return curl_response(request) return curl_response(request)
return render_template("tools.html", tools=get_tools_data()) return render_template("tools.html", tools=get_tools_data())
# endregion # endregion
# region Error Catching # region Error Catching
# Catch all for GET requests # Catch all for GET requests
@app.route("/<path:path>") @app.route("/<path:path>")
def catch_all(path: str): def catch_all(path: str):
if path.lower().replace(".html", "") in RESTRICTED_ROUTES: if path.lower().replace(".html", "") in RESTRICTED_ROUTES:
return error_response(request, message="Restricted route", code=403) return error_response(request, message="Restricted route", code=403)
@@ -702,17 +731,23 @@ def catch_all(path: str):
# If file exists, load it # If file exists, load it
if os.path.isfile("templates/" + path): if os.path.isfile("templates/" + path):
return render_template(path, handshake_scripts=getHandshakeScript(request.host), sites=SITES) return render_template(
path, handshake_scripts=getHandshakeScript(request.host), sites=SITES
)
# Try with .html # Try with .html
if os.path.isfile("templates/" + path + ".html"): if os.path.isfile("templates/" + path + ".html"):
return render_template( return render_template(
path + ".html", handshake_scripts=getHandshakeScript(request.host), sites=SITES path + ".html",
handshake_scripts=getHandshakeScript(request.host),
sites=SITES,
) )
if os.path.isfile("templates/" + path.strip("/") + ".html"): if os.path.isfile("templates/" + path.strip("/") + ".html"):
return render_template( return render_template(
path.strip("/") + ".html", handshake_scripts=getHandshakeScript(request.host), sites=SITES path.strip("/") + ".html",
handshake_scripts=getHandshakeScript(request.host),
sites=SITES,
) )
# Try to find a file matching # Try to find a file matching
@@ -729,6 +764,7 @@ def catch_all(path: str):
def not_found(e): def not_found(e):
return error_response(request) return error_response(request)
# endregion # endregion