3 Commits

Author SHA1 Message Date
53fe364fec feat: Update curl template for index
All checks were successful
Build Docker / BuildImage (push) Successful in 1m2s
2025-10-26 18:40:22 +11:00
2d0310db9f feat: Add tools curl page
All checks were successful
Build Docker / BuildImage (push) Successful in 55s
2025-10-26 18:28:15 +11:00
f783b2528c feat: Add initial ascii art for curl connections 2025-10-26 18:28:15 +11:00
13 changed files with 315 additions and 18 deletions

123
curl.py Normal file
View File

@@ -0,0 +1,123 @@
from flask import render_template
from tools import error_response, getAddress, get_tools_data, getClientIP
import os
from functools import lru_cache
import requests
def clean_path(path:str):
path = path.strip("/ ").lower()
# Strip any .html extension
if path.endswith(".html"):
path = path[:-5]
# If the path is empty, set it to "index"
if path == "":
path = "index"
return path
@lru_cache(maxsize=1)
def get_header():
with open("templates/header.ascii", "r") as f:
return f.read()
@lru_cache(maxsize=1)
def get_current_project():
git = requests.get(
"https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1",
headers={"Authorization": os.getenv("GIT_AUTH") if os.getenv("GIT_AUTH") else 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"]
return f"{repo_name} - {repo_description}"
@lru_cache(maxsize=1)
def get_projects():
projectsreq = requests.get(
"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos"
)
projects = projectsreq.json()
# Check for next page
pageNum = 1
while 'rel="next"' in projectsreq.headers["link"]:
projectsreq = requests.get(
"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos?page="
+ str(pageNum)
)
projects += projectsreq.json()
pageNum += 1
# Sort by last updated
projectsList = sorted(
projects, key=lambda x: x["updated_at"], reverse=True)
projects = ""
projectNum = 0
includedNames = []
while len(includedNames) < 5 and projectNum < len(projectsList):
# Avoid duplicates
if projectsList[projectNum]["name"] in includedNames:
projectNum += 1
continue
includedNames.append(projectsList[projectNum]["name"])
project = projectsList[projectNum]
projects += f"""{project['name']} - {project['description'] if project['description'] else 'No description'}
{project['html_url']}
"""
projectNum += 1
return projects
def curl_response(request):
# Check if <path>.ascii exists
path = clean_path(request.path)
# Handle special cases
if path == "index":
# Get current project
return render_template("index.ascii",repo=get_current_project(), ip=getClientIP(request)), 200, {'Content-Type': 'text/plain; charset=utf-8'}
if path == "projects":
# Get projects
return render_template("projects.ascii",header=get_header(),projects=get_projects()), 200, {'Content-Type': 'text/plain; charset=utf-8'}
if path == "donate":
# Get donation info
return render_template("donate.ascii",header=get_header(),
HNS=getAddress("HNS"), BTC=getAddress("BTC"),
SOL=getAddress("SOL"), ETH=getAddress("ETH")
), 200, {'Content-Type': 'text/plain; charset=utf-8'}
if path == "donate/more":
coinList = os.listdir(".well-known/wallets")
coinList = [file for file in coinList if file[0] != "."]
coinList.sort()
return render_template("donate_more.ascii",header=get_header(),
coins=coinList
), 200, {'Content-Type': 'text/plain; charset=utf-8'}
# For other donation pages, fall back to ascii if it exists
if path.startswith("donate/"):
coin = path.split("/")[1]
address = getAddress(coin)
if address != "":
return render_template("donate_coin.ascii",header=get_header(),coin=coin.upper(),address=address), 200, {'Content-Type': 'text/plain; charset=utf-8'}
if path == "tools":
tools = get_tools_data()
return render_template("tools.ascii",header=get_header(),tools=tools), 200, {'Content-Type': 'text/plain; charset=utf-8'}
if os.path.exists(f"templates/{path}.ascii"):
return render_template(f"{path}.ascii",header=get_header()), 200, {'Content-Type': 'text/plain; charset=utf-8'}
# Fallback to html if it exists
if os.path.exists(f"templates/{path}.html"):
return render_template(f"{path}.html")
return error_response(request)

View File

@@ -25,13 +25,13 @@
}, },
{ {
"name": "Zellij", "name": "Zellij",
"type": "terminal", "type": "Terminal Tools",
"url": "https://zellij.dev/", "url": "https://zellij.dev/",
"description": "A terminal workspace and multiplexer" "description": "A terminal workspace and multiplexer"
}, },
{ {
"name": "Fx", "name": "Fx",
"type": "terminal", "type": "Terminal Tools",
"url": "https://fx.wtf/", "url": "https://fx.wtf/",
"description": "A command-line JSON viewer and processor", "description": "A command-line JSON viewer and processor",
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/4.js\" id=\"asciicast-4\" async=\"true\"></script>", "demo": "<script src=\"https://asciinema.c.woodburn.au/a/4.js\" id=\"asciicast-4\" async=\"true\"></script>",
@@ -39,7 +39,7 @@
}, },
{ {
"name": "Zoxide", "name": "Zoxide",
"type": "terminal", "type": "Terminal Tools",
"url": "https://github.com/ajeetdsouza/zoxide", "url": "https://github.com/ajeetdsouza/zoxide",
"description": "cd but with fuzzy matching and other cool features", "description": "cd but with fuzzy matching and other cool features",
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/5.js\" id=\"asciicast-5\" async=\"true\"></script>", "demo": "<script src=\"https://asciinema.c.woodburn.au/a/5.js\" id=\"asciicast-5\" async=\"true\"></script>",
@@ -47,7 +47,7 @@
}, },
{ {
"name": "Atuin", "name": "Atuin",
"type": "terminal", "type": "Terminal Tools",
"url": "https://atuin.sh/", "url": "https://atuin.sh/",
"description": "A next-generation shell history manager", "description": "A next-generation shell history manager",
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/6.js\" id=\"asciicast-6\" async=\"true\"></script>", "demo": "<script src=\"https://asciinema.c.woodburn.au/a/6.js\" id=\"asciicast-6\" async=\"true\"></script>",
@@ -55,7 +55,7 @@
}, },
{ {
"name": "Tmate", "name": "Tmate",
"type": "terminal", "type": "Terminal Tools",
"url": "https://tmate.io/", "url": "https://tmate.io/",
"description": "Instant terminal sharing", "description": "Instant terminal sharing",
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/7.js\" id=\"asciicast-7\" async=\"true\"></script>", "demo": "<script src=\"https://asciinema.c.woodburn.au/a/7.js\" id=\"asciicast-7\" async=\"true\"></script>",
@@ -63,7 +63,7 @@
}, },
{ {
"name": "Eza", "name": "Eza",
"type": "terminal", "type": "Terminal Tools",
"url": "https://eza.rocks/", "url": "https://eza.rocks/",
"description": "A modern replacement for 'ls'", "description": "A modern replacement for 'ls'",
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/8.js\" id=\"asciicast-8\" async=\"true\"></script>", "demo": "<script src=\"https://asciinema.c.woodburn.au/a/8.js\" id=\"asciicast-8\" async=\"true\"></script>",
@@ -71,7 +71,7 @@
}, },
{ {
"name": "Bat", "name": "Bat",
"type": "terminal", "type": "Terminal Tools",
"url": "https://github.com/sharkdp/bat", "url": "https://github.com/sharkdp/bat",
"description": "A cat clone with syntax highlighting and Git integration", "description": "A cat clone with syntax highlighting and Git integration",
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/9.js\" id=\"asciicast-9\" async=\"true\"></script>", "demo": "<script src=\"https://asciinema.c.woodburn.au/a/9.js\" id=\"asciicast-9\" async=\"true\"></script>",
@@ -79,7 +79,7 @@
}, },
{ {
"name": "Oh My Zsh", "name": "Oh My Zsh",
"type": "terminal", "type": "Terminal Tools",
"url": "https://ohmyz.sh/", "url": "https://ohmyz.sh/",
"description": "A delightful community-driven framework for managing your Zsh configuration" "description": "A delightful community-driven framework for managing your Zsh configuration"
}, },

