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

    {page.replace("_"," ")}

    +

    {page.replace("_", " ")}

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