fix: Cleanup blueprint names and add tests
All checks were successful
Build Docker / BuildImage (push) Successful in 2m17s

This commit is contained in:
2025-10-13 15:32:31 +11:00
parent 3d5c16f9cb
commit 7f591e2724
16 changed files with 320 additions and 201 deletions

View File

@@ -7,7 +7,7 @@ acme_bp = Blueprint('acme', __name__)
@acme_bp.route("/hnsdoh-acme", methods=["POST"]) @acme_bp.route("/hnsdoh-acme", methods=["POST"])
def acme_post(): def post():
# Get the TXT record from the request # Get the TXT record from the request
if not request.is_json or not request.json: if not request.is_json or not request.json:
return json_response(request, "415 Unsupported Media Type", 415) return json_response(request, "415 Unsupported Media Type", 415)

View File

@@ -1,13 +1,16 @@
from flask import Blueprint, request, jsonify, make_response from flask import Blueprint, request, jsonify
import os import os
import datetime import datetime
import requests import requests
from mail import sendEmail from mail import sendEmail
from sol import create_transaction
from tools import getClientIP, getGitCommit, json_response from tools import getClientIP, getGitCommit, json_response
from blueprints.sol import sol_bp
api_bp = Blueprint('api', __name__) api_bp = Blueprint('api', __name__)
# Register solana blueprint
api_bp.register_blueprint(sol_bp)
NC_CONFIG = requests.get( NC_CONFIG = requests.get(
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json" "https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json"
@@ -19,7 +22,7 @@ if 'time-zone' not in NC_CONFIG:
@api_bp.route("/") @api_bp.route("/")
@api_bp.route("/help") @api_bp.route("/help")
def help_get(): def help():
return jsonify({ return jsonify({
"message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au", "message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au",
"endpoints": { "endpoints": {
@@ -39,12 +42,12 @@ def help_get():
@api_bp.route("/version") @api_bp.route("/version")
def version_get(): def version():
return jsonify({"version": getGitCommit()}) return jsonify({"version": getGitCommit()})
@api_bp.route("/time") @api_bp.route("/time")
def time_get(): def time():
timezone_offset = datetime.timedelta(hours=NC_CONFIG["time-zone"]) timezone_offset = datetime.timedelta(hours=NC_CONFIG["time-zone"])
timezone = datetime.timezone(offset=timezone_offset) timezone = datetime.timezone(offset=timezone_offset)
time = datetime.datetime.now(tz=timezone) time = datetime.datetime.now(tz=timezone)
@@ -59,7 +62,7 @@ def time_get():
@api_bp.route("/timezone") @api_bp.route("/timezone")
def timezone_get(): def timezone():
return jsonify({ return jsonify({
"timezone": NC_CONFIG["time-zone"], "timezone": NC_CONFIG["time-zone"],
"ip": getClientIP(request), "ip": getClientIP(request),
@@ -67,7 +70,7 @@ def timezone_get():
}) })
@api_bp.route("/message") @api_bp.route("/message")
def message_get(): def message():
return jsonify({ return jsonify({
"message": NC_CONFIG["message"], "message": NC_CONFIG["message"],
"ip": getClientIP(request), "ip": getClientIP(request),
@@ -76,7 +79,7 @@ def message_get():
@api_bp.route("/ip") @api_bp.route("/ip")
def ip_get(): def ip():
return jsonify({ return jsonify({
"ip": getClientIP(request), "ip": getClientIP(request),
"status": 200 "status": 200
@@ -105,7 +108,7 @@ def email_post():
@api_bp.route("/project") @api_bp.route("/project")
def project_get(): def project():
gitinfo = { gitinfo = {
"website": None, "website": None,
} }
@@ -135,83 +138,3 @@ def project_get():
"ip": getClientIP(request), "ip": getClientIP(request),
"status": 200 "status": 200
}) })
# 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

@@ -8,7 +8,7 @@ from tools import isCurl, getClientIP
blog_bp = Blueprint('blog', __name__) blog_bp = Blueprint('blog', __name__)
def list_blog_page_files(): def list_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") blog_pages = [page.removesuffix(".md")
@@ -17,7 +17,7 @@ def list_blog_page_files():
return blog_pages return blog_pages
def render_blog_page(date, handshake_scripts=None): def render_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
@@ -83,9 +83,9 @@ def fix_numbered_lists(html):
return str(soup) return str(soup)
def render_blog_home(handshake_scripts=None): def render_home(handshake_scripts=None):
# Get a list of pages # Get a list of pages
blog_pages = list_blog_page_files() blog_pages = list_page_files()
# Create a html list of pages # Create a html list of pages
blog_pages = [ blog_pages = [
f"""<li class="list-group-item"> f"""<li class="list-group-item">
@@ -105,7 +105,7 @@ def render_blog_home(handshake_scripts=None):
@blog_bp.route("/") @blog_bp.route("/")
def blog_index_get(): def index():
if not isCurl(request): if not isCurl(request):
global handshake_scripts global handshake_scripts
@@ -117,10 +117,10 @@ def blog_index_get():
or request.host == "test.nathan.woodburn.au" or request.host == "test.nathan.woodburn.au"
): ):
handshake_scripts = "" handshake_scripts = ""
return render_blog_home(handshake_scripts) return render_home(handshake_scripts)
# Get a list of pages # Get a list of pages
blog_pages = list_blog_page_files() blog_pages = list_page_files()
# Create a html list of pages # Create a html list of pages
blog_pages = [ blog_pages = [
{"name":page.replace("_", " "),"url":f"/blog/{page}", "download": f"/blog/{page}.md"} for page in blog_pages {"name":page.replace("_", " "),"url":f"/blog/{page}", "download": f"/blog/{page}.md"} for page in blog_pages
@@ -138,7 +138,7 @@ def blog_index_get():
@blog_bp.route("/<path:path>") @blog_bp.route("/<path:path>")
def blog_path_get(path): def path(path):
if not isCurl(request): if not isCurl(request):
global handshake_scripts global handshake_scripts
# If localhost, don't load handshake # If localhost, don't load handshake
@@ -150,7 +150,7 @@ def blog_path_get(path):
): ):
handshake_scripts = "" handshake_scripts = ""
return render_blog_page(path, handshake_scripts) return render_page(path, handshake_scripts)
# Convert md to html # Convert md to html
if not os.path.exists(f"data/blog/{path}.md"): if not os.path.exists(f"data/blog/{path}.md"):
@@ -170,7 +170,7 @@ def blog_path_get(path):
}), 200 }), 200
@blog_bp.route("/<path:path>.md") @blog_bp.route("/<path:path>.md")
def blog_path_md_get(path): def path_md(path):
if not os.path.exists(f"data/blog/{path}.md"): if not os.path.exists(f"data/blog/{path}.md"):
return render_template("404.html"), 404 return render_template("404.html"), 404

View File

@@ -5,7 +5,7 @@ import os
# Create blueprint # Create blueprint
now_bp = Blueprint('now', __name__) now_bp = Blueprint('now', __name__)
def list_now_page_files(): def list_page_files():
now_pages = os.listdir("templates/now") now_pages = os.listdir("templates/now")
now_pages = [ now_pages = [
page for page in now_pages if page != "template.html" and page != "old.html" page for page in now_pages if page != "template.html" and page != "old.html"
@@ -13,31 +13,31 @@ def list_now_page_files():
now_pages.sort(reverse=True) now_pages.sort(reverse=True)
return now_pages return now_pages
def list_now_dates(): def list_dates():
now_pages = list_now_page_files() now_pages = list_page_files()
now_dates = [page.split(".")[0] for page in now_pages] now_dates = [page.split(".")[0] for page in now_pages]
return now_dates return now_dates
def get_latest_now_date(formatted=False): def get_latest_date(formatted=False):
if formatted: if formatted:
date=list_now_dates()[0] date=list_dates()[0]
date = datetime.datetime.strptime(date, "%y_%m_%d") date = datetime.datetime.strptime(date, "%y_%m_%d")
date = date.strftime("%A, %B %d, %Y") date = date.strftime("%A, %B %d, %Y")
return date return date
return list_now_dates()[0] return list_dates()[0]
def render_latest_now(handshake_scripts=None): def render_latest(handshake_scripts=None):
now_page = list_now_dates()[0] now_page = list_dates()[0]
return render_now_page(now_page,handshake_scripts=handshake_scripts) return render(now_page,handshake_scripts=handshake_scripts)
def render_now_page(date,handshake_scripts=None): def render(date,handshake_scripts=None):
# If the date is not available, render the latest page # If the date is not available, render the latest page
if date is None: if date is None:
return render_latest_now(handshake_scripts=handshake_scripts) return render_latest(handshake_scripts=handshake_scripts)
# Remove .html # Remove .html
date = date.removesuffix(".html") date = date.removesuffix(".html")
if date not in list_now_dates(): if date not in list_dates():
return render_template("404.html"), 404 return render_template("404.html"), 404
@@ -46,7 +46,7 @@ def render_now_page(date,handshake_scripts=None):
return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts) return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts)
@now_bp.route("/") @now_bp.route("/")
def now_index_get(): def index():
handshake_scripts = '' handshake_scripts = ''
# If localhost, don't load handshake # If localhost, don't load handshake
if ( if (
@@ -59,11 +59,11 @@ def now_index_get():
else: else:
handshake_scripts = '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>' 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) return render_latest(handshake_scripts)
@now_bp.route("/<path:path>") @now_bp.route("/<path:path>")
def now_path_get(path): def path(path):
handshake_scripts = '' handshake_scripts = ''
# If localhost, don't load handshake # If localhost, don't load handshake
if ( if (
@@ -76,12 +76,12 @@ def now_path_get(path):
else: else:
handshake_scripts = '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>' 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) return render(path, handshake_scripts)
@now_bp.route("/old") @now_bp.route("/old")
@now_bp.route("/old/") @now_bp.route("/old/")
def now_old_get(): def old():
handshake_scripts = '' handshake_scripts = ''
# If localhost, don't load handshake # If localhost, don't load handshake
if ( if (
@@ -94,9 +94,9 @@ def now_old_get():
else: else:
handshake_scripts = '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>' 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:] now_dates = list_dates()[1:]
html = '<ul class="list-group">' 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>' html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_date(True)}</li></a>'
for date in now_dates: for date in now_dates:
link = date link = date
@@ -113,12 +113,12 @@ def now_old_get():
@now_bp.route("/now.rss") @now_bp.route("/now.rss")
@now_bp.route("/now.xml") @now_bp.route("/now.xml")
@now_bp.route("/rss.xml") @now_bp.route("/rss.xml")
def now_rss_get(): def rss():
host = "https://" + request.host host = "https://" + request.host
if ":" in request.host: if ":" in request.host:
host = "http://" + request.host host = "http://" + request.host
# Generate RSS feed # Generate RSS feed
now_pages = list_now_page_files() now_pages = list_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" />' 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: for page in now_pages:
link = page.strip(".html") link = page.strip(".html")
@@ -130,8 +130,8 @@ def now_rss_get():
@now_bp.route("/now.json") @now_bp.route("/now.json")
def now_json_get(): def json():
now_pages = list_now_page_files() now_pages = list_page_files()
host = "https://" + request.host host = "https://" + request.host
if ":" in request.host: if ":" in request.host:
host = "http://" + request.host host = "http://" + request.host

View File

@@ -5,7 +5,7 @@ import requests
podcast_bp = Blueprint('podcast', __name__) podcast_bp = Blueprint('podcast', __name__)
@podcast_bp.route("/ID1") @podcast_bp.route("/ID1")
def podcast_index_get(): def index():
# Proxy to ID1 url # Proxy to ID1 url
req = requests.get("https://podcasts.c.woodburn.au/ID1") req = requests.get("https://podcasts.c.woodburn.au/ID1")
if req.status_code != 200: if req.status_code != 200:
@@ -17,7 +17,7 @@ def podcast_index_get():
@podcast_bp.route("/ID1/") @podcast_bp.route("/ID1/")
def podcast_contents_get(): def contents():
# Proxy to ID1 url # Proxy to ID1 url
req = requests.get("https://podcasts.c.woodburn.au/ID1/") req = requests.get("https://podcasts.c.woodburn.au/ID1/")
if req.status_code != 200: if req.status_code != 200:
@@ -28,7 +28,7 @@ def podcast_contents_get():
@podcast_bp.route("/ID1/<path:path>") @podcast_bp.route("/ID1/<path:path>")
def podcast_path_get(path): def path(path):
# Proxy to ID1 url # Proxy to ID1 url
req = requests.get("https://podcasts.c.woodburn.au/ID1/" + path) req = requests.get("https://podcasts.c.woodburn.au/ID1/" + path)
if req.status_code != 200: if req.status_code != 200:
@@ -39,7 +39,7 @@ def podcast_path_get(path):
@podcast_bp.route("/ID1.xml") @podcast_bp.route("/ID1.xml")
def podcast_xml_get(): def xml():
# Proxy to ID1 url # Proxy to ID1 url
req = requests.get("https://podcasts.c.woodburn.au/ID1.xml") req = requests.get("https://podcasts.c.woodburn.au/ID1.xml")
if req.status_code != 200: if req.status_code != 200:
@@ -50,7 +50,7 @@ def podcast_xml_get():
@podcast_bp.route("/podsync.opml") @podcast_bp.route("/podsync.opml")
def podcast_podsync_get(): def podsync():
req = requests.get("https://podcasts.c.woodburn.au/podsync.opml") req = requests.get("https://podcasts.c.woodburn.au/podsync.opml")
if req.status_code != 200: if req.status_code != 200:
return error_response(request, "Error from Podcast Server", req.status_code) return error_response(request, "Error from Podcast Server", req.status_code)

125
blueprints/sol.py Normal file
View File

@@ -0,0 +1,125 @@
from flask import Blueprint, request, jsonify, make_response
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solders.system_program import TransferParams, transfer
from solders.message import MessageV0
from solders.transaction import VersionedTransaction
from solders.null_signer import NullSigner
import binascii
import base64
import os
sol_bp = Blueprint('sol', __name__)
SOLANA_HEADERS = {
"Content-Type": "application/json",
"X-Action-Version": "2.4.2",
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
SOLANA_ADDRESS = None
if os.path.isfile(".well-known/wallets/SOL"):
with open(".well-known/wallets/SOL") as file:
address = file.read()
SOLANA_ADDRESS = Pubkey.from_string(address.strip())
def create_transaction(sender_address: str, amount: float) -> 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.")
# Create transaction
sender = Pubkey.from_string(sender_address)
transfer_ix = transfer(
TransferParams(
from_pubkey=sender, to_pubkey=SOLANA_ADDRESS, lamports=int(
amount * 1000000000)
)
)
solana_client = Client("https://api.mainnet-beta.solana.com")
blockhashData = solana_client.get_latest_blockhash()
blockhash = blockhashData.value.blockhash
msg = MessageV0.try_compile(
payer=sender,
instructions=[transfer_ix],
address_lookup_table_accounts=[],
recent_blockhash=blockhash,
)
tx = VersionedTransaction(message=msg, keypairs=[NullSigner(sender)])
tx = bytes(tx).hex()
raw_bytes = binascii.unhexlify(tx)
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
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)
@sol_bp.route("/donate", methods=["GET", "OPTIONS"])
def sol_donate():
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
@sol_bp.route("/donate/<amount>")
def sol_donate_amount(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
@sol_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

View File

@@ -5,12 +5,12 @@ wk_bp = Blueprint('well-known', __name__)
@wk_bp.route("/<path:path>") @wk_bp.route("/<path:path>")
def wk_index_get(path): def index(path):
return send_from_directory(".well-known", path) return send_from_directory(".well-known", path)
@wk_bp.route("/wallets/<path:path>") @wk_bp.route("/wallets/<path:path>")
def wk_wallet_get(path): def wallets(path):
if path[0] == "." and 'proof' not in path: if path[0] == "." and 'proof' not in path:
return send_from_directory( return send_from_directory(
".well-known/wallets", path, mimetype="application/json" ".well-known/wallets", path, mimetype="application/json"
@@ -29,7 +29,7 @@ def wk_wallet_get(path):
@wk_bp.route("/nostr.json") @wk_bp.route("/nostr.json")
def wk_nostr_get(): def nostr():
# Get name parameter # Get name parameter
name = request.args.get("name") name = request.args.get("name")
if name: if name:
@@ -51,7 +51,7 @@ def wk_nostr_get():
@wk_bp.route("/xrp-ledger.toml") @wk_bp.route("/xrp-ledger.toml")
def wk_xrp_get(): def xrp():
# Create a response with the xrp-ledger.toml file # Create a response with the xrp-ledger.toml file
with open(".well-known/xrp-ledger.toml") as file: with open(".well-known/xrp-ledger.toml") as file:
toml = file.read() toml = file.read()

View File

@@ -81,7 +81,7 @@ NC_CONFIG = requests.get(
@app.route("/assets/<path:path>") @app.route("/assets/<path:path>")
def asset_get(path): def asset(path):
if path.endswith(".json"): if path.endswith(".json"):
return send_from_directory( return send_from_directory(
"templates/assets", path, mimetype="application/json" "templates/assets", path, mimetype="application/json"
@@ -121,7 +121,7 @@ def asset_get(path):
@app.route("/sitemap") @app.route("/sitemap")
@app.route("/sitemap.xml") @app.route("/sitemap.xml")
def sitemap_get(): def sitemap():
# Remove all .html from sitemap # Remove all .html from sitemap
if not os.path.isfile("templates/sitemap.xml"): if not os.path.isfile("templates/sitemap.xml"):
return error_response(request) return error_response(request)
@@ -133,14 +133,14 @@ def sitemap_get():
@app.route("/favicon.<ext>") @app.route("/favicon.<ext>")
def favicon_get(ext): def favicon(ext):
if ext not in ("png", "svg", "ico"): if ext not in ("png", "svg", "ico"):
return error_response(request) return error_response(request)
return send_from_directory("templates/assets/img/favicon", f"favicon.{ext}") return send_from_directory("templates/assets/img/favicon", f"favicon.{ext}")
@app.route("/<name>.js") @app.route("/<name>.js")
def javascript_get(name): def javascript(name):
# Check if file in js directory # Check if file in js directory
if not os.path.isfile("templates/assets/js/" + request.path.split("/")[-1]): if not os.path.isfile("templates/assets/js/" + request.path.split("/")[-1]):
return error_response(request) return error_response(request)
@@ -148,7 +148,7 @@ def javascript_get(name):
@app.route("/download/<path:path>") @app.route("/download/<path:path>")
def download_get(path): def download(path):
if path not in DOWNLOAD_ROUTES: if path not in DOWNLOAD_ROUTES:
return error_response(request, message="Invalid download") return error_response(request, message="Invalid download")
# Check if file exists # Check if file exists
@@ -163,7 +163,7 @@ def download_get(path):
@app.route("/manifest.json") @app.route("/manifest.json")
def manifest_get(): def manifest():
host = request.host host = request.host
# Read as json # Read as json
@@ -179,7 +179,7 @@ def manifest_get():
@app.route("/sw.js") @app.route("/sw.js")
def serviceWorker_get(): def serviceWorker():
return send_from_directory("pwa", "sw.js") return send_from_directory("pwa", "sw.js")
# endregion # endregion
@@ -191,19 +191,19 @@ def serviceWorker_get():
@app.route("/meet") @app.route("/meet")
@app.route("/meeting") @app.route("/meeting")
@app.route("/appointment") @app.route("/appointment")
def meetingLink_get(): def meetingLink():
return redirect( return redirect(
"https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", code=302 "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", code=302
) )
@app.route("/links") @app.route("/links")
def links_get(): def links():
return render_template("link.html") return render_template("link.html")
@app.route("/api/<path:function>") @app.route("/api/<path:function>")
def api_legacy_get(function): def api_legacy(function):
# Check if function is in api blueprint # Check if function is in api blueprint
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():
# Check if the redirect route exists # Check if the redirect route exists
@@ -213,7 +213,7 @@ def api_legacy_get(function):
@app.route("/actions.json") @app.route("/actions.json")
def sol_actions_get(): def sol_actions():
return jsonify( return jsonify(
{"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]} {"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]}
) )
@@ -224,7 +224,7 @@ def sol_actions_get():
@app.route("/") @app.route("/")
def index_get(): def index():
global HANDSHAKE_SCRIPTS global HANDSHAKE_SCRIPTS
global PROJECTS global PROJECTS
global PROJECTS_UPDATED global PROJECTS_UPDATED
@@ -413,7 +413,7 @@ def index_get():
@app.route("/donate") @app.route("/donate")
def donate_get(): def donate():
global HANDSHAKE_SCRIPTS global HANDSHAKE_SCRIPTS
# If localhost, don't load handshake # If localhost, don't load handshake
if ( if (
@@ -539,7 +539,7 @@ def donate_get():
@app.route("/address/<path:address>") @app.route("/address/<path:address>")
def qraddress_get(address): def qraddress(address):
qr = qrcode.QRCode( qr = qrcode.QRCode(
version=1, version=1,
error_correction=ERROR_CORRECT_L, error_correction=ERROR_CORRECT_L,
@@ -560,7 +560,7 @@ def qraddress_get(address):
@app.route("/qrcode/<path:data>") @app.route("/qrcode/<path:data>")
@app.route("/qr/<path:data>") @app.route("/qr/<path:data>")
def qrcode_get(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)
@@ -586,7 +586,7 @@ def qrcode_get(data):
@app.route("/supersecretpath") @app.route("/supersecretpath")
def supersecretpath_get(): def supersecretpath():
ascii_art = "" ascii_art = ""
if os.path.isfile("data/ascii.txt"): if os.path.isfile("data/ascii.txt"):
with open("data/ascii.txt") as file: with open("data/ascii.txt") as file:
@@ -704,7 +704,7 @@ def hosting_post():
@app.route("/resume.pdf") @app.route("/resume.pdf")
def resume_pdf_get(): def resume_pdf():
# Check if file exists # Check if file exists
if os.path.isfile("data/resume.pdf"): if os.path.isfile("data/resume.pdf"):
return send_file("data/resume.pdf") return send_file("data/resume.pdf")
@@ -717,7 +717,7 @@ def resume_pdf_get():
@app.route("/<path:path>") @app.route("/<path:path>")
def catch_all_get(path: str): def catch_all(path: str):
global HANDSHAKE_SCRIPTS global HANDSHAKE_SCRIPTS
# If localhost, don't load handshake # If localhost, don't load handshake
if ( if (

47
sol.py
View File

@@ -1,47 +0,0 @@
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solders.system_program import TransferParams, transfer
from solders.message import MessageV0
from solders.transaction import VersionedTransaction
from solders.null_signer import NullSigner
import binascii
import base64
import os
SOLANA_ADDRESS = None
if os.path.isfile(".well-known/wallets/SOL"):
with open(".well-known/wallets/SOL") as file:
address = file.read()
SOLANA_ADDRESS = Pubkey.from_string(address.strip())
def create_transaction(sender_address: str, amount: float) -> 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.")
# Create transaction
sender = Pubkey.from_string(sender_address)
transfer_ix = transfer(
TransferParams(
from_pubkey=sender, to_pubkey=SOLANA_ADDRESS, lamports=int(
amount * 1000000000)
)
)
solana_client = Client("https://api.mainnet-beta.solana.com")
blockhashData = solana_client.get_latest_blockhash()
blockhash = blockhashData.value.blockhash
msg = MessageV0.try_compile(
payer=sender,
instructions=[transfer_ix],
address_lookup_table_accounts=[],
recent_blockhash=blockhash,
)
tx = VersionedTransaction(message=msg, keypairs=[NullSigner(sender)])
tx = bytes(tx).hex()
raw_bytes = binascii.unhexlify(tx)
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
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)

17
tests/api.hurl Normal file
View File

@@ -0,0 +1,17 @@
GET http://127.0.0.1:5000/api/v1/
HTTP 200
GET http://127.0.0.1:5000/api/v1/help
HTTP 200
GET http://127.0.0.1:5000/api/v1/ip
HTTP 200
[Asserts]
jsonpath "$.ip" == "127.0.0.1"
GET http://127.0.0.1:5000/api/v1/time
HTTP 200
GET http://127.0.0.1:5000/api/v1/timezone
HTTP 200
GET http://127.0.0.1:5000/api/v1/message
HTTP 200
GET http://127.0.0.1:5000/api/v1/project
HTTP 200

9
tests/blog.hurl Normal file
View File

@@ -0,0 +1,9 @@
GET http://127.0.0.1:5000/blog/
HTTP 200
GET http://127.0.0.1:5000/blog/Fingertip_on_Linux_Mint
HTTP 200
GET http://127.0.0.1:5000/blog/Fingertip_on_Linux_Mint.md
HTTP 200

41
tests/legacy_api.hurl Normal file
View File

@@ -0,0 +1,41 @@
GET http://127.0.0.1:5000/api/help
HTTP 301
[Asserts]
header "Location" == "/api/v1/help"
GET http://127.0.0.1:5000/api/ip
HTTP 301
[Asserts]
header "Location" == "/api/v1/ip"
GET http://127.0.0.1:5000/api/message
HTTP 301
[Asserts]
header "Location" == "/api/v1/message"
GET http://127.0.0.1:5000/api/project
HTTP 301
[Asserts]
header "Location" == "/api/v1/project"
GET http://127.0.0.1:5000/api/donate
HTTP 301
[Asserts]
header "Location" == "/api/v1/donate"
GET http://127.0.0.1:5000/api/time
HTTP 301
[Asserts]
header "Location" == "/api/v1/time"
GET http://127.0.0.1:5000/api/timezone
HTTP 301
[Asserts]
header "Location" == "/api/v1/timezone"
GET http://127.0.0.1:5000/api/version
HTTP 301
[Asserts]
header "Location" == "/api/v1/version"

24
tests/now.hurl Normal file
View File

@@ -0,0 +1,24 @@
GET http://127.0.0.1:5000/now/
HTTP 200
GET http://127.0.0.1:5000/now/old
HTTP 200
GET http://127.0.0.1:5000/now/24_02_18
HTTP 200
GET http://127.0.0.1:5000/now/24_02_18
HTTP 200
GET http://127.0.0.1:5000/now/now.json
HTTP 200
GET http://127.0.0.1:5000/now/now.xml
HTTP 200
GET http://127.0.0.1:5000/now/now.rss
HTTP 200
GET http://127.0.0.1:5000/now/rss.xml
HTTP 200

14
tests/sol.hurl Normal file
View File

@@ -0,0 +1,14 @@
POST http://127.0.0.1:5000/api/v1/donate/1
{"account": "1111111111111111111111111111111B"}
POST http://127.0.0.1:5000/api/v1/donate/0.01
{"account": "1111111111111111111111111111111C"}
POST http://127.0.0.1:5000/api/v1/donate/0.1
{"account": "1111111111111111111111111111111D"}
POST http://127.0.0.1:5000/api/v1/donate/0.02
{"account": "1111111111111111111111111111111E"}
POST http://127.0.0.1:5000/api/v1/donate/{amount}
{"account": "11111111111111111111111111111112"}

11
tests/well-known.hurl Normal file
View File

@@ -0,0 +1,11 @@
GET http://127.0.0.1:5000/.well-known/xrp-ledger.toml
HTTP 200
GET http://127.0.0.1:5000/.well-known/nostr.json?name=hurl
HTTP 200
[Asserts]
jsonpath "$.names.hurl" == "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82"
GET http://127.0.0.1:5000/.well-known/wallets/BTC
HTTP 200

View File

@@ -35,7 +35,7 @@ def getGitCommit():
def isCurl(request: Request) -> bool: def isCurl(request: Request) -> bool:
""" """
Check if the request is from curl Check if the request is from curl or hurl
Args: Args:
request (Request): The Flask request object request (Request): The Flask request object
@@ -45,7 +45,9 @@ def isCurl(request: Request) -> bool:
""" """
if request.headers and request.headers.get("User-Agent"): if request.headers and request.headers.get("User-Agent"):
# Check if curl # Check if curl
if "curl" in request.headers.get("User-Agent", "curl"): if "curl" in request.headers.get("User-Agent", ""):
return True
if "hurl" in request.headers.get("User-Agent",""):
return True return True
return False return False