View File

@@ -25,7 +25,8 @@ from blueprints.wellknown import wk_bp
from blueprints.api import api_bp from blueprints.api import api_bp
from blueprints.podcast import podcast_bp from blueprints.podcast import podcast_bp
from blueprints.acme import acme_bp from blueprints.acme import acme_bp
from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getGitCommit, isDev, getHandshakeScript, get_tools_data from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getHandshakeScript, get_tools_data
from curl import curl_response
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
@@ -242,14 +243,7 @@ def index():
if request.args.get("load"): if request.args.get("load"):
loaded = False loaded = False
if isCurl(request): if isCurl(request):
return jsonify( return curl_response(request)
{
"message": "Welcome to Nathan.Woodburn/! This is a personal website. For more information, visit https://nathan.woodburn.au",
"ip": getClientIP(request),
"dev": isDev(request.host),
"version": getGitCommit()
}
)
if not loaded and not isCrawler(request): if not loaded and not isCrawler(request):
# Set cookie # Set cookie
@@ -401,6 +395,9 @@ def index():
# region Donate # region Donate
@app.route("/donate") @app.route("/donate")
def donate(): def donate():
if isCurl(request):
return curl_response(request)
coinList = os.listdir(".well-known/wallets") coinList = os.listdir(".well-known/wallets")
coinList = [file for file in coinList if file[0] != "."] coinList = [file for file in coinList if file[0] != "."]
coinList.sort() coinList.sort()
@@ -688,6 +685,8 @@ def resume_pdf():
@app.route("/tools") @app.route("/tools")
def tools(): def tools():
if isCurl(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
@@ -702,6 +701,10 @@ 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)
# If curl request, return curl response
if isCurl(request):
return curl_response(request)
if path in REDIRECT_ROUTES: if path in REDIRECT_ROUTES:
return redirect(REDIRECT_ROUTES[path], code=302) return redirect(REDIRECT_ROUTES[path], code=302)

