feat: Split code into modules
All checks were successful
Build Docker / BuildImage (push) Successful in 4m7s

This commit is contained in:
2025-10-10 22:58:06 +11:00
parent eee87e6ca7
commit fc56cafab8
7 changed files with 529 additions and 516 deletions

255
blueprints/api.py Normal file
View File

@@ -0,0 +1,255 @@
from flask import Blueprint, request, jsonify, make_response
import os
import datetime
import requests
from mail import sendEmail
from sol import create_transaction, get_solana_address
import json
api_bp = Blueprint('api', __name__)
ncReq = requests.get(
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json"
)
ncConfig = ncReq.json()
if 'time-zone' not in ncConfig:
ncConfig['time-zone'] = 10
def getClientIP(request):
x_forwarded_for = request.headers.get("X-Forwarded-For")
if x_forwarded_for:
ip = x_forwarded_for.split(",")[0]
else:
ip = request.remote_addr
return ip
def getGitCommit():
# if .git exists, get the latest commit hash
if os.path.isdir(".git"):
git_dir = ".git"
head_ref = ""
with open(os.path.join(git_dir, "HEAD")) as file:
head_ref = file.read().strip()
if head_ref.startswith("ref: "):
head_ref = head_ref[5:]
with open(os.path.join(git_dir, head_ref)) as file:
return file.read().strip()
else:
return head_ref
# Check if env SOURCE_COMMIT is set
if "SOURCE_COMMIT" in os.environ:
return os.environ["SOURCE_COMMIT"]
return "failed to get version"
@api_bp.route("/")
@api_bp.route("/help")
def api_help_get():
return jsonify({
"message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au",
"endpoints": {
"/time": "Get the current time",
"/timezone": "Get the current timezone",
"/message": "Get the message from the config",
"/ip": "Get your IP address",
"/project": "Get the current project from git",
"/version": "Get the current version of the website",
"/help": "Get this help message"
},
"version": getGitCommit()
})
@api_bp.route("/version")
def api_version_get():
return jsonify({"version": getGitCommit()})
@api_bp.route("/time")
def api_time_get():
timezone_offset = datetime.timedelta(hours=ncConfig["time-zone"])
timezone = datetime.timezone(offset=timezone_offset)
time = datetime.datetime.now(tz=timezone)
return jsonify({
"timestring": time.strftime("%A, %B %d, %Y %I:%M %p"),
"timestamp": time.timestamp(),
"timezone": ncConfig["time-zone"],
"timeISO": time.isoformat()
})
@api_bp.route("/timezone")
def api_timezone_get():
return jsonify({"timezone": ncConfig["time-zone"]})
@api_bp.route("/timezone", methods=["POST"])
def api_timezone_post():
# Refresh config
global ncConfig
conf = requests.get(
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json")
if conf.status_code != 200:
return jsonify({"message": "Error: Could not get timezone"})
if not conf.json():
return jsonify({"message": "Error: Could not get timezone"})
conf = conf.json()
if "time-zone" not in conf:
return jsonify({"message": "Error: Could not get timezone"})
ncConfig = conf
return jsonify({"message": "Successfully pulled latest timezone", "timezone": ncConfig["time-zone"]})
@api_bp.route("/message")
def api_message_get():
return jsonify({"message": ncConfig["message"]})
@api_bp.route("/ip")
def api_ip_get():
return jsonify({"ip": getClientIP(request)})
@api_bp.route("/email", methods=["POST"])
def api_email_post():
# Verify json
if not request.is_json:
return jsonify({
"status": 400,
"error": "Bad request JSON Data missing"
})
# Check if api key sent
data = request.json
if not data:
return jsonify({
"status": 400,
"error": "Bad request JSON Data missing"
})
if "key" not in data:
return jsonify({
"status": 401,
"error": "Unauthorized 'key' missing"
})
if data["key"] != os.getenv("EMAIL_KEY"):
return jsonify({
"status": 401,
"error": "Unauthorized 'key' invalid"
})
return sendEmail(data)
@api_bp.route("/project")
def api_project_get():
try:
git = requests.get(
"https://git.woodburn.au/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1",
headers={"Authorization": os.getenv("git_token")},
)
git = git.json()
git = git[0]
repo_name = git["repo"]["name"]
repo_name = repo_name.lower()
repo_description = git["repo"]["description"]
except Exception as e:
repo_name = "nathanwoodburn.github.io"
repo_description = "Personal website"
git = {
"repo": {
"html_url": "https://nathan.woodburn.au",
"name": "nathanwoodburn.github.io",
"description": "Personal website",
}
}
print(f"Error getting git data: {e}")
return jsonify({
"repo_name": repo_name,
"repo_description": repo_description,
"git": git,
})
# region Solana Links
SOLANA_HEADERS = {
"Content-Type": "application/json",
"X-Action-Version": "2.4.2",
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
@api_bp.route("/donate", methods=["GET", "OPTIONS"])
def sol_donate_get():
data = {
"icon": "https://nathan.woodburn.au/assets/img/profile.png",
"label": "Donate to Nathan.Woodburn/",
"title": "Donate to Nathan.Woodburn/",
"description": "Student, developer, and crypto enthusiast",
"links": {
"actions": [
{"label": "0.01 SOL", "href": "/api/v1/donate/0.01"},
{"label": "0.1 SOL", "href": "/api/v1/donate/0.1"},
{"label": "1 SOL", "href": "/api/v1/donate/1"},
{
"href": "/api/v1/donate/{amount}",
"label": "Donate",
"parameters": [
{"name": "amount", "label": "Enter a custom SOL amount"}
],
},
]
},
}
response = make_response(jsonify(data), 200, SOLANA_HEADERS)
if request.method == "OPTIONS":
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = (
"Content-Type,Authorization,Content-Encoding,Accept-Encoding,X-Action-Version,X-Blockchain-Ids"
)
return response
@api_bp.route("/donate/<amount>")
def sol_donate_amount_get(amount):
data = {
"icon": "https://nathan.woodburn.au/assets/img/profile.png",
"label": f"Donate {amount} SOL to Nathan.Woodburn/",
"title": "Donate to Nathan.Woodburn/",
"description": f"Donate {amount} SOL to Nathan.Woodburn/",
}
return jsonify(data), 200, SOLANA_HEADERS
@api_bp.route("/donate/<amount>", methods=["POST"])
def sol_donate_post(amount):
if not request.json:
return jsonify({"message": "Error: No JSON data provided"}), 400, SOLANA_HEADERS
if "account" not in request.json:
return jsonify({"message": "Error: No account provided"}), 400, SOLANA_HEADERS
sender = request.json["account"]
# Make sure amount is a number
try:
amount = float(amount)
except ValueError:
amount = 1 # Default to 1 SOL if invalid
if amount < 0.0001:
return jsonify({"message": "Error: Amount too small"}), 400, SOLANA_HEADERS
transaction = create_transaction(sender, amount)
return jsonify({"message": "Success", "transaction": transaction}), 200, SOLANA_HEADERS
# endregion

View File

@@ -1,19 +1,22 @@
import os import os
from flask import render_template from flask import Blueprint, render_template, request
import markdown import markdown
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import re import re
blog_bp = Blueprint('blog', __name__)
def list_blog_page_files(): def list_blog_page_files():
blog_pages = os.listdir("data/blog") blog_pages = os.listdir("data/blog")
# Remove .md extension # Remove .md extension
blog_pages = [page.removesuffix(".md") for page in blog_pages if page.endswith(".md")] blog_pages = [page.removesuffix(".md")
for page in blog_pages if page.endswith(".md")]
return blog_pages return blog_pages
def render_blog_page(date,handshake_scripts=None): def render_blog_page(date, handshake_scripts=None):
# Convert md to html # Convert md to html
if not os.path.exists(f"data/blog/{date}.md"): if not os.path.exists(f"data/blog/{date}.md"):
return render_template("404.html"), 404 return render_template("404.html"), 404
@@ -23,12 +26,12 @@ def render_blog_page(date,handshake_scripts=None):
# Get the title from the file name # Get the title from the file name
title = date.removesuffix(".md").replace("_", " ") title = date.removesuffix(".md").replace("_", " ")
# Convert the md to html # Convert the md to html
content = markdown.markdown(content, extensions=['sane_lists', 'codehilite', 'fenced_code']) content = markdown.markdown(
content, extensions=['sane_lists', 'codehilite', 'fenced_code'])
# Add target="_blank" to all links # Add target="_blank" to all links
content = content.replace('<a href="', '<a target="_blank" href="') content = content.replace('<a href="', '<a target="_blank" href="')
content = content.replace("<h4", "<h4 style='margin-bottom:0px;'") content = content.replace("<h4", "<h4 style='margin-bottom:0px;'")
content = fix_numbered_lists(content) content = fix_numbered_lists(content)
return render_template( return render_template(
@@ -62,7 +65,8 @@ def fix_numbered_lists(html):
for i in range(0, len(steps), 2): for i in range(0, len(steps), 2):
if i+1 < len(steps): if i+1 < len(steps):
step_html = steps[i+1].strip() step_html = steps[i+1].strip()
ol_items.append(f"<li style='list-style: auto;'>{step_html}</li>") ol_items.append(
f"<li style='list-style: auto;'>{step_html}</li>")
# Build the final list HTML # Build the final list HTML
ol_html = "<ol>\n" + "\n".join(ol_items) + "\n</ol>" ol_html = "<ol>\n" + "\n".join(ol_items) + "\n</ol>"
@@ -85,7 +89,7 @@ def render_blog_home(handshake_scripts=None):
blog_pages = [ blog_pages = [
f"""<li class="list-group-item"> f"""<li class="list-group-item">
<p style="margin-bottom: 0px;"><a href='/blog/{page}'>{page.replace("_"," ")}</a></p> <p style="margin-bottom: 0px;"><a href='/blog/{page}'>{page.replace("_", " ")}</a></p>
</li>""" </li>"""
for page in blog_pages for page in blog_pages
] ]
@@ -97,3 +101,34 @@ def render_blog_home(handshake_scripts=None):
blogs=blog_pages, blogs=blog_pages,
handshake_scripts=handshake_scripts, handshake_scripts=handshake_scripts,
) )
@blog_bp.route("/")
def blog_index_get():
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
return render_blog_home(handshake_scripts)
@blog_bp.route("/<path:path>")
def blog_path_get(path):
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
return render_blog_page(path, handshake_scripts)

140
blueprints/now.py Normal file
View File

@@ -0,0 +1,140 @@
from flask import Blueprint, render_template, make_response, request, jsonify
import datetime
import os
# Create blueprint
now_bp = Blueprint('now', __name__)
def list_now_page_files():
now_pages = os.listdir("templates/now")
now_pages = [
page for page in now_pages if page != "template.html" and page != "old.html"
]
now_pages.sort(reverse=True)
return now_pages
def list_now_dates():
now_pages = list_now_page_files()
now_dates = [page.split(".")[0] for page in now_pages]
return now_dates
def get_latest_now_date(formatted=False):
if formatted:
date=list_now_dates()[0]
date = datetime.datetime.strptime(date, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y")
return date
return list_now_dates()[0]
def render_latest_now(handshake_scripts=None):
now_page = list_now_dates()[0]
return render_now_page(now_page,handshake_scripts=handshake_scripts)
def render_now_page(date,handshake_scripts=None):
# If the date is not available, render the latest page
if date is None:
return render_latest_now(handshake_scripts=handshake_scripts)
# Remove .html
date = date.removesuffix(".html")
if date not in list_now_dates():
return render_template("404.html"), 404
date_formatted = datetime.datetime.strptime(date, "%y_%m_%d")
date_formatted = date_formatted.strftime("%A, %B %d, %Y")
return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts)
@now_bp.route("/")
def now_index_get():
handshake_scripts = ''
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
else:
handshake_scripts = '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>'
return render_latest_now(handshake_scripts)
@now_bp.route("/<path:path>")
def now_path_get(path):
handshake_scripts = ''
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
else:
handshake_scripts = '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>'
return render_now_page(path, handshake_scripts)
@now_bp.route("/old")
@now_bp.route("/old/")
def now_old_get():
handshake_scripts = ''
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
else:
handshake_scripts = '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>'
now_dates = list_now_dates()[1:]
html = '<ul class="list-group">'
html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_now_date(True)}</li></a>'
for date in now_dates:
link = date
date = datetime.datetime.strptime(date, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y")
html += f'<a style="text-decoration:none;" href="/now/{link}"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{date}</li></a>'
html += "</ul>"
return render_template(
"now/old.html", handshake_scripts=handshake_scripts, now_pages=html
)
@now_bp.route("/now.rss")
@now_bp.route("/now.xml")
@now_bp.route("/rss.xml")
def now_rss_get():
host = "https://" + request.host
if ":" in request.host:
host = "http://" + request.host
# Generate RSS feed
now_pages = list_now_page_files()
rss = f'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nathan.Woodburn/</title><link>{host}</link><description>See what I\'ve been up to</description><language>en-us</language><lastBuildDate>{datetime.datetime.now(tz=datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")}</lastBuildDate><atom:link href="{host}/now.rss" rel="self" type="application/rss+xml" />'
for page in now_pages:
link = page.strip(".html")
date = datetime.datetime.strptime(link, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y")
rss += f'<item><title>What\'s Happening {date}</title><link>{host}/now/{link}</link><description>Latest updates for {date}</description><guid>{host}/now/{link}</guid></item>'
rss += "</channel></rss>"
return make_response(rss, 200, {"Content-Type": "application/rss+xml"})
@now_bp.route("/now.json")
def now_json_get():
now_pages = list_now_page_files()
host = "https://" + request.host
if ":" in request.host:
host = "http://" + request.host
now_pages = [{"url": host+"/now/"+page.strip(".html"), "date": datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime(
"%A, %B %d, %Y"), "title": "What's Happening "+datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime("%A, %B %d, %Y")} for page in now_pages]
return jsonify(now_pages)

62
blueprints/wellknown.py Normal file
View File

@@ -0,0 +1,62 @@
from flask import Blueprint, render_template, make_response, request, jsonify, send_from_directory, redirect
import os
wk_bp = Blueprint('well-known', __name__)
@wk_bp.route("/<path:path>")
def wk_index_get(path):
return send_from_directory(".well-known", path)
@wk_bp.route("/wallets/<path:path>")
def wk_wallet_get(path):
if path[0] == "." and 'proof' not in path:
return send_from_directory(
".well-known/wallets", path, mimetype="application/json"
)
elif os.path.isfile(".well-known/wallets/" + path):
address = ""
with open(".well-known/wallets/" + path) as file:
address = file.read()
address = address.strip()
return make_response(address, 200, {"Content-Type": "text/plain"})
if os.path.isfile(".well-known/wallets/" + path.upper()):
return redirect("/.well-known/wallets/" + path.upper(), code=302)
return render_template("404.html"), 404
@wk_bp.route("/nostr.json")
def wk_nostr_get():
# Get name parameter
name = request.args.get("name")
if name:
return jsonify(
{
"names": {
name: "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82"
}
}
)
return jsonify(
{
"names": {
"nathan": "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82",
"_": "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82",
}
}
)
@wk_bp.route("/xrp-ledger.toml")
def wk_xrp_get():
# Create a response with the xrp-ledger.toml file
with open(".well-known/xrp-ledger.toml") as file:
toml = file.read()
response = make_response(toml, 200, {"Content-Type": "application/toml"})
# Set cors headers
response.headers["Access-Control-Allow-Origin"] = "*"
return response

43
now.py
View File

@@ -1,43 +0,0 @@
import os
from flask import render_template
from datetime import datetime
def list_now_page_files():
now_pages = os.listdir("templates/now")
now_pages = [
page for page in now_pages if page != "template.html" and page != "old.html"
]
now_pages.sort(reverse=True)
return now_pages
def list_now_dates():
now_pages = list_now_page_files()
now_dates = [page.split(".")[0] for page in now_pages]
return now_dates
def get_latest_now_date(formatted=False):
if formatted:
date=list_now_dates()[0]
date = datetime.strptime(date, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y")
return date
return list_now_dates()[0]
def render_now_page(date,handshake_scripts=None):
# If the date is not available, render the latest page
if date is None:
return render_latest_now(handshake_scripts=handshake_scripts)
# Remove .html
date = date.removesuffix(".html")
if date not in list_now_dates():
return render_template("404.html"), 404
date_formatted = datetime.strptime(date, "%y_%m_%d")
date_formatted = date_formatted.strftime("%A, %B %d, %Y")
return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts)
def render_latest_now(handshake_scripts=None):
now_page = list_now_dates()[0]
return render_now_page(now_page,handshake_scripts=handshake_scripts)

483
server.py
View File

@@ -20,20 +20,21 @@ from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_H
from ansi2html import Ansi2HTMLConverter from ansi2html import Ansi2HTMLConverter
from functools import cache from functools import cache
from PIL import Image from PIL import Image
from mail import sendEmail from blueprints.now import now_bp
from now import ( from blueprints.blog import blog_bp
list_now_dates, from blueprints.wellknown import wk_bp
get_latest_now_date, from blueprints.api import api_bp, getGitCommit, getClientIP
list_now_page_files,
render_latest_now,
render_now_page,
)
from blog import render_blog_home, render_blog_page
from sol import create_transaction from sol import create_transaction
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
# Register the now blueprint with the URL prefix
app.register_blueprint(now_bp, url_prefix='/now')
app.register_blueprint(blog_bp, url_prefix='/blog')
app.register_blueprint(wk_bp, url_prefix='/.well-known')
app.register_blueprint(api_bp, url_prefix='/api/v1')
dotenv.load_dotenv() dotenv.load_dotenv()
# Rate limiting for hosting enquiries # Rate limiting for hosting enquiries
@@ -93,35 +94,6 @@ def getFilePath(name, path):
return os.path.join(root, name) return os.path.join(root, name)
def getClientIP(request):
x_forwarded_for = request.headers.get("X-Forwarded-For")
if x_forwarded_for:
ip = x_forwarded_for.split(",")[0]
else:
ip = request.remote_addr
return ip
def getGitCommit():
# if .git exists, get the latest commit hash
if os.path.isdir(".git"):
git_dir = ".git"
head_ref = ""
with open(os.path.join(git_dir, "HEAD")) as file:
head_ref = file.read().strip()
if head_ref.startswith("ref: "):
head_ref = head_ref[5:]
with open(os.path.join(git_dir, head_ref)) as file:
return file.read().strip()
else:
return head_ref
# Check if env SOURCE_COMMIT is set
if "SOURCE_COMMIT" in os.environ:
return os.environ["SOURCE_COMMIT"]
return "failed to get version"
# endregion # endregion
@@ -190,68 +162,6 @@ def javascript_get(name):
return render_template("404.html"), 404 return render_template("404.html"), 404
return send_from_directory("templates/assets/js", request.path.split("/")[-1]) return send_from_directory("templates/assets/js", request.path.split("/")[-1])
# endregion
# region Well-known routes
@app.route("/.well-known/<path:path>")
def wk_index_get(path):
return send_from_directory(".well-known", path)
@app.route("/.well-known/wallets/<path:path>")
def wk_wallet_get(path):
if path[0] == "." and 'proof' not in path:
return send_from_directory(
".well-known/wallets", path, mimetype="application/json"
)
elif os.path.isfile(".well-known/wallets/" + path):
address = ""
with open(".well-known/wallets/" + path) as file:
address = file.read()
address = address.strip()
return make_response(address, 200, {"Content-Type": "text/plain"})
if os.path.isfile(".well-known/wallets/" + path.upper()):
return redirect("/.well-known/wallets/" + path.upper(), code=302)
return render_template("404.html"), 404
@app.route("/.well-known/nostr.json")
def wk_nostr_get():
# Get name parameter
name = request.args.get("name")
if name:
return jsonify(
{
"names": {
name: "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82"
}
}
)
return jsonify(
{
"names": {
"nathan": "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82",
"_": "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82",
}
}
)
@app.route("/.well-known/xrp-ledger.toml")
def wk_xrp_get():
# Create a response with the xrp-ledger.toml file
with open(".well-known/xrp-ledger.toml") as file:
toml = file.read()
response = make_response(toml, 200, {"Content-Type": "application/toml"})
# Set cors headers
response.headers["Access-Control-Allow-Origin"] = "*"
return response
# endregion # endregion
# region PWA routes # region PWA routes
@@ -279,236 +189,6 @@ def serviceWorker_get():
# endregion # endregion
# region Solana Links
SOLANA_HEADERS = {
"Content-Type": "application/json",
"X-Action-Version": "2.4.2",
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
@app.route("/actions.json")
def sol_actions_get():
return jsonify(
{"rules": [{"pathPattern": "/donate**", "apiPath": "/api/donate**"}]}
)
@app.route("/api/donate", methods=["GET", "OPTIONS"])
def sol_donate_get():
data = {
"icon": "https://nathan.woodburn.au/assets/img/profile.png",
"label": "Donate to Nathan.Woodburn/",
"title": "Donate to Nathan.Woodburn/",
"description": "Student, developer, and crypto enthusiast",
"links": {
"actions": [
{"label": "0.01 SOL", "href": "/api/donate/0.01"},
{"label": "0.1 SOL", "href": "/api/donate/0.1"},
{"label": "1 SOL", "href": "/api/donate/1"},
{
"href": "/api/donate/{amount}",
"label": "Donate",
"parameters": [
{"name": "amount", "label": "Enter a custom SOL amount"}
],
},
]
},
}
response = make_response(jsonify(data), 200, SOLANA_HEADERS)
if request.method == "OPTIONS":
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = (
"Content-Type,Authorization,Content-Encoding,Accept-Encoding,X-Action-Version,X-Blockchain-Ids"
)
return response
@app.route("/api/donate/<amount>")
def sol_donate_amount_get(amount):
data = {
"icon": "https://nathan.woodburn.au/assets/img/profile.png",
"label": f"Donate {amount} SOL to Nathan.Woodburn/",
"title": "Donate to Nathan.Woodburn/",
"description": f"Donate {amount} SOL to Nathan.Woodburn/",
}
return jsonify(data), 200, SOLANA_HEADERS
@app.route("/api/donate/<amount>", methods=["POST"])
def sol_donate_post(amount):
if not request.json:
return jsonify({"message": "Error: No JSON data provided"}), 400, SOLANA_HEADERS
if "account" not in request.json:
return jsonify({"message": "Error: No account provided"}), 400, SOLANA_HEADERS
sender = request.json["account"]
# Make sure amount is a number
try:
amount = float(amount)
except ValueError:
return jsonify({"message": "Error: Invalid amount"}), 400, SOLANA_HEADERS
if amount < 0.0001:
return jsonify({"message": "Error: Amount too small"}), 400, SOLANA_HEADERS
transaction = create_transaction(sender, amount)
return jsonify({"message": "Success", "transaction": transaction}), 200, SOLANA_HEADERS
# endregion
# region API routes
@app.route("/api/version")
@app.route("/api/v1/version")
def api_version_get():
return jsonify({"version": getGitCommit()})
@app.route("/api")
@app.route("/api/")
@app.route("/api/v1")
@app.route("/api/v1/")
@app.route("/api/help")
def api_help_get():
return jsonify({
"message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au",
"endpoints": {
"/api/v1/time": "Get the current time",
"/api/v1/timezone": "Get the current timezone",
"/api/v1/message": "Get the message from the config",
"/api/v1/ip": "Get your IP address",
"/api/v1/project": "Get the current project from git",
"/api/v1/version": "Get the current version of the website",
"/api/v1/help": "Get this help message"
},
"version": getGitCommit()
})
@app.route("/api/time")
@app.route("/api/v1/time")
def api_time_get():
timezone_offset = datetime.timedelta(hours=ncConfig["time-zone"])
timezone = datetime.timezone(offset=timezone_offset)
time = datetime.datetime.now(tz=timezone)
return jsonify({
"timestring": time.strftime("%A, %B %d, %Y %I:%M %p"),
"timestamp": time.timestamp(),
"timezone": ncConfig["time-zone"],
"timeISO": time.isoformat()
})
@app.route("/api/timezone")
@app.route("/api/v1/timezone")
def api_timezone_get():
return jsonify({"timezone": ncConfig["time-zone"]})
@app.route("/api/timezone", methods=["POST"])
@app.route("/api/v1/timezone", methods=["POST"])
def api_timezone_post():
# Refresh config
global ncConfig
conf = requests.get(
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json")
if conf.status_code != 200:
return jsonify({"message": "Error: Could not get timezone"})
if not conf.json():
return jsonify({"message": "Error: Could not get timezone"})
conf = conf.json()
if "time-zone" not in conf:
return jsonify({"message": "Error: Could not get timezone"})
ncConfig = conf
return jsonify({"message": "Successfully pulled latest timezone", "timezone": ncConfig["time-zone"]})
@app.route("/api/message")
@app.route("/api/v1/message")
def api_message_get():
return jsonify({"message": ncConfig["message"]})
@app.route("/api/ip")
@app.route("/api/v1/ip")
def api_ip_get():
return jsonify({"ip": getClientIP(request)})
@app.route("/api/email", methods=["POST"])
@app.route("/api/v1/email", methods=["POST"])
def api_email_post():
# Verify json
if not request.is_json:
return jsonify({
"status": 400,
"error": "Bad request JSON Data missing"
})
# Check if api key sent
data = request.json
if not data:
return jsonify({
"status": 400,
"error": "Bad request JSON Data missing"
})
if "key" not in data:
return jsonify({
"status": 401,
"error": "Unauthorized 'key' missing"
})
if data["key"] != os.getenv("EMAIL_KEY"):
return jsonify({
"status": 401,
"error": "Unauthorized 'key' invalid"
})
return sendEmail(data)
@app.route("/api/v1/project")
def api_project_get():
try:
git = requests.get(
"https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1",
headers={"Authorization": os.getenv("git_token")},
)
git = git.json()
git = git[0]
repo_name = git["repo"]["name"]
repo_name = repo_name.lower()
repo_description = git["repo"]["description"]
except Exception as e:
repo_name = "nathanwoodburn.github.io"
repo_description = "Personal website"
git = {
"repo": {
"html_url": "https://nathan.woodburn.au",
"name": "nathanwoodburn.github.io",
"description": "Personal website",
}
}
print(f"Error getting git data: {e}")
return jsonify({
"repo_name": repo_name,
"repo_description": repo_description,
"git": git,
})
# endregion
# region Misc routes # region Misc routes
@@ -530,6 +210,17 @@ def links_get():
def generator_get(): def generator_get():
return render_template(request.path.split("/")[-2] + ".html") return render_template(request.path.split("/")[-2] + ".html")
@app.route("/api/<path:function>")
def api_old_get(function):
return redirect(f"/api/v1/{function}", code=301)
@app.route("/actions.json")
def sol_actions_get():
return jsonify(
{"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]}
)
# endregion # endregion
# region Main routes # region Main routes
@@ -729,138 +420,6 @@ def index_get():
return resp return resp
# region Now Pages
@app.route("/now")
@app.route("/now/")
def now_index_get():
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
return render_latest_now(handshake_scripts)
@app.route("/now/<path:path>")
def now_path_get(path):
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
return render_now_page(path, handshake_scripts)
@app.route("/old")
@app.route("/old/")
@app.route("/now/old")
@app.route("/now/old/")
def now_old_get():
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
now_dates = list_now_dates()[1:]
html = '<ul class="list-group">'
html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_now_date(True)}</li></a>'
for date in now_dates:
link = date
date = datetime.datetime.strptime(date, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y")
html += f'<a style="text-decoration:none;" href="/now/{link}"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{date}</li></a>'
html += "</ul>"
return render_template(
"now/old.html", handshake_scripts=handshake_scripts, now_pages=html
)
@app.route("/now.rss")
@app.route("/now.xml")
@app.route("/rss.xml")
def now_rss_get():
host = "https://" + request.host
if ":" in request.host:
host = "http://" + request.host
# Generate RSS feed
now_pages = list_now_page_files()
rss = f'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nathan.Woodburn/</title><link>{host}</link><description>See what I\'ve been up to</description><language>en-us</language><lastBuildDate>{datetime.datetime.now(tz=datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")}</lastBuildDate><atom:link href="{host}/now.rss" rel="self" type="application/rss+xml" />'
for page in now_pages:
link = page.strip(".html")
date = datetime.datetime.strptime(link, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y")
rss += f'<item><title>What\'s Happening {date}</title><link>{host}/now/{link}</link><description>Latest updates for {date}</description><guid>{host}/now/{link}</guid></item>'
rss += "</channel></rss>"
return make_response(rss, 200, {"Content-Type": "application/rss+xml"})
@app.route("/now.json")
def now_json_get():
now_pages = list_now_page_files()
host = "https://" + request.host
if ":" in request.host:
host = "http://" + request.host
now_pages = [{"url": host+"/now/"+page.strip(".html"), "date": datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime(
"%A, %B %d, %Y"), "title": "What's Happening "+datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime("%A, %B %d, %Y")} for page in now_pages]
return jsonify(now_pages)
# endregion
# region Blog Pages
@app.route("/blog")
@app.route("/blog/")
def blog_index_get():
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
return render_blog_home(handshake_scripts)
@app.route("/blog/<path:path>")
def blog_path_get(path):
global handshake_scripts
# If localhost, don't load handshake
if (
request.host == "localhost:5000"
or request.host == "127.0.0.1:5000"
or os.getenv("dev") == "true"
or request.host == "test.nathan.woodburn.au"
):
handshake_scripts = ""
return render_blog_page(path, handshake_scripts)
# endregion
# region Donate # region Donate

5
sol.py
View File

@@ -40,3 +40,8 @@ def create_transaction(sender_address: str, amount: float) -> str:
raw_bytes = binascii.unhexlify(tx) raw_bytes = binascii.unhexlify(tx)
base64_string = base64.b64encode(raw_bytes).decode("utf-8") base64_string = base64.b64encode(raw_bytes).decode("utf-8")
return base64_string return base64_string
def get_solana_address() -> str:
if SOLANA_ADDRESS is None:
raise ValueError("SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address.")
return str(SOLANA_ADDRESS)