Compare commits
42 Commits
feat/prelo
...
53fe364fec
| Author | SHA1 | Date | |
|---|---|---|---|
|
53fe364fec
|
|||
|
2d0310db9f
|
|||
|
f783b2528c
|
|||
|
b089b8c0a8
|
|||
|
8f774ba8f0
|
|||
|
f4f5f47ee7
|
|||
|
16f17a9486
|
|||
|
72483674f6
|
|||
|
b69c7f381b
|
|||
|
d7d4dbed8b
|
|||
|
2437b19836
|
|||
|
abd23e0eb8
|
|||
|
57a4b977ec
|
|||
|
7f591e2724
|
|||
|
3d5c16f9cb
|
|||
|
fdb5f84c92
|
|||
|
eaf363ee27
|
|||
|
0ea9db3473
|
|||
|
8d6acca5e9
|
|||
|
bfc1f0839a
|
|||
|
258061c64d
|
|||
|
399ac5f0da
|
|||
|
74362de02a
|
|||
|
9f7b93b8a1
|
|||
|
665921d046
|
|||
|
84cf772273
|
|||
|
22cd49a012
|
|||
|
00d035a0e8
|
|||
|
fc56cafab8
|
|||
|
eee87e6ca7
|
|||
|
09852f19b6
|
|||
|
8b464cd89d
|
|||
|
98597768f3
|
|||
|
08f80ddb5c
|
|||
|
33fd8136a7
|
|||
|
b2943bfeac
|
|||
|
c3c7c86a66
|
|||
|
8563a6857f
|
|||
|
1650d25d0f
|
|||
|
f4ee2297a7
|
|||
|
35ced02977
|
|||
|
45709632d5
|
@@ -1,5 +1,6 @@
|
||||
FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder
|
||||
|
||||
RUN apk add curl
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt /app
|
||||
@@ -14,4 +15,4 @@ COPY . /app
|
||||
ENTRYPOINT ["python3"]
|
||||
CMD ["main.py"]
|
||||
|
||||
FROM builder AS dev-envs
|
||||
FROM builder AS dev-envs
|
||||
|
||||
36
blueprints/acme.py
Normal file
36
blueprints/acme.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from flask import Blueprint, request
|
||||
import os
|
||||
from cloudflare import Cloudflare
|
||||
from tools import json_response
|
||||
|
||||
acme_bp = Blueprint('acme', __name__)
|
||||
|
||||
|
||||
@acme_bp.route("/hnsdoh-acme", methods=["POST"])
|
||||
def post():
|
||||
# Get the TXT record from the request
|
||||
if not request.is_json or not request.json:
|
||||
return json_response(request, "415 Unsupported Media Type", 415)
|
||||
if "txt" not in request.json or "auth" not in request.json:
|
||||
return json_response(request, "400 Bad Request", 400)
|
||||
|
||||
txt = request.json["txt"]
|
||||
auth = request.json["auth"]
|
||||
if auth != os.getenv("CF_AUTH"):
|
||||
return json_response(request, "401 Unauthorized", 401)
|
||||
|
||||
cf = Cloudflare(api_token=os.getenv("CF_TOKEN"))
|
||||
zone = cf.zones.list(name="hnsdoh.com").to_dict()
|
||||
zone_id = zone["result"][0]["id"] # type: ignore
|
||||
existing_records = cf.dns.records.list(
|
||||
zone_id=zone_id, type="TXT", name="_acme-challenge.hnsdoh.com" # type: ignore
|
||||
).to_dict()
|
||||
record_id = existing_records["result"][0]["id"] # type: ignore
|
||||
cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id)
|
||||
cf.dns.records.create(
|
||||
zone_id=zone_id,
|
||||
type="TXT",
|
||||
name="_acme-challenge",
|
||||
content=txt,
|
||||
)
|
||||
return json_response(request, "Success", 200)
|
||||
293
blueprints/api.py
Normal file
293
blueprints/api.py
Normal file
@@ -0,0 +1,293 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
import os
|
||||
import datetime
|
||||
import requests
|
||||
import re
|
||||
from mail import sendEmail
|
||||
from tools import getClientIP, getGitCommit, json_response, parse_date, get_tools_data
|
||||
from blueprints.sol import sol_bp
|
||||
from dateutil import parser as date_parser
|
||||
|
||||
# Constants
|
||||
HTTP_OK = 200
|
||||
HTTP_BAD_REQUEST = 400
|
||||
HTTP_UNAUTHORIZED = 401
|
||||
HTTP_NOT_FOUND = 404
|
||||
HTTP_UNSUPPORTED_MEDIA = 415
|
||||
HTTP_SERVER_ERROR = 500
|
||||
|
||||
api_bp = Blueprint('api', __name__)
|
||||
# Register solana blueprint
|
||||
api_bp.register_blueprint(sol_bp)
|
||||
|
||||
# Load configuration
|
||||
NC_CONFIG = requests.get(
|
||||
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json"
|
||||
).json()
|
||||
|
||||
if 'time-zone' not in NC_CONFIG:
|
||||
NC_CONFIG['time-zone'] = 10
|
||||
|
||||
|
||||
@api_bp.route("/")
|
||||
@api_bp.route("/help")
|
||||
def help():
|
||||
"""Provide API documentation and help."""
|
||||
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",
|
||||
"/page_date?url=URL&verbose=BOOL": "Get the last modified date of a webpage (verbose is optional, default false)",
|
||||
"/status": "Just check if the site is up",
|
||||
"/ping": "Just check if the site is up",
|
||||
"/help": "Get this help message"
|
||||
},
|
||||
"base_url": "/api/v1",
|
||||
"version": getGitCommit(),
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK
|
||||
})
|
||||
|
||||
@api_bp.route("/status")
|
||||
@api_bp.route("/ping")
|
||||
def status():
|
||||
return json_response(request, "200 OK", HTTP_OK)
|
||||
|
||||
@api_bp.route("/version")
|
||||
def version():
|
||||
"""Get the current version of the website."""
|
||||
return jsonify({"version": getGitCommit()})
|
||||
|
||||
|
||||
@api_bp.route("/time")
|
||||
def time():
|
||||
"""Get the current time in the configured timezone."""
|
||||
timezone_offset = datetime.timedelta(hours=NC_CONFIG["time-zone"])
|
||||
timezone = datetime.timezone(offset=timezone_offset)
|
||||
current_time = datetime.datetime.now(tz=timezone)
|
||||
return jsonify({
|
||||
"timestring": current_time.strftime("%A, %B %d, %Y %I:%M %p"),
|
||||
"timestamp": current_time.timestamp(),
|
||||
"timezone": NC_CONFIG["time-zone"],
|
||||
"timeISO": current_time.isoformat(),
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK
|
||||
})
|
||||
|
||||
|
||||
@api_bp.route("/timezone")
|
||||
def timezone():
|
||||
"""Get the current timezone setting."""
|
||||
return jsonify({
|
||||
"timezone": NC_CONFIG["time-zone"],
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK
|
||||
})
|
||||
|
||||
|
||||
@api_bp.route("/message")
|
||||
def message():
|
||||
"""Get the message from the configuration."""
|
||||
return jsonify({
|
||||
"message": NC_CONFIG["message"],
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK
|
||||
})
|
||||
|
||||
|
||||
@api_bp.route("/ip")
|
||||
def ip():
|
||||
"""Get the client's IP address."""
|
||||
return jsonify({
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK
|
||||
})
|
||||
|
||||
|
||||
@api_bp.route("/email", methods=["POST"])
|
||||
def email_post():
|
||||
"""Send an email via the API (requires API key)."""
|
||||
# Verify json
|
||||
if not request.is_json:
|
||||
return json_response(request, "415 Unsupported Media Type", HTTP_UNSUPPORTED_MEDIA)
|
||||
|
||||
# Check if api key sent
|
||||
data = request.json
|
||||
if not data:
|
||||
return json_response(request, "400 Bad Request", HTTP_BAD_REQUEST)
|
||||
|
||||
if "key" not in data:
|
||||
return json_response(request, "400 Bad Request 'key' missing", HTTP_BAD_REQUEST)
|
||||
|
||||
if data["key"] != os.getenv("EMAIL_KEY"):
|
||||
return json_response(request, "401 Unauthorized", HTTP_UNAUTHORIZED)
|
||||
|
||||
# TODO: Add client info to email
|
||||
return sendEmail(data)
|
||||
|
||||
|
||||
@api_bp.route("/project")
|
||||
def project():
|
||||
"""Get information about the current git project."""
|
||||
gitinfo = {
|
||||
"website": None,
|
||||
}
|
||||
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"]
|
||||
gitinfo["name"] = repo_name
|
||||
gitinfo["description"] = repo_description
|
||||
gitinfo["url"] = git["repo"]["html_url"]
|
||||
if "website" in git["repo"]:
|
||||
gitinfo["website"] = git["repo"]["website"]
|
||||
except Exception as e:
|
||||
print(f"Error getting git data: {e}")
|
||||
return json_response(request, "500 Internal Server Error", HTTP_SERVER_ERROR)
|
||||
|
||||
return jsonify({
|
||||
"repo_name": repo_name,
|
||||
"repo_description": repo_description,
|
||||
"repo": gitinfo,
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK
|
||||
})
|
||||
|
||||
@api_bp.route("/tools")
|
||||
def tools():
|
||||
"""Get a list of tools used by Nathan Woodburn."""
|
||||
try:
|
||||
tools = get_tools_data()
|
||||
except Exception as e:
|
||||
print(f"Error getting tools data: {e}")
|
||||
return json_response(request, "500 Internal Server Error", HTTP_SERVER_ERROR)
|
||||
|
||||
# Remove demo and move demo_url to demo
|
||||
for tool in tools:
|
||||
if "demo_url" in tool:
|
||||
tool["demo"] = tool.pop("demo_url")
|
||||
|
||||
return json_response(request, {"tools": tools}, HTTP_OK)
|
||||
|
||||
@api_bp.route("/page_date")
|
||||
def page_date():
|
||||
url = request.args.get("url")
|
||||
if not url:
|
||||
return json_response(request, "400 Bad Request 'url' missing", HTTP_BAD_REQUEST)
|
||||
|
||||
verbose = request.args.get("verbose", "").lower() in ["true", "1", "yes", "y"]
|
||||
|
||||
if not url.startswith(("https://", "http://")):
|
||||
return json_response(request, "400 Bad Request 'url' invalid", HTTP_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
r = requests.get(url, timeout=5)
|
||||
r.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return json_response(request, f"400 Bad Request 'url' unreachable: {e}", HTTP_BAD_REQUEST)
|
||||
|
||||
page_text = r.text
|
||||
|
||||
# Remove ordinal suffixes globally
|
||||
page_text = re.sub(r'(\d+)(st|nd|rd|th)', r'\1', page_text, flags=re.IGNORECASE)
|
||||
# Remove HTML comments
|
||||
page_text = re.sub(r'<!--.*?-->', '', page_text, flags=re.DOTALL)
|
||||
|
||||
date_patterns = [
|
||||
r'(\d{4})[/-](\d{1,2})[/-](\d{1,2})', # YYYY-MM-DD
|
||||
r'(\d{1,2})[/-](\d{1,2})[/-](\d{4})', # DD-MM-YYYY
|
||||
r'(?:Last updated:|Updated:|Updated last:)?\s*(\d{1,2})\s+([A-Za-z]{3,9})[, ]?\s*(\d{4})', # DD Month YYYY
|
||||
r'(?:\b\w+\b\s+){0,3}([A-Za-z]{3,9})\s+(\d{1,2}),?\s*(\d{4})', # Month DD, YYYY with optional words
|
||||
r'\b(\d{4})(\d{2})(\d{2})\b', # YYYYMMDD
|
||||
r'(?:Last updated:|Updated:|Last update)?\s*([A-Za-z]{3,9})\s+(\d{4})', # Month YYYY only
|
||||
]
|
||||
|
||||
|
||||
|
||||
# Structured data patterns
|
||||
json_date_patterns = {
|
||||
r'"datePublished"\s*:\s*"([^"]+)"': "published",
|
||||
r'"dateModified"\s*:\s*"([^"]+)"': "modified",
|
||||
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:published_time"\s+content\s*=\s*"([^"]+)"': "published",
|
||||
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:modified_time"\s+content\s*=\s*"([^"]+)"': "modified",
|
||||
r'<time\s+datetime\s*=\s*"([^"]+)"': "published"
|
||||
}
|
||||
|
||||
found_dates = []
|
||||
|
||||
# Extract content dates
|
||||
for idx, pattern in enumerate(date_patterns):
|
||||
for match in re.findall(pattern, page_text):
|
||||
if not match:
|
||||
continue
|
||||
groups = match[-3:] # last three elements
|
||||
found_dates.append([groups, idx, "content"])
|
||||
|
||||
# Extract structured data dates
|
||||
for pattern, date_type in json_date_patterns.items():
|
||||
for match in re.findall(pattern, page_text):
|
||||
try:
|
||||
dt = date_parser.isoparse(match)
|
||||
formatted_date = dt.strftime('%Y-%m-%d')
|
||||
found_dates.append([[formatted_date], -1, date_type])
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
if not found_dates:
|
||||
return json_response(request, "Date not found on page", HTTP_BAD_REQUEST)
|
||||
|
||||
today = datetime.date.today()
|
||||
tolerance_date = today + datetime.timedelta(days=1) # Allow for slight future dates (e.g., time zones)
|
||||
# When processing dates
|
||||
processed_dates = []
|
||||
for date_groups, pattern_format, date_type in found_dates:
|
||||
if pattern_format == -1:
|
||||
# Already formatted date
|
||||
try:
|
||||
dt = datetime.datetime.strptime(date_groups[0], "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
parsed_date = parse_date(date_groups)
|
||||
if not parsed_date:
|
||||
continue
|
||||
dt = datetime.datetime.strptime(parsed_date, "%Y-%m-%d").date()
|
||||
|
||||
# Only keep dates in the past (with tolerance)
|
||||
if dt <= tolerance_date:
|
||||
date_obj = {"date": dt.strftime("%Y-%m-%d"), "type": date_type}
|
||||
if verbose:
|
||||
if pattern_format == -1:
|
||||
date_obj.update({"source": "metadata", "pattern_used": pattern_format, "raw": date_groups[0]})
|
||||
else:
|
||||
date_obj.update({"source": "content", "pattern_used": pattern_format, "raw": " ".join(date_groups)})
|
||||
processed_dates.append(date_obj)
|
||||
|
||||
if not processed_dates:
|
||||
if verbose:
|
||||
return jsonify({
|
||||
"message": "No valid dates found on page",
|
||||
"found_dates": found_dates,
|
||||
"processed_dates": processed_dates
|
||||
}), HTTP_BAD_REQUEST
|
||||
return json_response(request, "No valid dates found on page", HTTP_BAD_REQUEST)
|
||||
# Sort dates and return latest
|
||||
processed_dates.sort(key=lambda x: x["date"])
|
||||
latest = processed_dates[-1]
|
||||
|
||||
response = {"latest": latest["date"], "type": latest["type"]}
|
||||
if verbose:
|
||||
response["dates"] = processed_dates
|
||||
|
||||
return json_response(request, response, HTTP_OK)
|
||||
@@ -1,37 +1,42 @@
|
||||
import os
|
||||
from flask import render_template
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, render_template, request, jsonify
|
||||
import markdown
|
||||
from markdown.extensions.codehilite import CodeHiliteExtension
|
||||
from markdown.extensions.fenced_code import FencedCodeExtension
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from tools import isCurl, getClientIP, getHandshakeScript
|
||||
|
||||
blog_bp = Blueprint('blog', __name__)
|
||||
|
||||
|
||||
def list_blog_page_files():
|
||||
def list_page_files():
|
||||
blog_pages = os.listdir("data/blog")
|
||||
# Sort pages by modified time, newest first
|
||||
blog_pages.sort(
|
||||
key=lambda x: os.path.getmtime(os.path.join("data/blog", x)), reverse=True)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
def render_blog_page(date,handshake_scripts=None):
|
||||
def render_page(date, handshake_scripts=None):
|
||||
# Convert md to html
|
||||
if not os.path.exists(f"data/blog/{date}.md"):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
|
||||
with open(f"data/blog/{date}.md", "r") as f:
|
||||
content = f.read()
|
||||
# Get the title from the file name
|
||||
title = date.removesuffix(".md").replace("_", " ")
|
||||
# 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
|
||||
content = content.replace('<a href="', '<a target="_blank" href="')
|
||||
|
||||
content = content.replace("<h4", "<h4 style='margin-bottom:0px;'")
|
||||
|
||||
content = fix_numbered_lists(content)
|
||||
|
||||
return render_template(
|
||||
@@ -48,7 +53,7 @@ def fix_numbered_lists(html):
|
||||
# Find the <p> tag containing numbered steps
|
||||
paragraphs = soup.find_all('p')
|
||||
for p in paragraphs:
|
||||
content = p.decode_contents()
|
||||
content = p.decode_contents() # type: ignore
|
||||
|
||||
# Check for likely numbered step structure
|
||||
if re.search(r'1\.\s', content):
|
||||
@@ -65,7 +70,8 @@ def fix_numbered_lists(html):
|
||||
for i in range(0, len(steps), 2):
|
||||
if i+1 < len(steps):
|
||||
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
|
||||
ol_html = "<ol>\n" + "\n".join(ol_items) + "\n</ol>"
|
||||
@@ -81,14 +87,14 @@ def fix_numbered_lists(html):
|
||||
return str(soup)
|
||||
|
||||
|
||||
def render_blog_home(handshake_scripts=None):
|
||||
def render_home(handshake_scripts: str | None = None):
|
||||
# Get a list of pages
|
||||
blog_pages = list_blog_page_files()
|
||||
blog_pages = list_page_files()
|
||||
# Create a html list of pages
|
||||
blog_pages = [
|
||||
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>"""
|
||||
for page in blog_pages
|
||||
]
|
||||
@@ -99,4 +105,60 @@ def render_blog_home(handshake_scripts=None):
|
||||
"blog/blog.html",
|
||||
blogs=blog_pages,
|
||||
handshake_scripts=handshake_scripts,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@blog_bp.route("/")
|
||||
def index():
|
||||
if not isCurl(request):
|
||||
return render_home(handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
# Get a list of pages
|
||||
blog_pages = list_page_files()
|
||||
# Create a html list of pages
|
||||
blog_pages = [
|
||||
{"name": page.replace("_", " "), "url": f"/blog/{page}", "download": f"/blog/{page}.md"} for page in blog_pages
|
||||
]
|
||||
|
||||
# Render the template
|
||||
return jsonify({
|
||||
"status": 200,
|
||||
"message": "Check out my various blog postsa",
|
||||
"ip": getClientIP(request),
|
||||
"blogs": blog_pages
|
||||
}), 200
|
||||
|
||||
|
||||
@blog_bp.route("/<path:path>")
|
||||
def path(path):
|
||||
if not isCurl(request):
|
||||
return render_page(path, handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
# Convert md to html
|
||||
if not os.path.exists(f"data/blog/{path}.md"):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
with open(f"data/blog/{path}.md", "r") as f:
|
||||
content = f.read()
|
||||
# Get the title from the file name
|
||||
title = path.replace("_", " ")
|
||||
return jsonify({
|
||||
"status": 200,
|
||||
"message": f"Blog post: {title}",
|
||||
"ip": getClientIP(request),
|
||||
"title": title,
|
||||
"content": content,
|
||||
"download": f"/blog/{path}.md"
|
||||
}), 200
|
||||
|
||||
|
||||
@blog_bp.route("/<path:path>.md")
|
||||
def path_md(path):
|
||||
if not os.path.exists(f"data/blog/{path}.md"):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
with open(f"data/blog/{path}.md", "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Return the raw markdown file
|
||||
return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
||||
110
blueprints/now.py
Normal file
110
blueprints/now.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from flask import Blueprint, render_template, make_response, request, jsonify
|
||||
import datetime
|
||||
import os
|
||||
from tools import getHandshakeScript
|
||||
|
||||
# Create blueprint
|
||||
now_bp = Blueprint('now', __name__)
|
||||
|
||||
|
||||
def list_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_dates():
|
||||
now_pages = list_page_files()
|
||||
now_dates = [page.split(".")[0] for page in now_pages]
|
||||
return now_dates
|
||||
|
||||
|
||||
def get_latest_date(formatted=False):
|
||||
if formatted:
|
||||
date = list_dates()[0]
|
||||
date = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||
date = date.strftime("%A, %B %d, %Y")
|
||||
return date
|
||||
return list_dates()[0]
|
||||
|
||||
|
||||
def render_latest(handshake_scripts=None):
|
||||
now_page = list_dates()[0]
|
||||
return render(now_page, handshake_scripts=handshake_scripts)
|
||||
|
||||
|
||||
def render(date, handshake_scripts=None):
|
||||
# If the date is not available, render the latest page
|
||||
if date is None:
|
||||
return render_latest(handshake_scripts=handshake_scripts)
|
||||
# Remove .html
|
||||
date = date.removesuffix(".html")
|
||||
|
||||
if date not in list_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 index():
|
||||
return render_latest(handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
|
||||
@now_bp.route("/<path:path>")
|
||||
def path(path):
|
||||
return render(path, handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
|
||||
@now_bp.route("/old")
|
||||
@now_bp.route("/old/")
|
||||
def old():
|
||||
now_dates = list_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_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=getHandshakeScript(request.host), now_pages=html
|
||||
)
|
||||
|
||||
|
||||
@now_bp.route("/now.rss")
|
||||
@now_bp.route("/now.xml")
|
||||
@now_bp.route("/rss.xml")
|
||||
def rss():
|
||||
host = "https://" + request.host
|
||||
if ":" in request.host:
|
||||
host = "http://" + request.host
|
||||
# Generate RSS feed
|
||||
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" />'
|
||||
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 json():
|
||||
now_pages = list_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)
|
||||
59
blueprints/podcast.py
Normal file
59
blueprints/podcast.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from flask import Blueprint, make_response, request
|
||||
from tools import error_response
|
||||
import requests
|
||||
|
||||
podcast_bp = Blueprint('podcast', __name__)
|
||||
|
||||
@podcast_bp.route("/ID1")
|
||||
def index():
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@podcast_bp.route("/ID1/")
|
||||
def contents():
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1/")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@podcast_bp.route("/ID1/<path:path>")
|
||||
def path(path):
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1/" + path)
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@podcast_bp.route("/ID1.xml")
|
||||
def xml():
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1.xml")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@podcast_bp.route("/podsync.opml")
|
||||
def podsync():
|
||||
req = requests.get("https://podcasts.c.woodburn.au/podsync.opml")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
125
blueprints/sol.py
Normal file
125
blueprints/sol.py
Normal 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
|
||||
9
blueprints/template.py
Normal file
9
blueprints/template.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from flask import Blueprint, request
|
||||
from tools import json_response
|
||||
|
||||
template_bp = Blueprint('template', __name__)
|
||||
|
||||
|
||||
@template_bp.route("/")
|
||||
def index():
|
||||
return json_response(request, "Success", 200)
|
||||
62
blueprints/wellknown.py
Normal file
62
blueprints/wellknown.py
Normal 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 index(path):
|
||||
return send_from_directory(".well-known", path)
|
||||
|
||||
|
||||
@wk_bp.route("/wallets/<path:path>")
|
||||
def wallets(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 nostr():
|
||||
# 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 xrp():
|
||||
# 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
|
||||
123
curl.py
Normal file
123
curl.py
Normal 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"[1m{repo_name}[0m - {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"""[1m{project['name']}[0m - {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)
|
||||
53
data/blog/Software_I_Use.md
Normal file
53
data/blog/Software_I_Use.md
Normal file
@@ -0,0 +1,53 @@
|
||||
G'day,
|
||||
Just thought it might be useful to write down some of the software I use regularly. I've no clue if you'll find any useful :)
|
||||
|
||||
For a more complete list, check out [/tools](/tools)
|
||||
|
||||
<br>
|
||||
## Overview
|
||||
OS: Arch Linux | Because it is quick to update and has all the latest tools I can play with
|
||||
DE: Hyprland | Feel free to check out my dotfiles if you're interested
|
||||
Shell: ZSH
|
||||
|
||||
<br>
|
||||
## Desktop Applications
|
||||
|
||||
[Obsidian](https://obsidian.md/) | Note taking app that stores everything in Markdown files
|
||||
[Alacritty](https://alacritty.org/) | Terminal emulator
|
||||
[Brave](https://brave.com/) | Browser with ad blocker built in
|
||||
[VSCode](https://code.visualstudio.com/) | Yeah its heavy but I'm used to it
|
||||
|
||||
<br>
|
||||
## Terminal Tools
|
||||
|
||||
[Zellij](https://zellij.dev/) | Easy to use terminal multiplexer
|
||||
[Fx](https://fx.wtf/) | JSON parser with pretty colours. Similar to jq
|
||||
[Zoxide](https://github.com/ajeetdsouza/zoxide) | cd but with fuzzy matching and other cool features
|
||||
[Atuin](https://atuin.sh/) | Terminal history with fuzzy search
|
||||
[Tmate](https://tmate.io/) | Terminal sharing. Useful when troubleshooting isses for remote users
|
||||
[Eza](https://eza.rocks/) | Like ls but pretty
|
||||
[Tre](https://github.com/dduan/tre) | Like tree but pretty
|
||||
[Bat](https://github.com/sharkdp/bat) | Like cat but pretty. Syntax highlighting, line numbers, search, git integration and more
|
||||
[Oh My ZSH](https://ohmyz.sh/) | Shell customization and plugins
|
||||
|
||||
<br>
|
||||
## Server Management
|
||||
|
||||
[Proxmox](https://proxmox.com/en/) | Virtualization manager for my baremetal server
|
||||
[Portainer](https://www.portainer.io/) | Docker container manager
|
||||
[Coolify](https://coolify.io/) | Open source alternative to heroku. I use it to host a lot of different services
|
||||
[Opnsense](https://opnsense.org/) | Firewall and router
|
||||
[Nginx Proxy Manager](https://nginxproxymanager.com/) | Reverse proxy manager with a nice UI
|
||||
[Tailscale](https://tailscale.com/) | VPN to let me access my network from anywhere
|
||||
|
||||
<br>
|
||||
## Self-Hosting Services
|
||||
[Authentik](https://goauthentik.io/) | Identity provider for single sign on
|
||||
[Gitea](https://gitea.io/) | Git hosting service
|
||||
[Nextcloud](https://nextcloud.com/) | Think Dropbox but self hosted
|
||||
[Umami](https://umami.is/) | Self hosted web analytics
|
||||
[Uptime Kuma](https://uptime.kuma.pet/) | Self hosted status page and monitoring tool
|
||||
[PhotoPrism](https://photoprism.app/) | Self hosted photo management tool
|
||||
[FreeScout](https://freescout.net/) | Self hosted email dashboard
|
||||
[Transfer.sh](https://upload.woodburn.au/) | Self hosted file sharing service
|
||||
|
||||
BIN
data/resume.pdf
BIN
data/resume.pdf
Binary file not shown.
170
data/tools.json
Normal file
170
data/tools.json
Normal file
@@ -0,0 +1,170 @@
|
||||
[
|
||||
{
|
||||
"name":"Obsidian",
|
||||
"type":"Desktop Applications",
|
||||
"url":"https://obsidian.md/",
|
||||
"description":"Note taking app that stores everything in Markdown files"
|
||||
},
|
||||
{
|
||||
"name": "Alacritty",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://alacritty.org/",
|
||||
"description": "A cross-platform, GPU-accelerated terminal emulator"
|
||||
},
|
||||
{
|
||||
"name": "Brave",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://brave.com/",
|
||||
"description": "Privacy-focused web browser"
|
||||
},
|
||||
{
|
||||
"name": "VSCode",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://code.visualstudio.com/",
|
||||
"description": "Source-code editor developed by Microsoft"
|
||||
},
|
||||
{
|
||||
"name": "Zellij",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://zellij.dev/",
|
||||
"description": "A terminal workspace and multiplexer"
|
||||
},
|
||||
{
|
||||
"name": "Fx",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://fx.wtf/",
|
||||
"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_url": "https://asciinema.c.woodburn.au/a/4"
|
||||
},
|
||||
{
|
||||
"name": "Zoxide",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://github.com/ajeetdsouza/zoxide",
|
||||
"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_url": "https://asciinema.c.woodburn.au/a/5"
|
||||
},
|
||||
{
|
||||
"name": "Atuin",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://atuin.sh/",
|
||||
"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_url": "https://asciinema.c.woodburn.au/a/6"
|
||||
},
|
||||
{
|
||||
"name": "Tmate",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://tmate.io/",
|
||||
"description": "Instant terminal sharing",
|
||||
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/7.js\" id=\"asciicast-7\" async=\"true\"></script>",
|
||||
"demo_url": "https://asciinema.c.woodburn.au/a/7"
|
||||
},
|
||||
{
|
||||
"name": "Eza",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://eza.rocks/",
|
||||
"description": "A modern replacement for 'ls'",
|
||||
"demo": "<script src=\"https://asciinema.c.woodburn.au/a/8.js\" id=\"asciicast-8\" async=\"true\"></script>",
|
||||
"demo_url": "https://asciinema.c.woodburn.au/a/8"
|
||||
},
|
||||
{
|
||||
"name": "Bat",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://github.com/sharkdp/bat",
|
||||
"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_url": "https://asciinema.c.woodburn.au/a/9"
|
||||
},
|
||||
{
|
||||
"name": "Oh My Zsh",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://ohmyz.sh/",
|
||||
"description": "A delightful community-driven framework for managing your Zsh configuration"
|
||||
},
|
||||
{
|
||||
"name": "Proxmox",
|
||||
"type": "Server Management",
|
||||
"url": "https://www.proxmox.com/en",
|
||||
"description": "Open-source server virtualization management solution"
|
||||
},
|
||||
{
|
||||
"name": "Portainer",
|
||||
"type": "Server Management",
|
||||
"url": "https://www.portainer.io/",
|
||||
"description": "Lightweight management UI which allows you to easily manage your Docker containers"
|
||||
},
|
||||
{
|
||||
"name": "Coolify",
|
||||
"type": "Server Management",
|
||||
"url": "https://coolify.io/",
|
||||
"description": "An open-source self-hosted Heroku alternative"
|
||||
},
|
||||
{
|
||||
"name": "OpnSense",
|
||||
"type": "Server Management",
|
||||
"url": "https://opnsense.org/",
|
||||
"description": "Open source, easy-to-use and easy-to-build FreeBSD based firewall and routing platform"
|
||||
},
|
||||
{
|
||||
"name": "Nginx Proxy Manager",
|
||||
"type": "Server Management",
|
||||
"url": "https://nginxproxymanager.com/",
|
||||
"description": "A powerful yet easy to use web interface for managing Nginx proxy hosts"
|
||||
},
|
||||
{
|
||||
"name": "Tailscale",
|
||||
"type": "Server Management",
|
||||
"url": "https://tailscale.com/",
|
||||
"description": "A zero-config VPN that just works"
|
||||
},
|
||||
{
|
||||
"name": "Authentik",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://goauthentik.io/",
|
||||
"description": "An open-source identity provider focused on flexibility and ease of use"
|
||||
},
|
||||
{
|
||||
"name": "Uptime Kuma",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://uptime.kuma.pet/",
|
||||
"description": "A fancy self-hosted monitoring tool"
|
||||
},
|
||||
{
|
||||
"name": "Gitea",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://about.gitea.com/",
|
||||
"description": "A painless self-hosted Git service"
|
||||
},
|
||||
{
|
||||
"name": "Nextcloud",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://nextcloud.com/",
|
||||
"description": "A suite of client-server software for creating and using file hosting services"
|
||||
},
|
||||
{
|
||||
"name": "Umami",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://umami.is/",
|
||||
"description": "A simple, fast, privacy-focused alternative to Google Analytics"
|
||||
},
|
||||
{
|
||||
"name": "PhotoPrism",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://photoprism.app/",
|
||||
"description": "AI-powered app for browsing, organizing & sharing your photo collection"
|
||||
},
|
||||
{
|
||||
"name": "FreeScout",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://freescout.net/",
|
||||
"description": "Self hosted email dashboard"
|
||||
},
|
||||
{
|
||||
"name": "Vaultwarden",
|
||||
"type": "Miscellaneous",
|
||||
"url": "https://github.com/dani-garcia/vaultwarden",
|
||||
"description": "Password manager server implementation compatible with Bitwarden clients"
|
||||
}
|
||||
]
|
||||
19
mail.py
19
mail.py
@@ -22,7 +22,11 @@ import os
|
||||
# }'
|
||||
|
||||
def validateSender(email):
|
||||
domains = os.getenv("EMAIL_DOMAINS").split(",")
|
||||
domains = os.getenv("EMAIL_DOMAINS")
|
||||
if not domains:
|
||||
return False
|
||||
|
||||
domains = domains.split(",")
|
||||
for domain in domains:
|
||||
if re.match(r".+@" + domain, email):
|
||||
return True
|
||||
@@ -84,8 +88,17 @@ def sendEmail(data):
|
||||
|
||||
# Sending the email
|
||||
try:
|
||||
with smtplib.SMTP_SSL(os.getenv("EMAIL_SMTP"), 465) as server:
|
||||
server.login(os.getenv("EMAIL_USER"), os.getenv("EMAIL_PASS"))
|
||||
host = os.getenv("EMAIL_SMTP")
|
||||
user = os.getenv("EMAIL_USER")
|
||||
password = os.getenv("EMAIL_PASS")
|
||||
if host is None or user is None or password is None:
|
||||
return jsonify({
|
||||
"status": 500,
|
||||
"error": "Email server not configured"
|
||||
})
|
||||
|
||||
with smtplib.SMTP_SSL(host, 465) as server:
|
||||
server.login(user, password)
|
||||
server.sendmail(fromEmail, to, msg.as_string())
|
||||
print("Email sent successfully.")
|
||||
return jsonify({
|
||||
|
||||
10
main.py
10
main.py
@@ -1,12 +1,6 @@
|
||||
import time
|
||||
from flask import Flask
|
||||
from server import app
|
||||
import server
|
||||
from gunicorn.app.base import BaseApplication
|
||||
import os
|
||||
import dotenv
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
class GunicornApp(BaseApplication):
|
||||
@@ -17,8 +11,8 @@ class GunicornApp(BaseApplication):
|
||||
|
||||
def load_config(self):
|
||||
for key, value in self.options.items():
|
||||
if key in self.cfg.settings and value is not None:
|
||||
self.cfg.set(key.lower(), value)
|
||||
if key in self.cfg.settings and value is not None: # type: ignore
|
||||
self.cfg.set(key.lower(), value) # type: ignore
|
||||
|
||||
def load(self):
|
||||
return self.application
|
||||
|
||||
48
now.py
48
now.py
@@ -1,48 +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]
|
||||
|
||||
#region Rendering
|
||||
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 not date 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)
|
||||
|
||||
#endregion
|
||||
@@ -14,4 +14,5 @@ solders
|
||||
weasyprint
|
||||
markdown
|
||||
pygments
|
||||
beautifulsoup4
|
||||
beautifulsoup4
|
||||
python-dateutil
|
||||
|
||||
53
templates/403.html
Normal file
53
templates/403.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/403">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/403">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/404.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>HTTP: <span>403</span></p>
|
||||
<div class="text-center">
|
||||
<div class="text-start" style="display: inline-block;"><code><em>this_page</em>.<em>found</em> = true;</code><code><span>if</span> (<em>this_page</em>.<em>readable</em>) {<br><span class="tab-space"></span><b>return</b> <em>this_page</em>;<br>} <span>else</span> {<br><span class="tab-space"></span><b>alert</b>('<i>This page is not readable!</i>');<br>}</code></div>
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/403.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -108,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<p style="margin-top: 1em;">Hi, I am Nathan Woodburn and I live in Canberra<br>I am currently studying at the Australian National University<br>I enjoy 3D printing and CAD<br>I code stuff with C#, Linux Bash and tons of other languages<br>I'm a co-founder of <a href="https://hns.au" target="_blank">Handshake Australia</a><br>I currently work for <a href="https://learn.namebase.io" target="_blank">Namebase</a><br><br></p><i class="fas fa-arrow-down" style="font-size: 50px;" onclick="slideout()"></i>
|
||||
<p style="margin-top: 1em;">Hi, I am Nathan Woodburn and I live in Canberra<br>I am currently studying at the Australian National University<br>I enjoy managing linux servers for my various projects<br>I code stuff with C#, Linux Bash and tons of other languages<br>I'm a co-founder of <a href="https://hns.au" target="_blank">Handshake Australia</a><br><br></p><i class="fas fa-arrow-down" style="font-size: 50px;" onclick="slideout()"></i>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
|
||||
2
templates/assets/css/styles.min.css
vendored
2
templates/assets/css/styles.min.css
vendored
@@ -1 +1 @@
|
||||
:root,[data-bs-theme=light]{--bs-primary:#6E0E9C;--bs-primary-rgb:110,14,156;--bs-primary-text-emphasis:#2C063E;--bs-primary-bg-subtle:#E2CFEB;--bs-primary-border-subtle:#C59FD7;--bs-link-color:#6E0E9C;--bs-link-color-rgb:110,14,156;--bs-link-hover-color:#a41685;--bs-link-hover-color-rgb:164,22,133}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5E0C85;--bs-btn-hover-border-color:#580B7D;--bs-btn-focus-shadow-rgb:233,219,240;--bs-btn-active-color:#fff;--bs-btn-active-bg:#580B7D;--bs-btn-active-border-color:#530B75;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6E0E9C;--bs-btn-disabled-border-color:#6E0E9C}.btn-outline-primary{--bs-btn-color:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-focus-shadow-rgb:110,14,156;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6E0E9C;--bs-btn-hover-border-color:#6E0E9C;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6E0E9C;--bs-btn-active-border-color:#6E0E9C;--bs-btn-disabled-color:#6E0E9C;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6E0E9C}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}@media (min-width:992px){.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}}
|
||||
:root,[data-bs-theme=light]{--bs-primary:#6E0E9C;--bs-primary-rgb:110,14,156;--bs-primary-text-emphasis:#2C063E;--bs-primary-bg-subtle:#E2CFEB;--bs-primary-border-subtle:#C59FD7;--bs-link-color:#6E0E9C;--bs-link-color-rgb:110,14,156;--bs-link-hover-color:#a41685;--bs-link-hover-color-rgb:164,22,133}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5E0C85;--bs-btn-hover-border-color:#580B7D;--bs-btn-focus-shadow-rgb:233,219,240;--bs-btn-active-color:#fff;--bs-btn-active-bg:#580B7D;--bs-btn-active-border-color:#530B75;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6E0E9C;--bs-btn-disabled-border-color:#6E0E9C}.btn-outline-primary{--bs-btn-color:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-focus-shadow-rgb:110,14,156;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6E0E9C;--bs-btn-hover-border-color:#6E0E9C;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6E0E9C;--bs-btn-active-border-color:#6E0E9C;--bs-btn-disabled-color:#6E0E9C;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6E0E9C}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}@media (min-width:992px){.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}}
|
||||
1
templates/assets/js/403.min.js
vendored
Normal file
1
templates/assets/js/403.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
const trigger="s",secret="/supersecretpath",home="/";var isSecret=!1;function error_check(){return function(){isSecret?(alert("You found the secret path"),window.location=secret):(alert("This page is not readable!"))}}function type(e,t){var n=document.getElementsByTagName("code")[e].innerHTML.toString(),o=0;document.getElementsByTagName("code")[e].innerHTML="",setTimeout((function(){var t=setInterval((function(){o++,document.getElementsByTagName("code")[e].innerHTML=n.slice(0,o)+"|",o==n.length&&(clearInterval(t),document.getElementsByTagName("code")[e].innerHTML=n)}),10)}),t)}setTimeout(error_check(),5e3),document.addEventListener("keydown",(function(e){"s"==e.key&&(isSecret=!0)})),type(0,0),type(1,600),type(2,1300);
|
||||
@@ -1,446 +0,0 @@
|
||||
;(function() {
|
||||
"use strict";
|
||||
|
||||
// General
|
||||
var canvas,
|
||||
screen,
|
||||
gameSize,
|
||||
game;
|
||||
|
||||
// Assets
|
||||
var invaderCanvas,
|
||||
invaderMultiplier,
|
||||
invaderSize = 20,
|
||||
initialOffsetInvader,
|
||||
invaderAttackRate,
|
||||
invaderSpeed,
|
||||
invaderSpawnDelay = 250;
|
||||
|
||||
// Counter
|
||||
var i = 0,
|
||||
kills = 0,
|
||||
spawnDelayCounter = invaderSpawnDelay;
|
||||
|
||||
var invaderDownTimer;
|
||||
|
||||
// Text
|
||||
var blocks = [
|
||||
[3, 4, 8, 9, 10, 15, 16],
|
||||
[2, 4, 7, 11, 14, 16],
|
||||
[1, 4, 7, 11, 13, 16],
|
||||
[1, 2, 3, 4, 5, 7, 11, 13, 14, 15, 16, 17],
|
||||
[4, 7, 11, 16],
|
||||
[4, 8, 9, 10, 16]
|
||||
];
|
||||
|
||||
// Game Controller
|
||||
// ---------------
|
||||
var Game = function() {
|
||||
|
||||
this.level = -1;
|
||||
this.lost = false;
|
||||
|
||||
this.player = new Player();
|
||||
this.invaders = [];
|
||||
this.invaderShots = [];
|
||||
|
||||
if (invaderDownTimer === undefined) {
|
||||
invaderDownTimer = setInterval(function() {
|
||||
for (i = 0; i < game.invaders.length; i++) game.invaders[i].move();
|
||||
}, 1000 - (this.level * 1.8));
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
Game.prototype = {
|
||||
update: function() {
|
||||
|
||||
// Next level
|
||||
if (game.invaders.length === 0) {
|
||||
|
||||
spawnDelayCounter += 1;
|
||||
if (spawnDelayCounter < invaderSpawnDelay) return;
|
||||
|
||||
this.level += 1;
|
||||
|
||||
invaderAttackRate -= 0.002;
|
||||
invaderSpeed += 10;
|
||||
|
||||
game.invaders = createInvaders();
|
||||
|
||||
spawnDelayCounter = 0;
|
||||
}
|
||||
|
||||
if (!this.lost) {
|
||||
|
||||
// Collision
|
||||
game.player.projectile.forEach(function(projectile) {
|
||||
game.invaders.forEach(function(invader) {
|
||||
if (collides(projectile, invader)) {
|
||||
invader.destroy();
|
||||
projectile.active = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.invaderShots.forEach(function(invaderShots) {
|
||||
if (collides(invaderShots, game.player)) {
|
||||
game.player.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
for (i = 0; i < game.invaders.length; i++) game.invaders[i].update();
|
||||
|
||||
}
|
||||
|
||||
// Don't stop player & projectiles.. they look nice
|
||||
game.player.update();
|
||||
for (i = 0; i < game.invaderShots.length; i++) game.invaderShots[i].update();
|
||||
|
||||
this.invaders = game.invaders.filter(function(invader) {
|
||||
return invader.active;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
draw: function() {
|
||||
|
||||
if (this.lost) {
|
||||
screen.fillStyle = "rgba(0, 0, 0, 0.03)";
|
||||
screen.fillRect(0, 0, gameSize.width, gameSize.height);
|
||||
|
||||
screen.font = "55px Lucida Console";
|
||||
screen.textAlign = "center";
|
||||
screen.fillStyle = "white";
|
||||
screen.fillText("You lost", gameSize.width / 2, gameSize.height / 2);
|
||||
screen.font = "20px Lucida Console";
|
||||
screen.fillText("Points: " + kills, gameSize.width / 2, gameSize.height / 2 + 30);
|
||||
|
||||
} else {
|
||||
screen.clearRect(0, 0, gameSize.width, gameSize.height);
|
||||
|
||||
screen.font = "10px Lucida Console";
|
||||
screen.textAlign = "right";
|
||||
screen.fillText("Points: " + kills, gameSize.width, gameSize.height - 12);
|
||||
}
|
||||
|
||||
screen.beginPath();
|
||||
|
||||
var i;
|
||||
this.player.draw();
|
||||
if (!this.lost)
|
||||
for (i = 0; i < this.invaders.length; i++) this.invaders[i].draw();
|
||||
for (i = 0; i < this.invaderShots.length; i++) this.invaderShots[i].draw();
|
||||
|
||||
screen.fill();
|
||||
|
||||
},
|
||||
|
||||
invadersBelow: function(invader) {
|
||||
return this.invaders.filter(function(b) {
|
||||
return Math.abs(invader.coordinates.x - b.coordinates.x) === 0 &&
|
||||
b.coordinates.y > invader.coordinates.y;
|
||||
}).length > 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Invaders
|
||||
// --------
|
||||
var Invader = function(coordinates) {
|
||||
this.active = true;
|
||||
this.coordinates = coordinates;
|
||||
this.size = {
|
||||
width: invaderSize,
|
||||
height: invaderSize
|
||||
};
|
||||
|
||||
this.patrolX = 0;
|
||||
this.speedX = invaderSpeed;
|
||||
|
||||
};
|
||||
|
||||
Invader.prototype = {
|
||||
update: function() {
|
||||
|
||||
if (Math.random() > invaderAttackRate && !game.invadersBelow(this)) {
|
||||
var projectile = new Projectile({
|
||||
x: this.coordinates.x + this.size.width / 2,
|
||||
y: this.coordinates.y + this.size.height - 5
|
||||
}, {
|
||||
x: 0,
|
||||
y: 2
|
||||
});
|
||||
game.invaderShots.push(projectile);
|
||||
}
|
||||
|
||||
},
|
||||
draw: function() {
|
||||
if (this.active) screen.drawImage(invaderCanvas, this.coordinates.x, this.coordinates.y);
|
||||
|
||||
},
|
||||
move: function() {
|
||||
if (this.patrolX < 0 || this.patrolX > 100) {
|
||||
this.speedX = -this.speedX;
|
||||
this.patrolX += this.speedX;
|
||||
this.coordinates.y += this.size.height;
|
||||
|
||||
if (this.coordinates.y + this.size.height * 2 > gameSize.height) game.lost = true;
|
||||
|
||||
} else {
|
||||
this.coordinates.x += this.speedX;
|
||||
this.patrolX += this.speedX;
|
||||
}
|
||||
|
||||
},
|
||||
destroy: function() {
|
||||
this.active = false;
|
||||
kills += 1;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Player
|
||||
// ------
|
||||
var Player = function() {
|
||||
this.active = true;
|
||||
this.size = {
|
||||
width: 16,
|
||||
height: 8
|
||||
};
|
||||
this.shooterHeat = -3;
|
||||
this.coordinates = {
|
||||
x: gameSize.width / 2 - (this.size.width / 2) | 0,
|
||||
y: gameSize.height - this.size.height * 2
|
||||
};
|
||||
|
||||
this.projectile = [];
|
||||
this.keyboarder = new KeyController();
|
||||
};
|
||||
|
||||
Player.prototype = {
|
||||
update: function() {
|
||||
|
||||
for (var i = 0; i < this.projectile.length; i++) this.projectile[i].update();
|
||||
|
||||
this.projectile = this.projectile.filter(function(projectile) {
|
||||
return projectile.active;
|
||||
});
|
||||
|
||||
if (!this.active) return;
|
||||
|
||||
if (this.keyboarder.isDown(this.keyboarder.KEYS.LEFT) && this.coordinates.x > 0) this.coordinates.x -= 2;
|
||||
else if (this.keyboarder.isDown(this.keyboarder.KEYS.RIGHT) && this.coordinates.x < gameSize.width - this.size.width) this.coordinates.x += 2;
|
||||
|
||||
if (this.keyboarder.isDown(this.keyboarder.KEYS.Space)) {
|
||||
this.shooterHeat += 1;
|
||||
if (this.shooterHeat < 0) {
|
||||
var projectile = new Projectile({
|
||||
x: this.coordinates.x + this.size.width / 2 - 1,
|
||||
y: this.coordinates.y - 1
|
||||
}, {
|
||||
x: 0,
|
||||
y: -7
|
||||
});
|
||||
this.projectile.push(projectile);
|
||||
} else if (this.shooterHeat > 12) this.shooterHeat = -3;
|
||||
} else {
|
||||
this.shooterHeat = -3;
|
||||
}
|
||||
|
||||
},
|
||||
draw: function() {
|
||||
if (this.active) {
|
||||
screen.rect(this.coordinates.x, this.coordinates.y, this.size.width, this.size.height);
|
||||
screen.rect(this.coordinates.x - 2, this.coordinates.y + 2, 20, 6);
|
||||
screen.rect(this.coordinates.x + 6, this.coordinates.y - 4, 4, 4);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.projectile.length; i++) this.projectile[i].draw();
|
||||
|
||||
},
|
||||
destroy: function() {
|
||||
this.active = false;
|
||||
game.lost = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Projectile
|
||||
// ------
|
||||
var Projectile = function(coordinates, velocity) {
|
||||
this.active = true;
|
||||
this.coordinates = coordinates;
|
||||
this.size = {
|
||||
width: 3,
|
||||
height: 3
|
||||
};
|
||||
this.velocity = velocity;
|
||||
};
|
||||
|
||||
Projectile.prototype = {
|
||||
update: function() {
|
||||
this.coordinates.x += this.velocity.x;
|
||||
this.coordinates.y += this.velocity.y;
|
||||
|
||||
if (this.coordinates.y > gameSize.height || this.coordinates.y < 0) this.active = false;
|
||||
|
||||
},
|
||||
draw: function() {
|
||||
if (this.active) screen.rect(this.coordinates.x, this.coordinates.y, this.size.width, this.size.height);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Keyboard input tracking
|
||||
// -----------------------
|
||||
var KeyController = function() {
|
||||
this.KEYS = {
|
||||
LEFT: 37,
|
||||
RIGHT: 39,
|
||||
Space: 32
|
||||
};
|
||||
var keyCode = [37, 39, 32];
|
||||
var keyState = {};
|
||||
|
||||
var counter;
|
||||
window.addEventListener('keydown', function(e) {
|
||||
for (counter = 0; counter < keyCode.length; counter++)
|
||||
if (keyCode[counter] == e.keyCode) {
|
||||
keyState[e.keyCode] = true;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', function(e) {
|
||||
for (counter = 0; counter < keyCode.length; counter++)
|
||||
if (keyCode[counter] == e.keyCode) {
|
||||
keyState[e.keyCode] = false;
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.isDown = function(keyCode) {
|
||||
return keyState[keyCode] === true;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// Other functions
|
||||
// ---------------
|
||||
function collides(a, b) {
|
||||
return a.coordinates.x < b.coordinates.x + b.size.width &&
|
||||
a.coordinates.x + a.size.width > b.coordinates.x &&
|
||||
a.coordinates.y < b.coordinates.y + b.size.height &&
|
||||
a.coordinates.y + a.size.height > b.coordinates.y;
|
||||
}
|
||||
|
||||
function getPixelRow(rowRaw) {
|
||||
var textRow = [],
|
||||
placer = 0,
|
||||
row = Math.floor(rowRaw / invaderMultiplier);
|
||||
if (row >= blocks.length) return [];
|
||||
for (var i = 0; i < blocks[row].length; i++) {
|
||||
var tmpContent = blocks[row][i] * invaderMultiplier;
|
||||
for (var j = 0; j < invaderMultiplier; j++) textRow[placer + j] = tmpContent + j;
|
||||
placer += invaderMultiplier;
|
||||
}
|
||||
return textRow;
|
||||
}
|
||||
|
||||
// Write Text
|
||||
// -----------
|
||||
function createInvaders() {
|
||||
var invaders = [];
|
||||
|
||||
var i = blocks.length * invaderMultiplier;
|
||||
while (i--) {
|
||||
var j = getPixelRow(i);
|
||||
for (var k = 0; k < j.length; k++) {
|
||||
invaders.push(new Invader({
|
||||
x: j[k] * invaderSize,
|
||||
y: i * invaderSize
|
||||
}));
|
||||
}
|
||||
}
|
||||
return invaders;
|
||||
}
|
||||
|
||||
// Start game
|
||||
// ----------
|
||||
window.addEventListener('load', function() {
|
||||
|
||||
var invaderAsset = new Image;
|
||||
invaderAsset.onload = function() {
|
||||
|
||||
invaderCanvas = document.createElement('canvas');
|
||||
invaderCanvas.width = invaderSize;
|
||||
invaderCanvas.height = invaderSize;
|
||||
invaderCanvas.getContext("2d").drawImage(invaderAsset, 0, 0);
|
||||
|
||||
// Game Creation
|
||||
canvas = document.getElementById("space-invaders");
|
||||
screen = canvas.getContext('2d');
|
||||
|
||||
initGameStart();
|
||||
loop();
|
||||
|
||||
};
|
||||
invaderAsset.src = "/assets/img/invader.gif";
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
initGameStart();
|
||||
});
|
||||
document.getElementById('restart').addEventListener('click', function() {
|
||||
initGameStart();
|
||||
});
|
||||
|
||||
function initGameStart() {
|
||||
if (window.innerWidth > 1200) {
|
||||
screen.canvas.width = 1200;
|
||||
screen.canvas.height = 500;
|
||||
gameSize = {
|
||||
width: 1200,
|
||||
height: 500
|
||||
};
|
||||
invaderMultiplier = 3;
|
||||
initialOffsetInvader = 420;
|
||||
} else if (window.innerWidth > 800) {
|
||||
screen.canvas.width = 900;
|
||||
screen.canvas.height = 600;
|
||||
gameSize = {
|
||||
width: 900,
|
||||
height: 600
|
||||
};
|
||||
invaderMultiplier = 2;
|
||||
initialOffsetInvader = 280;
|
||||
} else {
|
||||
screen.canvas.width = 600;
|
||||
screen.canvas.height = 300;
|
||||
gameSize = {
|
||||
width: 600,
|
||||
height: 300
|
||||
};
|
||||
invaderMultiplier = 1;
|
||||
initialOffsetInvader = 140;
|
||||
}
|
||||
|
||||
kills = 0;
|
||||
invaderAttackRate = 0.999;
|
||||
invaderSpeed = 20;
|
||||
spawnDelayCounter = invaderSpawnDelay;
|
||||
|
||||
game = new Game();
|
||||
}
|
||||
|
||||
function loop() {
|
||||
game.update();
|
||||
game.draw();
|
||||
|
||||
requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1,154 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const loadingScreen = document.getElementById("loading-screen");
|
||||
|
||||
// Terminal simulation data
|
||||
const commands = [
|
||||
{ pre: '┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~</span>]', message: "cd Git" },
|
||||
{ pre: '┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~/Git</span>]', message: "cd Nathanwoodburn.github.io" },
|
||||
{ pre: '┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~/Git/Nathanwoodburn.github.io</span>]', message: "python3 main.py" }
|
||||
];
|
||||
|
||||
const serverMessages = [
|
||||
"Starting server with 1 workers and 2 threads",
|
||||
"+0000] [1] [INFO] Starting gunicorn 22.0.0",
|
||||
"+0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)",
|
||||
"+0000] [1] [INFO] Using worker: gthread",
|
||||
"+0000] [8] [INFO] Booting worker with pid: 8",
|
||||
"Preloading assets for faster navigation..."
|
||||
];
|
||||
|
||||
let currentCommand = 0;
|
||||
let assetsLoaded = false;
|
||||
let terminalComplete = false;
|
||||
|
||||
// Enhanced asset preloading function
|
||||
function preloadAssets() {
|
||||
const assets = [
|
||||
// Additional CSS files that might not be in preload
|
||||
'/assets/css/animate.min.min.css',
|
||||
'/assets/css/fixes.min.css',
|
||||
'/assets/css/Footer-Dark-icons.min.css',
|
||||
'/assets/css/GridSystem-1.min.css',
|
||||
// Font files
|
||||
'/assets/fonts/fa-solid-900.woff2',
|
||||
'/assets/fonts/fa-brands-400.woff2',
|
||||
'/assets/fonts/fa-regular-400.woff2'
|
||||
];
|
||||
|
||||
let loadedCount = 0;
|
||||
const totalAssets = assets.length;
|
||||
|
||||
function onAssetLoad() {
|
||||
loadedCount++;
|
||||
if (loadedCount === totalAssets) {
|
||||
assetsLoaded = true;
|
||||
checkReadyToRedirect();
|
||||
}
|
||||
}
|
||||
|
||||
// Load additional assets
|
||||
assets.forEach(assetUrl => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'preload';
|
||||
link.as = assetUrl.endsWith('.css') ? 'style' :
|
||||
assetUrl.endsWith('.js') ? 'script' :
|
||||
assetUrl.includes('/fonts/') ? 'font' : 'fetch';
|
||||
if (link.as === 'font') {
|
||||
link.crossOrigin = 'anonymous';
|
||||
}
|
||||
link.href = assetUrl;
|
||||
link.onload = onAssetLoad;
|
||||
link.onerror = onAssetLoad; // Count errors as loaded to prevent hanging
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
|
||||
// If no additional assets, mark as loaded
|
||||
if (totalAssets === 0) {
|
||||
assetsLoaded = true;
|
||||
checkReadyToRedirect();
|
||||
}
|
||||
}
|
||||
|
||||
function checkReadyToRedirect() {
|
||||
if (assetsLoaded && terminalComplete) {
|
||||
setTimeout(redirectToIndex, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentTime() {
|
||||
const now = new Date();
|
||||
return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')} ${String(now.getUTCHours()).padStart(2, '0')}:${String(now.getUTCMinutes()).padStart(2, '0')}:${String(now.getUTCSeconds()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function displayServerMessages(messages, callback) {
|
||||
const timestamp = getCurrentTime();
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const messageDiv = document.createElement("div");
|
||||
messageDiv.classList.add("loading-line");
|
||||
messageDiv.innerHTML = i !== 0 ? "[" + timestamp + "] " + messages[i] : messages[i];
|
||||
loadingScreen.appendChild(messageDiv);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
function redirectToIndex() {
|
||||
if (window.location.pathname === "/") {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners for manual redirect
|
||||
window.addEventListener("keypress", redirectToIndex);
|
||||
|
||||
if (window.innerWidth < 768) {
|
||||
console.log("Screen width is less than 768px, allowing click to redirect");
|
||||
window.addEventListener("click", redirectToIndex);
|
||||
}
|
||||
|
||||
function typeCommand(command, callback) {
|
||||
const preDiv = document.createElement("div");
|
||||
preDiv.classList.add("loading-pre");
|
||||
preDiv.innerHTML = command.pre;
|
||||
loadingScreen.appendChild(preDiv);
|
||||
|
||||
const commandDiv = document.createElement("div");
|
||||
commandDiv.classList.add("loading-line");
|
||||
commandDiv.innerHTML = '└─<span class="blue">$</span> <span class="cursor"></span>';
|
||||
loadingScreen.appendChild(commandDiv);
|
||||
|
||||
let charIndex = 0;
|
||||
const typeInterval = setInterval(() => {
|
||||
commandDiv.removeChild(commandDiv.querySelector(".cursor"));
|
||||
commandDiv.innerHTML += command.message[charIndex] + '<span class="cursor"></span>';
|
||||
charIndex++;
|
||||
|
||||
if (charIndex === command.message.length) {
|
||||
commandDiv.removeChild(commandDiv.querySelector(".cursor"));
|
||||
clearInterval(typeInterval);
|
||||
callback();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function runTerminalSimulation() {
|
||||
if (currentCommand < commands.length) {
|
||||
typeCommand(commands[currentCommand], () => {
|
||||
currentCommand++;
|
||||
setTimeout(runTerminalSimulation, 200);
|
||||
});
|
||||
} else {
|
||||
displayServerMessages(serverMessages, () => {
|
||||
terminalComplete = true;
|
||||
checkReadyToRedirect();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Start the loading process
|
||||
preloadAssets();
|
||||
runTerminalSimulation();
|
||||
});
|
||||
@@ -53,6 +53,7 @@ Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -54,6 +54,7 @@ Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
14
templates/contact.ascii
Normal file
14
templates/contact.ascii
Normal file
@@ -0,0 +1,14 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m CONTACT ME [0m
|
||||
[1;36m────────────[0m
|
||||
|
||||
Here are my socials — I’m 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
25
templates/donate.ascii
Normal file
@@ -0,0 +1,25 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m DONATE [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
If you’d like to support my work 💙
|
||||
|
||||
- PayPal: [https://paypal.me/nathanwoodburn]
|
||||
- GitHub: [https://github.com/sponsors/Nathanwoodburn]
|
||||
- Stripe: [https://donate.stripe.com/8wM6pv0VD08Xe408ww]
|
||||
|
||||
[1mHNS: nathan.woodburn[0m
|
||||
[1m{{ HNS }}[0m
|
||||
|
||||
[1mBTC: thinbadger6@primal.net[0m
|
||||
[1m{{ BTC }}[0m
|
||||
|
||||
[1mSOL: woodburn.sol[0m
|
||||
[1m{{ SOL }}[0m
|
||||
|
||||
[1mETH: woodburn.au[0m
|
||||
[1m{{ ETH }}[0m
|
||||
|
||||
More donation options → [/donate/more]
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
10
templates/donate_coin.ascii
Normal file
10
templates/donate_coin.ascii
Normal file
@@ -0,0 +1,10 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m DONATE [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
Here is my [1m{{ coin }}[0m address if you'd like to send a donation 💙
|
||||
[1m{{ address }}[0m
|
||||
|
||||
Thank you for your support! 🙏
|
||||
|
||||
13
templates/donate_more.ascii
Normal file
13
templates/donate_more.ascii
Normal file
@@ -0,0 +1,13 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m DONATE [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
Here is a list of additional cryptocurrencies and donation methods 💙
|
||||
For each coin below, you can get the address from [1m/donate/<coin>[0m
|
||||
|
||||
{% for coin in coins %}{% if loop.index0 % 4 == 0 and loop.index0 != 0 %}
|
||||
{% endif %}[1m{{ coin }}[0m{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
|
||||
Thank you for your support! 🙏
|
||||
|
||||
25
templates/favicon.ascii
Normal file
25
templates/favicon.ascii
Normal file
@@ -0,0 +1,25 @@
|
||||
▒▒▒ ▓▓▓
|
||||
▒░░░░▒▓ ▓▓▓▓▓▓▓
|
||||
▒░░░░░░▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒░░░░░▒▒▒▒▒▒▒ ▓▓▒▓▓▓▓▓▓▓▓▓▓
|
||||
▒░░░▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒░░▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒ ▒▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒ ▒▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒ ▒▒▒▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▒▒ ▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▒▒▒▒ ▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
|
||||
▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓█
|
||||
▓▓▓▓ ▓▓▓█
|
||||
|
||||
11
templates/header.ascii
Normal file
11
templates/header.ascii
Normal file
@@ -0,0 +1,11 @@
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
[1;36m . . , . . . .. / [0m
|
||||
[1;36m |\ | _.-+-|_ _.._ | | _ _ _||_ . .._.._ / [0m
|
||||
[1;36m | \|(_] | [ )(_][ ) * |/\|(_)(_)(_][_)(_|[ [ )/ [0m
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
|
||||
Home [/]
|
||||
Contact [/contact]
|
||||
Projects [/projects]
|
||||
Donate [/donate]
|
||||
|
||||
390
templates/hosting.html
Normal file
390
templates/hosting.html
Normal file
@@ -0,0 +1,390 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="height: 100%;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/hosting">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/hosting">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77" style="display: flex;flex-direction: column;">{{handshake_scripts | safe}}
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="text-center content-section" style="background: #110033;padding-bottom: 100px;flex: 1;display: flex;flex-direction: column;justify-content: center;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>Web Hosting</h2>
|
||||
<p style="margin-bottom: 10px;">Configure your hosting setup below</p><form id="costForm">
|
||||
<div style="padding: 10px;">
|
||||
<label style="font-weight: bold; margin-bottom: 15px; display: block;">Choose a plan:</label>
|
||||
<div style="display: flex; gap: 15px; flex-wrap: wrap; margin-top: 10px;">
|
||||
<div class="plan-card" onclick="selectPreset('standard')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
|
||||
<input type="radio" name="preset" value="standard" onchange="handlePresetChange()" checked style="margin-bottom: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">Standard WordPress</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #007bff; margin-bottom: 10px;">$6/month</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
|
||||
<li style="margin-bottom: 5px;">✓ 1 CPU Core</li>
|
||||
<li style="margin-bottom: 5px;">✓ 0.5GB RAM</li>
|
||||
<li style="margin-bottom: 5px;">✓ 10GB Storage</li>
|
||||
<li style="margin-bottom: 5px;">✓ Perfect for small sites</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="plan-card" onclick="selectPreset('premium')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
|
||||
<input type="radio" name="preset" value="premium" onchange="handlePresetChange()" style="margin-bottom: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">Premium WordPress</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #28a745; margin-bottom: 10px;">$10/month</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
|
||||
<li style="margin-bottom: 5px;">✓ 1 CPU Core</li>
|
||||
<li style="margin-bottom: 5px;">✓ 1GB RAM</li>
|
||||
<li style="margin-bottom: 5px;">✓ 50GB Storage</li>
|
||||
<li style="margin-bottom: 5px;">✓ Weekly Backups Included</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="plan-card" onclick="selectPreset('custom')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
|
||||
<input type="radio" name="preset" value="custom" onchange="handlePresetChange()" style="margin-bottom: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">Custom Configuration</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #6c757d; margin-bottom: 10px;">Variable</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
|
||||
<li style="margin-bottom: 5px;">✓ Choose your CPU</li>
|
||||
<li style="margin-bottom: 5px;">✓ Choose your RAM</li>
|
||||
<li style="margin-bottom: 5px;">✓ Choose your Storage</li>
|
||||
<li style="margin-bottom: 5px;">✓ Optional Backups</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="customSliders" style="display: none;">
|
||||
<div>
|
||||
<label class="form-label" for="cpus" style="padding: 10px;">CPUs:
|
||||
<span id="cpusValue" style="display:inline-block; min-width: 3ch; font-family: monospace;">1</span>
|
||||
</label>
|
||||
<input id="cpus" name="cpus" class="form-range" type="range"
|
||||
min="1" max="4" value="1" step="1"
|
||||
style="max-width: 500px; padding-top: 10px;"
|
||||
oninput="updateValue('cpus')">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="memory" style="padding: 10px;">Memory (GB):
|
||||
<span id="memoryValue" style="display:inline-block; min-width: 5ch; font-family: monospace;">0.5</span>
|
||||
</label>
|
||||
<input id="memory" name="memory" class="form-range" type="range"
|
||||
min="0" max="6" value="0" step="1"
|
||||
style="max-width: 500px; padding-top: 10px;"
|
||||
oninput="updateValue('memory')">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="disk" style="padding: 10px;">Disk Space (GB):
|
||||
<span id="diskValue" style="display:inline-block; min-width: 5ch; font-family: monospace;">10</span>
|
||||
</label>
|
||||
<input id="disk" name="disk" class="form-range" type="range"
|
||||
min="10" max="500" value="10" step="10"
|
||||
style="max-width: 500px; padding-top: 10px;"
|
||||
oninput="updateValue('disk')">
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px;">
|
||||
<label><input type="checkbox" id="backups" onchange="calculateCost()"> Backups ($5/month for up to 150GB)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px; font-weight: bold;">
|
||||
Monthly Cost: $<span id="monthlyCost" style="display:inline-block; min-width: 6ch; font-family: monospace; text-align: left;">0.00</span><br>
|
||||
<small>All prices are in AUD</small>
|
||||
<br>
|
||||
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="Your Email" required style="margin-top: 10px;">
|
||||
<!-- Allow message -->
|
||||
<textarea id="message" name="message" class="form-control" placeholder="Your Message" rows="3" style="margin-top: 10px;"></textarea>
|
||||
|
||||
<button type="button" class="btn btn-primary" style="margin-top: 10px;" onclick="sendEnquiry()">Send Enquiry</button>
|
||||
<small id="enquiryStatus" style="display: block; margin-top: 10px;"></small>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Rates
|
||||
const cpuRate = 3;
|
||||
const memoryRate = 5;
|
||||
const diskRate = 0.1;
|
||||
const backupRate = 1;
|
||||
|
||||
// Memory values mapping
|
||||
const memoryValues = [0.5, 1, 2, 4, 8, 16, 24];
|
||||
|
||||
// Initialize on load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Check if parameters are in the URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Check for preset parameter
|
||||
if (urlParams.has('preset')) {
|
||||
const presetValue = urlParams.get('preset');
|
||||
const presetRadio = document.querySelector(`input[name="preset"][value="${presetValue}"]`);
|
||||
if (presetRadio) {
|
||||
presetRadio.checked = true;
|
||||
handlePresetChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (urlParams.has('cpus')) {
|
||||
document.getElementById('cpus').value = urlParams.get('cpus');
|
||||
}
|
||||
if (urlParams.has('memory')) {
|
||||
const memoryParam = parseFloat(urlParams.get('memory'));
|
||||
const memoryIndex = memoryValues.indexOf(memoryParam);
|
||||
document.getElementById('memory').value = memoryIndex >= 0 ? memoryIndex : 0;
|
||||
}
|
||||
if (urlParams.has('disk')) {
|
||||
document.getElementById('disk').value = urlParams.get('disk');
|
||||
}
|
||||
if (urlParams.has('backups')) {
|
||||
document.getElementById('backups').checked = urlParams.get('backups') === 'true';
|
||||
}
|
||||
if (urlParams.has('email')) {
|
||||
document.getElementById('email').value = urlParams.get('email');
|
||||
}
|
||||
if (urlParams.has('message')) {
|
||||
document.getElementById('message').value = urlParams.get('message');
|
||||
}
|
||||
// Update the displayed values
|
||||
updateValue('cpus');
|
||||
updateValue('memory');
|
||||
updateValue('disk');
|
||||
calculateCost();
|
||||
// Send the enquiry if the form is submitted
|
||||
document.getElementById('costForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
sendEnquiry();
|
||||
});
|
||||
|
||||
// Initialize preset handling and card styles
|
||||
handlePresetChange();
|
||||
updateCardStyles();
|
||||
});
|
||||
|
||||
function updateValue(field) {
|
||||
let value = document.getElementById(field).value;
|
||||
|
||||
if (field === 'memory') {
|
||||
value = memoryValues[parseInt(value)];
|
||||
}
|
||||
|
||||
document.getElementById(field + 'Value').textContent = value;
|
||||
calculateCost();
|
||||
}
|
||||
|
||||
function selectPreset(presetValue) {
|
||||
document.querySelector(`input[name="preset"][value="${presetValue}"]`).checked = true;
|
||||
handlePresetChange();
|
||||
updateCardStyles();
|
||||
}
|
||||
|
||||
function updateCardStyles() {
|
||||
const cards = document.querySelectorAll('.plan-card');
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
|
||||
cards.forEach((card, index) => {
|
||||
const presetValues = ['standard', 'premium', 'custom'];
|
||||
const isSelected = presetValues[index] === selectedPreset;
|
||||
|
||||
if (isSelected) {
|
||||
card.style.borderColor = '#007bff';
|
||||
card.style.background = '#f8f9fa';
|
||||
card.style.transform = 'translateY(-2px)';
|
||||
card.style.boxShadow = '0 4px 12px rgba(0,123,255,0.15)';
|
||||
} else {
|
||||
card.style.borderColor = '#ddd';
|
||||
card.style.background = 'white';
|
||||
card.style.transform = 'translateY(0)';
|
||||
card.style.boxShadow = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handlePresetChange() {
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
const customSliders = document.getElementById('customSliders');
|
||||
|
||||
updateCardStyles();
|
||||
|
||||
if (selectedPreset === 'custom') {
|
||||
customSliders.style.display = 'block';
|
||||
} else {
|
||||
customSliders.style.display = 'none';
|
||||
|
||||
// Set preset values
|
||||
if (selectedPreset === 'standard') {
|
||||
document.getElementById('cpus').value = 1;
|
||||
document.getElementById('memory').value = 0; // 0.5GB
|
||||
document.getElementById('disk').value = 10;
|
||||
document.getElementById('backups').checked = false;
|
||||
} else if (selectedPreset === 'premium') {
|
||||
document.getElementById('cpus').value = 1;
|
||||
document.getElementById('memory').value = 1; // 1GB
|
||||
document.getElementById('disk').value = 50;
|
||||
document.getElementById('backups').checked = true;
|
||||
}
|
||||
|
||||
// Update displayed values
|
||||
updateValue('cpus');
|
||||
updateValue('memory');
|
||||
updateValue('disk');
|
||||
}
|
||||
|
||||
calculateCost();
|
||||
}
|
||||
|
||||
function calculateCost() {
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
|
||||
// For presets, use fixed pricing
|
||||
if (selectedPreset === 'standard') {
|
||||
document.getElementById('monthlyCost').textContent = '6.00';
|
||||
return;
|
||||
} else if (selectedPreset === 'premium') {
|
||||
document.getElementById('monthlyCost').textContent = '10.00';
|
||||
return;
|
||||
}
|
||||
|
||||
// Custom calculation
|
||||
const cpus = parseFloat(document.getElementById('cpus').value);
|
||||
const memoryIndex = parseInt(document.getElementById('memory').value);
|
||||
const memory = memoryValues[memoryIndex];
|
||||
const disk = parseFloat(document.getElementById('disk').value);
|
||||
|
||||
let monthlyCost = (cpus * cpuRate) + (memory * memoryRate) + (disk * diskRate);
|
||||
|
||||
if (document.getElementById('backups').checked) {
|
||||
const backupUnits = Math.ceil(disk / 50);
|
||||
monthlyCost += backupUnits * backupRate;
|
||||
}
|
||||
|
||||
document.getElementById('monthlyCost').textContent = monthlyCost.toFixed(2);
|
||||
}
|
||||
|
||||
function sendEnquiry() {
|
||||
// Ensure that the email field is filled
|
||||
const email = document.getElementById('email').value;
|
||||
if (!email) {
|
||||
document.getElementById('enquiryStatus').textContent = 'Please enter your email address.';
|
||||
document.getElementById('enquiryStatus').style.color = 'red';
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect form data
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
const cpus = document.getElementById('cpus').value;
|
||||
const memoryIndex = parseInt(document.getElementById('memory').value);
|
||||
const memory = memoryValues[memoryIndex];
|
||||
const disk = document.getElementById('disk').value;
|
||||
const backups = document.getElementById('backups').checked ? 'Yes' : 'No';
|
||||
const message = document.getElementById('message').value;
|
||||
const enquiryData = {
|
||||
email: email,
|
||||
preset: selectedPreset,
|
||||
cpus: cpus,
|
||||
memory: memory,
|
||||
disk: disk,
|
||||
backups: backups,
|
||||
message: message
|
||||
};
|
||||
// Send the enquiry data to the server
|
||||
fetch('/hosting/send-enquiry', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(enquiryData)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
document.getElementById('enquiryStatus').textContent = 'Enquiry sent successfully!';
|
||||
document.getElementById('enquiryStatus').style.color = 'green';
|
||||
calculateCost();
|
||||
} else {
|
||||
document.getElementById('enquiryStatus').textContent = 'Failed to send enquiry. Please try again.';
|
||||
document.getElementById('enquiryStatus').style.color = 'red';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error sending enquiry:', error);
|
||||
document.getElementById('enquiryStatus').textContent = 'Error sending enquiry. Please check your network connection.';
|
||||
document.getElementById('enquiryStatus').style.color = 'red';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>{{custom | safe}}
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
43
templates/index.ascii
Normal file
43
templates/index.ascii
Normal file
@@ -0,0 +1,43 @@
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
[1;36m . . , . . . .. / [0m
|
||||
[1;36m |\ | _.-+-|_ _.._ | | _ _ _||_ . .._.._ / [0m
|
||||
[1;36m | \|(_] | [ )(_][ ) * |/\|(_)(_)(_][_)(_|[ [ )/ [0m
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
|
||||
Home [/]
|
||||
Contact [/contact]
|
||||
Projects [/projects]
|
||||
Donate [/donate]
|
||||
API [/api/v1/]
|
||||
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m ABOUT ME [0m
|
||||
[1;36m──────────[0m
|
||||
|
||||
Hi, I'm [1mNathan Woodburn[0m from Canberra, Australia.
|
||||
I've been homeschooled through Year 12 and am now studying a
|
||||
[1mBachelor of Computer Science[0m.
|
||||
|
||||
I love building random projects, so this site is always evolving.
|
||||
I'm also one of the founders of [1;36mHandshake AU[0m [https://hns.au],
|
||||
working to grow Handshake adoption across Australia.
|
||||
|
||||
I'm currently working on: {{ repo | safe }}
|
||||
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m SKILLS [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
- Linux servers & CLI
|
||||
- DNS & DNSSEC
|
||||
- NGINX web servers
|
||||
- Programming:
|
||||
- Python 3
|
||||
- C#
|
||||
- Java
|
||||
- Bash
|
||||
|
||||
|
||||
Served to: {{ ip }}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
@@ -94,7 +95,7 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>About ME</h2>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;" alt="My Profile"><img class="profile foreground" src="/assets/img/pfront.webp" alt=""></div>
|
||||
<p style="margin-bottom: 5px;">Hi, I'm Nathan Woodburn and I live in Canberra, Australia.<br>I've been home schooled all the way to Yr 12.<br>I'm currently studying a Bachelor of Computer Science.<br>I create tons of random projects so this site is often behind.<br>I'm one of the founders of <a href="https://hns.au" target="_blank">Handshake AU</a> working to increase Handshake adoption in Australia.<br>I work for <a href="https://www.namebase.io" target="_blank">Namebase</a> as tech and general support. Namebase is a US based company owned by <a href="https://namecheap.com" target="_blank">Namecheap</a>.</p>
|
||||
<p style="margin-bottom: 5px;">Hi, I'm Nathan Woodburn and I live in Canberra, Australia.<br>I've been home schooled all the way to Yr 12.<br>I'm currently studying a Bachelor of Computer Science.<br>I create tons of random projects so this site is often behind.<br>I'm one of the founders of <a href="https://hns.au" target="_blank">Handshake AU</a> working to increase Handshake adoption in Australia.</p>
|
||||
<p title="{{repo_description}}" style="margin-bottom: 0px;display: inline-block;">I'm currently working on</p>
|
||||
<p data-bs-toggle="tooltip" data-bss-tooltip="" title="{{repo_description}}" style="display: inline-block;">{{repo | safe}}</p>
|
||||
</div>
|
||||
@@ -103,12 +104,16 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>Skills</h2>
|
||||
<ul class="list-unstyled" style="font-size: 18px;">
|
||||
<li class="printing">3D Printing</li>
|
||||
<li>Autodesk Fusion 360 (CAD Modeling)</li>
|
||||
<li class="programc">Programming with various languages</li>
|
||||
<li>DNS, DNSSEC and Trustless SSL</li>
|
||||
<li class="programlinux">Linux Servers and CLI</li>
|
||||
<li>DNS, DNSSEC and Trustless SSL</li>
|
||||
<li class="programnginx">NGINX Web Servers</li>
|
||||
<li class="programc">Programming in<ul class="list-inline">
|
||||
<li class="list-inline-item">Python 3</li>
|
||||
<li class="list-inline-item">C#</li>
|
||||
<li class="list-inline-item">Java</li>
|
||||
<li class="list-inline-item">Bash</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -54,6 +54,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -54,6 +54,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -53,6 +53,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
166
templates/now/25_07_21.html
Normal file
166
templates/now/25_07_21.html
Normal file
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="background: black;height: auto;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/25_07_21">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/25_07_21">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<meta name="twitter:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
<meta name="description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<meta name="twitter:description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>{{handshake_scripts | safe}}
|
||||
<div style="height: 10em;"></div>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;"><img class="profile foreground" src="/assets/img/pfront.webp"></div>
|
||||
<h1 class="nathanwoodburn" style="margin-bottom: 0px;">Nathan.Woodburn/</h1>
|
||||
<h3 style="margin-bottom: 0px;">WHat's Happening Now</h3>
|
||||
<h6>{{DATE}}</h6>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">Uni Semester 2</h1>
|
||||
<p>I'm back at uni this week. I'm doing 2 courses this semester: Software Engineering, Operating Systems Implementation, Network Security. I'm hoping to finish my degree in semester 1 of 2026.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">FireWallet Updates</h1>
|
||||
<p>I've updated FireWallet, a wallet for Handshake (a decentralized alternative DNS root zone). I've updated the transactions page to display the tx action clearer.<br>There is a new auction page with better UI and dynamic reloading. The auctions now also show mempool bids.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="text-center content-section" id="contact" style="padding-top: 0px;padding-bottom: 3em;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 d-none d-print-block d-sm-block d-md-block d-lg-block d-xl-block d-xxl-block mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon"></i></a></li>
|
||||
<li class="social-link discord"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link mastodon"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link youtube"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link signal"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8 d-block d-print-none d-sm-none d-md-none d-lg-none d-xl-none d-xxl-none mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="social-link-sml"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon-sml">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon-sml">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon-sml"></i></a></li>
|
||||
<li class="discord social-link-sml"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon-sml">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="mastodon social-link-sml"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon-sml">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="youtube social-link-sml"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon-sml">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="signal social-link-sml"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon-sml">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">Want to look at some past Now pages?<br>Check out <a href="/old">/old</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
178
templates/now/25_08_15.html
Normal file
178
templates/now/25_08_15.html
Normal file
@@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="background: black;height: auto;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/25_08_15">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/25_08_15">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<meta name="twitter:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
<meta name="description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<meta name="twitter:description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>{{handshake_scripts | safe}}
|
||||
<div style="height: 10em;"></div>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;"><img class="profile foreground" src="/assets/img/pfront.webp"></div>
|
||||
<h1 class="nathanwoodburn" style="margin-bottom: 0px;">Nathan.Woodburn/</h1>
|
||||
<h3 style="margin-bottom: 0px;">WHat's Happening Now</h3>
|
||||
<h6>{{DATE}}</h6>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">HNSDoH Updates</h1>
|
||||
<p>Lately HNSDoH, my distributed DNS resolver service, has been crashing or having timeouts. In order to mitigate this, I've updated HNSDoH to only route non-ICANN domains to HSD (Handshake domain backend server). This will stop HSD from being swamped with regular traffic and allow it to focus only on resolving Handshake domains. I have also added caching to help with repeat requests.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">HNSAU Redesign</h1>
|
||||
<p>I've updated HNSAU to have an entire new UI thanks to the input from Erwin and Tim. Have a look at <a href="https://hns.au" target="_blank">https://hns.au</a></p>
|
||||
</div>
|
||||
</section>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">FireAlerts</h1>
|
||||
<p>I've recently released an alerts system for your Handshake domains. It will send you a reminder before your domain expires. Have a look at <a href="https://alerts.firewallet.au" target="_blank">https://alerts.firewallet.au</a></p>
|
||||
</div>
|
||||
</section>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">Support</h1>
|
||||
<p>I am currently running low on funding. So if you use any of my projects and would like to support me please send me a message or visit the link below<br><a class="btn btn-primary" role="button" target="_blank" href="/donate">Donate</a></p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="text-center content-section" id="contact" style="padding-top: 0px;padding-bottom: 3em;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 d-none d-print-block d-sm-block d-md-block d-lg-block d-xl-block d-xxl-block mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon"></i></a></li>
|
||||
<li class="social-link discord"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link mastodon"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link youtube"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link signal"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8 d-block d-print-none d-sm-none d-md-none d-lg-none d-xl-none d-xxl-none mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="social-link-sml"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon-sml">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon-sml">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon-sml"></i></a></li>
|
||||
<li class="discord social-link-sml"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon-sml">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="mastodon social-link-sml"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon-sml">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="youtube social-link-sml"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon-sml">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="signal social-link-sml"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon-sml">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">Want to look at some past Now pages?<br>Check out <a href="/old">/old</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
172
templates/now/25_10_23.html
Normal file
172
templates/now/25_10_23.html
Normal file
@@ -0,0 +1,172 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="background: black;height: auto;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/25_10_23">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/25_10_23">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<meta name="twitter:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
<meta name="description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<meta name="twitter:description" content="G'day,
|
||||
Find out what I've been up to in the last little bit">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>{{handshake_scripts | safe}}
|
||||
<div style="height: 10em;"></div>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;"><img class="profile foreground" src="/assets/img/pfront.webp"></div>
|
||||
<h1 class="nathanwoodburn" style="margin-bottom: 0px;">Nathan.Woodburn/</h1>
|
||||
<h3 style="margin-bottom: 0px;">WHat's Happening Now</h3>
|
||||
<h6>{{DATE}}</h6>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">Uni Updates</h1>
|
||||
<p>I'm finishing up uni for the year with exams in the next few weeks. I should be finishing my degree in the first semester of next year. So I'm hoping to find some work for next year to start earning again.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">Software Updates</h1>
|
||||
<p>I haven't done any major updates to my projects lately. I've cleaned up my main website code base to be easier to manage. Other than that I've done a few bug fixes for shaker-bot (a discord verification bot), FireWallet and HNS-Login (a domain authentication service).</p>
|
||||
</div>
|
||||
</section>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">5 Years of Github Usage</h1>
|
||||
<p>This month marks 5 years since my first git commit pushed to Github. In 5 years, I've made over 4,000 commits, 200 repositories, 60 issues, 40 PRs. Of those 40 PRs, I've contributed code to 10 open source projects.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="text-center content-section" id="contact" style="padding-top: 0px;padding-bottom: 3em;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 d-none d-print-block d-sm-block d-md-block d-lg-block d-xl-block d-xxl-block mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon"></i></a></li>
|
||||
<li class="social-link discord"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link mastodon"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link youtube"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link signal"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8 d-block d-print-none d-sm-none d-md-none d-lg-none d-xl-none d-xxl-none mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="social-link-sml"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon-sml">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon-sml">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon-sml"></i></a></li>
|
||||
<li class="discord social-link-sml"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon-sml">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="mastodon social-link-sml"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon-sml">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="youtube social-link-sml"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon-sml">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="signal social-link-sml"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon-sml">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">Want to look at some past Now pages?<br>Check out <a href="/old">/old</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -54,6 +54,7 @@ Find out what I've been up to in the last week">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -54,6 +54,7 @@ Find out what I've been up to in the last little bit">
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
7
templates/projects.ascii
Normal file
7
templates/projects.ascii
Normal file
@@ -0,0 +1,7 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m RECENT PROJECTS [0m
|
||||
[1;36m─────────────────[0m
|
||||
|
||||
{{projects}}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<div style="max-width: 2000px;margin: auto;">
|
||||
<div style="margin-bottom: 50px;">
|
||||
<h1 class="r-heading3" style="font-size: 25px;">Summary</h1>
|
||||
<p class="r-body">Cybersecurity-focused computing student with hands-on experience in DNS, Linux system administration, server infrastructure, and decentralized technologies. Skilled in providing technical support, resolving complex domain-related issues, and engaging in open-source blockchain communities. Experienced speaker and contributor at Handshake-related conferences. Passionate about building secure, resilient, and privacy-respecting systems from the ground up.</p>
|
||||
<p class="r-body">Linux and server administration student with experience managing servers, DNS, virtualization, and networking. Skilled in deploying and maintaining self-hosted services, troubleshooting complex system issues, and building resilient, automated infrastructures. Passionate about open-source tools and practical system design.</p>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-lg-2 row-cols-xl-2 row-cols-xxl-2">
|
||||
<div class="col">
|
||||
@@ -112,7 +112,7 @@
|
||||
<h6 class="r-heading3">Australian National University | 2022 - Present</h6>
|
||||
<ul class="r-body">
|
||||
<li>Currently pursuing a Bachelor of Computing with a specialization in cybersecurity.</li>
|
||||
<li>Gaining hands-on experience in network security, cryptography, and secure software development.</li>
|
||||
<li>Gaining hands-on experience in network security, system design, and secure software development.</li>
|
||||
<li>Building a strong foundation in computer science principles, programming, and system architecture.</li>
|
||||
<li>Collaborating on group projects and labs to apply theoretical knowledge to real-world challenges.</li>
|
||||
</ul>
|
||||
@@ -132,7 +132,7 @@
|
||||
<h4 class="r-heading2">Home Educated</h4>
|
||||
<h6 class="r-heading3">Self-Directed Learning</h6>
|
||||
<ul class="r-body">
|
||||
<li>Cultivated time management, self-discipline, and critical thinking skills crucial for success in tech and cybersecurity.</li>
|
||||
<li>Cultivated time management, self-discipline, and critical thinking skills crucial for success in tech.</li>
|
||||
<li>Developed a strong passion for technology, programming, and system administration through independent exploration.</li>
|
||||
<li>Built custom applications, managed servers, and solved technical challenges in a flexible learning environment.</li>
|
||||
</ul>
|
||||
@@ -142,16 +142,7 @@
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<div class="col">
|
||||
<div class="noprintbreak">
|
||||
<h1 class="r-heading1">Projects</h1>
|
||||
<h4 class="r-heading2">FireWallet</h4>
|
||||
<h6 class="r-heading3">Python, Handshake, Plugin Architecture</h6>
|
||||
<ul class="r-body">
|
||||
<li>Developed a modular Python-based Handshake wallet with plugin support for extensibility.</li>
|
||||
<li>Presented at HandyCon 2024 and 2025, showcasing usability improvements and HNS site resolution.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
<h1 class="r-heading1">Projects</h1>
|
||||
<div class="noprintbreak">
|
||||
<h4 class="r-heading2">Server Lab</h4>
|
||||
<h6 class="r-heading3">Proxmox, Networking, Linux, DNS</h6>
|
||||
@@ -173,6 +164,15 @@
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="noprintbreak">
|
||||
<h4 class="r-heading2">FireWallet</h4>
|
||||
<h6 class="r-heading3">Python, Handshake, Plugin Architecture</h6>
|
||||
<ul class="r-body">
|
||||
<li>Developed a modular Python-based Handshake wallet with plugin support for extensibility.</li>
|
||||
<li>Presented at HandyCon 2024 and 2025, showcasing usability improvements and HNS site resolution.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<div>
|
||||
|
||||
@@ -66,6 +66,12 @@
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/now/25_06_19</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/now/25_08_15</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/now/25_10_23</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/now/old</loc>
|
||||
</url>
|
||||
@@ -75,6 +81,9 @@
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/donate</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/hosting</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/</loc>
|
||||
</url>
|
||||
@@ -87,4 +96,7 @@
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/resume</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://nathan.woodburn.au/tools</loc>
|
||||
</url>
|
||||
</urlset>
|
||||
20
templates/tools.ascii
Normal file
20
templates/tools.ascii
Normal file
@@ -0,0 +1,20 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m Tools [0m
|
||||
[1;36m────────────[0m
|
||||
|
||||
Here are some of the tools I use regularly — most of them are open source! 🛠️
|
||||
|
||||
{% for type, tools_in_type in tools | groupby('type') %}
|
||||
[4m[1;33m{{type}}[0m
|
||||
{% for tool in tools_in_type %}
|
||||
[1;33m{{tool.name}}[0m
|
||||
{{tool.description}}
|
||||
Website: {{tool.url}}
|
||||
{% if tool.demo_url %}Demo: {{tool.demo_url}}{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
{% endfor %}
|
||||
|
||||
|
||||
148
templates/tools.html
Normal file
148
templates/tools.html
Normal file
@@ -0,0 +1,148 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Tools | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/tools">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/tools">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta name="description" content="Check out some tools I use">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="masthead" style="background: url("/assets/img/bg/projects.webp") bottom / cover no-repeat;height: auto;padding-top: 20px;">
|
||||
<div style="margin-top: 150px;margin-bottom: 100px;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h1 class="brand-heading">Tools</h1>
|
||||
<p>Here is a list of applications, tools and services I use regularly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="text-center content-section" id="tools" style="padding-bottom: 100px;">
|
||||
<div class="container">{% for type, tools_in_type in tools | groupby('type') %}
|
||||
<h2 class="mt-4 mb-3 sticky-top bg-primary py-2 section-header" id="{{type}}">{{ type }}</h2>
|
||||
<div class="row">
|
||||
{% for tool in tools_in_type %}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card h-100 shadow-sm transition-all" style="transition: transform 0.2s, box-shadow 0.2s;" onmouseover="this.style.transform='translateY(-5px)'; this.style.boxShadow='0 0.5rem 1rem rgba(0,0,0,0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='';">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h4 class="card-title">{{tool.name}}</h4>
|
||||
<p class="card-text">{{ tool.description }}</p>
|
||||
<div class="btn-group gap-3 mt-auto" role="group">{% if tool.demo %}<button class="btn btn-primary" type="button" data-bs-target="#modal-{{tool.name}}" data-bs-toggle="modal" style="transition: transform 0.2s, background-color 0.2s;" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">View Demo</button>{% endif %}<a class="btn btn-primary" role="button" href="{{tool.url}}" target="_blank" style="transition: transform 0.2s, background-color 0.2s;" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">{{tool.name}} Website</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- Modals for this type -->
|
||||
{% for tool in tools_in_type %}
|
||||
{% if tool.demo %}
|
||||
<div id="modal-{{tool.name}}" class="modal fade" role="dialog" tabindex="-1" style="z-index: 1055;">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{tool.name}}</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ tool.demo | safe }}
|
||||
</div>
|
||||
<div class="modal-footer"><button class="btn btn-light" type="button" data-bs-dismiss="modal">Close</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const navbar = document.getElementById('mainNav');
|
||||
const headers = document.querySelectorAll('.section-header');
|
||||
|
||||
if (navbar) {
|
||||
const navbarHeight = navbar.offsetHeight;
|
||||
headers.forEach(header => {
|
||||
header.style.top = navbarHeight + 'px';
|
||||
header.style.zIndex = '100';
|
||||
header.style.scrollMarginTop = navbarHeight + 'px';
|
||||
});
|
||||
|
||||
// Handle hash navigation on page load
|
||||
if (window.location.hash) {
|
||||
setTimeout(() => {
|
||||
const target = document.querySelector(window.location.hash);
|
||||
if (target) {
|
||||
window.scrollTo({
|
||||
top: target.offsetTop - navbarHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</footer>{{handshake_scripts | safe}}
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
3
tests/README.md
Normal file
3
tests/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Tests
|
||||
|
||||
These tests use hurl. Note that the SOL tests are slow as they create transactions
|
||||
20
tests/api.hurl
Normal file
20
tests/api.hurl
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
GET http://127.0.0.1:5000/api/v1/tools
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.tools" count > 5
|
||||
9
tests/blog.hurl
Normal file
9
tests/blog.hurl
Normal 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
41
tests/legacy_api.hurl
Normal 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
24
tests/now.hurl
Normal 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.slow_hurl
Normal file
14
tests/sol.slow_hurl
Normal 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"}
|
||||
3
tests/test.sh
Executable file
3
tests/test.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
hurl --test *.hurl
|
||||
11
tests/well-known.hurl
Normal file
11
tests/well-known.hurl
Normal 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
|
||||
|
||||
257
tools.py
Normal file
257
tools.py
Normal file
@@ -0,0 +1,257 @@
|
||||
from flask import Request, render_template, jsonify, make_response
|
||||
import os
|
||||
from functools import lru_cache as cache
|
||||
import datetime
|
||||
from typing import Optional, Dict, Union, Tuple
|
||||
import re
|
||||
from dateutil.parser import parse
|
||||
import json
|
||||
|
||||
# HTTP status codes
|
||||
HTTP_OK = 200
|
||||
HTTP_BAD_REQUEST = 400
|
||||
HTTP_NOT_FOUND = 404
|
||||
|
||||
|
||||
def getClientIP(request: Request) -> str:
|
||||
"""
|
||||
Get the client's IP address from the request.
|
||||
|
||||
Args:
|
||||
request (Request): The Flask request object
|
||||
|
||||
Returns:
|
||||
str: The client's IP address
|
||||
"""
|
||||
x_forwarded_for = request.headers.get("X-Forwarded-For")
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(",")[0]
|
||||
else:
|
||||
ip = request.remote_addr
|
||||
if ip is None:
|
||||
ip = "unknown"
|
||||
return ip
|
||||
|
||||
@cache
|
||||
def getGitCommit() -> str:
|
||||
"""
|
||||
Get the current git commit hash.
|
||||
|
||||
Returns:
|
||||
str: The current git commit hash or a failure message
|
||||
"""
|
||||
# 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"
|
||||
|
||||
|
||||
def isCurl(request: Request) -> bool:
|
||||
"""
|
||||
Check if the request is from curl or hurl.
|
||||
|
||||
Args:
|
||||
request (Request): The Flask request object
|
||||
|
||||
Returns:
|
||||
bool: True if the request is from curl or hurl, False otherwise
|
||||
"""
|
||||
if request.headers and request.headers.get("User-Agent"):
|
||||
user_agent = request.headers.get("User-Agent", "")
|
||||
return "curl" in user_agent or "hurl" in user_agent
|
||||
return False
|
||||
|
||||
|
||||
def isCrawler(request: Request) -> bool:
|
||||
"""
|
||||
Check if the request is from a web crawler (e.g., Googlebot, Bingbot).
|
||||
|
||||
Args:
|
||||
request (Request): The Flask request object
|
||||
|
||||
Returns:
|
||||
bool: True if the request is from a web crawler, False otherwise
|
||||
"""
|
||||
if request.headers and request.headers.get("User-Agent"):
|
||||
user_agent = request.headers.get("User-Agent", "")
|
||||
return "Googlebot" in user_agent or "Bingbot" in user_agent
|
||||
return False
|
||||
|
||||
@cache
|
||||
def isDev(host: str) -> bool:
|
||||
"""
|
||||
Check if the host indicates a development environment.
|
||||
|
||||
Args:
|
||||
host (str): The host string from the request
|
||||
|
||||
Returns:
|
||||
bool: True if in development environment, False otherwise
|
||||
"""
|
||||
if (
|
||||
host == "localhost:5000"
|
||||
or host == "127.0.0.1:5000"
|
||||
or os.getenv("DEV") == "true"
|
||||
or host == "test.nathan.woodburn.au"
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@cache
|
||||
def getHandshakeScript(host: str) -> str:
|
||||
"""
|
||||
Get the handshake script HTML snippet.
|
||||
|
||||
Args:
|
||||
domain (str): The domain to use in the handshake script
|
||||
|
||||
Returns:
|
||||
str: The handshake script HTML snippet
|
||||
"""
|
||||
if isDev(host):
|
||||
return ""
|
||||
return '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>'
|
||||
|
||||
@cache
|
||||
def getAddress(coin: str) -> str:
|
||||
"""
|
||||
Get the wallet address for a cryptocurrency.
|
||||
|
||||
Args:
|
||||
coin (str): The cryptocurrency code
|
||||
|
||||
Returns:
|
||||
str: The wallet address or empty string if not found
|
||||
"""
|
||||
address = ""
|
||||
wallet_path = f".well-known/wallets/{coin.upper()}"
|
||||
if os.path.isfile(wallet_path):
|
||||
with open(wallet_path) as file:
|
||||
address = file.read()
|
||||
return address
|
||||
|
||||
|
||||
@cache
|
||||
def getFilePath(name: str, path: str) -> Optional[str]:
|
||||
"""
|
||||
Find a file in a directory tree.
|
||||
|
||||
Args:
|
||||
name (str): The filename to find
|
||||
path (str): The root directory to search
|
||||
|
||||
Returns:
|
||||
Optional[str]: The full path to the file or None if not found
|
||||
"""
|
||||
for root, dirs, files in os.walk(path):
|
||||
if name in files:
|
||||
return os.path.join(root, name)
|
||||
return None
|
||||
|
||||
|
||||
def json_response(request: Request, message: Union[str, Dict] = "404 Not Found", code: int = 404):
|
||||
"""
|
||||
Create a JSON response with standard formatting.
|
||||
|
||||
Args:
|
||||
request (Request): The Flask request object
|
||||
message (Union[str, Dict]): The response message or data
|
||||
code (int): The HTTP status code
|
||||
|
||||
Returns:
|
||||
Tuple[Dict, int]: The JSON response and HTTP status code
|
||||
"""
|
||||
if isinstance(message, dict):
|
||||
# Add status and ip to dict
|
||||
message["status"] = code
|
||||
message["ip"] = getClientIP(request)
|
||||
return jsonify(message), code
|
||||
|
||||
return jsonify({
|
||||
"status": code,
|
||||
"message": message,
|
||||
"ip": getClientIP(request),
|
||||
}), code
|
||||
|
||||
|
||||
def error_response(
|
||||
request: Request,
|
||||
message: str = "404 Not Found",
|
||||
code: int = 404,
|
||||
force_json: bool = False
|
||||
) -> Union[Tuple[Dict, int], object]:
|
||||
"""
|
||||
Create an error response in JSON or HTML format.
|
||||
|
||||
Args:
|
||||
request (Request): The Flask request object
|
||||
message (str): The error message
|
||||
code (int): The HTTP status code
|
||||
force_json (bool): Whether to force JSON response regardless of client
|
||||
|
||||
Returns:
|
||||
Union[Tuple[Dict, int], object]: The JSON or HTML response
|
||||
"""
|
||||
if force_json or isCurl(request):
|
||||
return json_response(request, message, code)
|
||||
|
||||
# Check if <error code>.html exists in templates
|
||||
template_name = f"{code}.html" if os.path.isfile(
|
||||
f"templates/{code}.html") else "404.html"
|
||||
response = make_response(render_template(
|
||||
template_name, code=code, message=message), code)
|
||||
|
||||
# Add message to response headers
|
||||
response.headers["X-Error-Message"] = message
|
||||
return response
|
||||
|
||||
|
||||
def parse_date(date_groups: list[str]) -> str | None:
|
||||
"""
|
||||
Parse a list of date components into YYYY-MM-DD format.
|
||||
Uses dateutil.parser for robust parsing.
|
||||
Works for:
|
||||
- DD Month YYYY
|
||||
- Month DD, YYYY
|
||||
- YYYY-MM-DD
|
||||
- YYYYMMDD
|
||||
- Month YYYY (defaults day to 1)
|
||||
- Handles ordinal suffixes (st, nd, rd, th)
|
||||
"""
|
||||
try:
|
||||
# Join date groups into a single string
|
||||
date_str = " ".join(date_groups).strip()
|
||||
|
||||
# Remove ordinal suffixes
|
||||
date_str = re.sub(r'(\d+)(st|nd|rd|th)', r'\1',
|
||||
date_str, flags=re.IGNORECASE)
|
||||
|
||||
# Parse with dateutil, default day=1 if missing
|
||||
dt = parse(date_str, default=datetime.datetime(1900, 1, 1))
|
||||
|
||||
# If year is missing, parse will fallback to 1900 → reject
|
||||
if dt.year == 1900:
|
||||
return None
|
||||
|
||||
return dt.strftime("%Y-%m-%d")
|
||||
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
def get_tools_data():
|
||||
with open("data/tools.json", "r") as f:
|
||||
return json.load(f)
|
||||
Reference in New Issue
Block a user