14
templates/contact.ascii Normal file
View File

@@ -0,0 +1,14 @@
{{header}}
───────────────────────────────────────────────
 CONTACT ME 
────────────
Here are my socials — Im most active on Discord 💬
- Twitter: https://twitter.com/woodburn_nathan
- GitHub: https://github.com/Nathanwoodburn
- Email: mailto:about@nathan.woodburn.au
- Discord: https://l.woodburn.au/discord
- Mastodon: https://mastodon.woodburn.au/@nathanwoodburn
- YouTube: https://www.youtube.com/@nathanjwoodburn

25
templates/donate.ascii Normal file
View File

@@ -0,0 +1,25 @@
{{header}}
───────────────────────────────────────────────
 DONATE 
────────
If youd like to support my work 💙
- PayPal: [https://paypal.me/nathanwoodburn]
- GitHub: [https://github.com/sponsors/Nathanwoodburn]
- Stripe: [https://donate.stripe.com/8wM6pv0VD08Xe408ww]
HNS: nathan.woodburn
{{ HNS }}
BTC: thinbadger6@primal.net
{{ BTC }}
SOL: woodburn.sol
{{ SOL }}
ETH: woodburn.au
{{ ETH }}
More donation options → [/donate/more]

View File

@@ -0,0 +1,10 @@
{{header}}
───────────────────────────────────────────────
 DONATE 
────────
Here is my {{ coin }} address if you'd like to send a donation 💙
{{ address }}
Thank you for your support! 🙏

View File

@@ -0,0 +1,13 @@
{{header}}
───────────────────────────────────────────────
 DONATE 
────────
Here is a list of additional cryptocurrencies and donation methods 💙
For each coin below, you can get the address from /donate/<coin>
{% for coin in coins %}{% if loop.index0 % 4 == 0 and loop.index0 != 0 %}
{% endif %}{{ coin }}{% if not loop.last %}, {% endif %}{% endfor %}
Thank you for your support! 🙏

25
templates/favicon.ascii Normal file
View File

@@ -0,0 +1,25 @@
▒▒▒ ▓▓▓
▒░░░░▒▓ ▓▓▓▓▓▓▓
▒░░░░░░▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓
▒░░░░░▒▒▒▒▒▒▒ ▓▓▒▓▓▓▓▓▓▓▓▓▓
▒░░░▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒░░▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒ ▒▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒ ▒▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒ ▒▒▒▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▒▒ ▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▒▒▒▒ ▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓█
▓▓▓▓ ▓▓▓█

11
templates/header.ascii Normal file
View File

@@ -0,0 +1,11 @@
─────────────────────────────────────────────────────
 . . , . . . .. / 
 |\ | _.-+-|_ _.._ | | _ _ _||_ . .._.._ / 
 | \|(_] | [ )(_][ ) * |/\|(_)(_)(_][_)(_|[ [ )/ 
─────────────────────────────────────────────────────
Home [/]
Contact [/contact]
Projects [/projects]
Donate [/donate]

43
templates/index.ascii Normal file
View File

@@ -0,0 +1,43 @@
─────────────────────────────────────────────────────
 . . , . . . .. / 
 |\ | _.-+-|_ _.._ | | _ _ _||_ . .._.._ / 
 | \|(_] | [ )(_][ ) * |/\|(_)(_)(_][_)(_|[ [ )/ 
─────────────────────────────────────────────────────
Home [/]
Contact [/contact]
Projects [/projects]
Donate [/donate]
API [/api/v1/]
───────────────────────────────────────────────
 ABOUT ME 
──────────
Hi, I'm Nathan Woodburn from Canberra, Australia.
I've been homeschooled through Year 12 and am now studying a
Bachelor of Computer Science.
I love building random projects, so this site is always evolving.
I'm also one of the founders of Handshake AU [https://hns.au],
working to grow Handshake adoption across Australia.
I'm currently working on: {{ repo | safe }}
───────────────────────────────────────────────
 SKILLS 
────────
- Linux servers & CLI
- DNS & DNSSEC
- NGINX web servers
- Programming:
- Python 3
- C#
- Java
- Bash
Served to: {{ ip }}
───────────────────────────────────────────────

7
templates/projects.ascii Normal file
View File

@@ -0,0 +1,7 @@
{{header}}
───────────────────────────────────────────────
 RECENT PROJECTS 
─────────────────
{{projects}}

20
templates/tools.ascii Normal file
View File

@@ -0,0 +1,20 @@
{{header}}
───────────────────────────────────────────────
 Tools 
────────────
Here are some of the tools I use regularly — most of them are open source! 🛠️
{% for type, tools_in_type in tools | groupby('type') %}
{{type}}
{% for tool in tools_in_type %}
{{tool.name}}
{{tool.description}}
Website: {{tool.url}}
{% if tool.demo_url %}Demo: {{tool.demo_url}}{% endif %}
{% endfor %}
───────────────────────────────────────────────
{% endfor %}

View File

@@ -14,4 +14,7 @@ GET http://127.0.0.1:5000/api/v1/message
HTTP 200 HTTP 200
GET http://127.0.0.1:5000/api/v1/project GET http://127.0.0.1:5000/api/v1/project
HTTP 200 HTTP 200
GET http://127.0.0.1:5000/api/v1/tools
HTTP 200
[Asserts]
jsonpath "$.tools" count > 5