diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml new file mode 100644 index 0000000..858de65 --- /dev/null +++ b/.gitea/workflows/check.yml @@ -0,0 +1,18 @@ +name: Check Code Quality +run-name: Ruff CI +on: + push: + +jobs: + RuffCheck: + runs-on: [ubuntu-latest, amd] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + run: | + apt update + apt install -y python3 python3-pip + - name: Install Ruff + run: pip install ruff + - name: Run Ruff + run: ruff check . diff --git a/NathanWoodburn.bsdesign b/NathanWoodburn.bsdesign new file mode 100644 index 0000000..e04683e Binary files /dev/null and b/NathanWoodburn.bsdesign differ diff --git a/blueprints/acme.py b/blueprints/acme.py index f543551..bf1e3c2 100644 --- a/blueprints/acme.py +++ b/blueprints/acme.py @@ -3,10 +3,10 @@ import os from cloudflare import Cloudflare from tools import json_response -acme_bp = Blueprint('acme', __name__) +app = Blueprint('acme', __name__) -@acme_bp.route("/hnsdoh-acme", methods=["POST"]) +@app.route("/hnsdoh-acme", methods=["POST"]) def post(): # Get the TXT record from the request if not request.is_json or not request.json: diff --git a/blueprints/api.py b/blueprints/api.py index a8a00c9..15ce9f9 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -5,7 +5,7 @@ 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 blueprints import sol from dateutil import parser as date_parser from blueprints.spotify import get_spotify_track @@ -17,9 +17,9 @@ HTTP_NOT_FOUND = 404 HTTP_UNSUPPORTED_MEDIA = 415 HTTP_SERVER_ERROR = 500 -api_bp = Blueprint('api', __name__) +app = Blueprint('api', __name__, url_prefix='/api/v1') # Register solana blueprint -api_bp.register_blueprint(sol_bp) +app.register_blueprint(sol.app) # Load configuration NC_CONFIG = requests.get( @@ -30,8 +30,8 @@ if 'time-zone' not in NC_CONFIG: NC_CONFIG['time-zone'] = 10 -@api_bp.route("/") -@api_bp.route("/help") +@app.route("/", strict_slashes=False) +@app.route("/help") def help(): """Provide API documentation and help.""" return jsonify({ @@ -57,18 +57,18 @@ def help(): "status": HTTP_OK }) -@api_bp.route("/status") -@api_bp.route("/ping") +@app.route("/status") +@app.route("/ping") def status(): return json_response(request, "200 OK", HTTP_OK) -@api_bp.route("/version") +@app.route("/version") def version(): """Get the current version of the website.""" return jsonify({"version": getGitCommit()}) -@api_bp.route("/time") +@app.route("/time") def time(): """Get the current time in the configured timezone.""" timezone_offset = datetime.timedelta(hours=NC_CONFIG["time-zone"]) @@ -84,7 +84,7 @@ def time(): }) -@api_bp.route("/timezone") +@app.route("/timezone") def timezone(): """Get the current timezone setting.""" return jsonify({ @@ -94,7 +94,7 @@ def timezone(): }) -@api_bp.route("/message") +@app.route("/message") def message(): """Get the message from the configuration.""" return jsonify({ @@ -104,7 +104,7 @@ def message(): }) -@api_bp.route("/ip") +@app.route("/ip") def ip(): """Get the client's IP address.""" return jsonify({ @@ -113,7 +113,7 @@ def ip(): }) -@api_bp.route("/email", methods=["POST"]) +@app.route("/email", methods=["POST"]) def email_post(): """Send an email via the API (requires API key).""" # Verify json @@ -135,7 +135,7 @@ def email_post(): return sendEmail(data) -@api_bp.route("/project") +@app.route("/project") def project(): """Get information about the current git project.""" gitinfo = { @@ -168,7 +168,7 @@ def project(): "status": HTTP_OK }) -@api_bp.route("/tools") +@app.route("/tools") def tools(): """Get a list of tools used by Nathan Woodburn.""" try: @@ -176,15 +176,10 @@ def tools(): 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("/playing") +@app.route("/playing") def playing(): """Get the currently playing Spotify track.""" track_info = get_spotify_track() @@ -193,7 +188,7 @@ def playing(): return json_response(request, {"spotify": track_info}, HTTP_OK) -@api_bp.route("/headers") +@app.route("/headers") def headers(): """Get the request headers.""" headers = dict(request.headers) @@ -216,7 +211,7 @@ def headers(): "status": HTTP_OK }) -@api_bp.route("/page_date") +@app.route("/page_date") def page_date(): url = request.args.get("url") if not url: diff --git a/blueprints/blog.py b/blueprints/blog.py index c94dc6f..73105ea 100644 --- a/blueprints/blog.py +++ b/blueprints/blog.py @@ -5,7 +5,7 @@ from bs4 import BeautifulSoup import re from tools import isCLI, getClientIP, getHandshakeScript -blog_bp = Blueprint('blog', __name__) +app = Blueprint('blog', __name__, url_prefix='/blog') def list_page_files(): @@ -108,7 +108,7 @@ def render_home(handshake_scripts: str | None = None): ) -@blog_bp.route("/") +@app.route("/", strict_slashes=False) def index(): if not isCLI(request): return render_home(handshake_scripts=getHandshakeScript(request.host)) @@ -129,7 +129,7 @@ def index(): }), 200 -@blog_bp.route("/") +@app.route("/") def path(path): if not isCLI(request): return render_page(path, handshake_scripts=getHandshakeScript(request.host)) @@ -152,7 +152,7 @@ def path(path): }), 200 -@blog_bp.route("/.md") +@app.route("/.md") def path_md(path): if not os.path.exists(f"data/blog/{path}.md"): return render_template("404.html"), 404 diff --git a/blueprints/now.py b/blueprints/now.py index 913626c..d6e4208 100644 --- a/blueprints/now.py +++ b/blueprints/now.py @@ -1,10 +1,13 @@ from flask import Blueprint, render_template, make_response, request, jsonify import datetime import os -from tools import getHandshakeScript +from tools import getHandshakeScript, error_response, isCLI +from curl import get_header, MAX_WIDTH +from bs4 import BeautifulSoup +import re # Create blueprint -now_bp = Blueprint('now', __name__) +app = Blueprint('now', __name__, url_prefix='/now') def list_page_files(): @@ -44,27 +47,115 @@ def render(date, handshake_scripts=None): date = date.removesuffix(".html") if date not in list_dates(): - return render_template("404.html"), 404 + return error_response(request) 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) +def render_curl(date=None): + # If the date is not available, render the latest page + if date is None: + date = get_latest_date() -@now_bp.route("/") + # Remove .html if present + date = date.removesuffix(".html") + + if date not in list_dates(): + return error_response(request) + + # Format the date nicely + date_formatted = datetime.datetime.strptime(date, "%y_%m_%d") + date_formatted = date_formatted.strftime("%A, %B %d, %Y") + + # Load HTML + with open(f"templates/now/{date}.html", "r", encoding="utf-8") as f: + raw_html = f.read().replace("{{ date }}", date_formatted) + soup = BeautifulSoup(raw_html, 'html.parser') + + posts = [] + + # Find divs matching your pattern + divs = soup.find_all("div", style=re.compile(r"max-width:\s*700px", re.IGNORECASE)) + if not divs: + return error_response(request, message="No content found for CLI rendering.") + + for div in divs: + # header could be h1/h2/h3 inside the div + header_tag = div.find(["h1", "h2", "h3"]) # type: ignore + # content is usually one or more

tags inside the div + p_tags = div.find_all("p") # type: ignore + + if header_tag and p_tags: + header_text = header_tag.get_text(strip=True) # type: ignore + content_lines = [] + + for p in p_tags: + # Extract text + text = p.get_text(strip=False) + + # Extract any links in the paragraph + links = [a.get("href") for a in p.find_all("a", href=True)] # type: ignore + # Set max width for text wrapping + + # Wrap text manually + wrapped_lines = [] + for line in text.splitlines(): + while len(line) > MAX_WIDTH: + # Find last space within max_width + split_at = line.rfind(' ', 0, MAX_WIDTH) + if split_at == -1: + split_at = MAX_WIDTH + wrapped_lines.append(line[:split_at].rstrip()) + line = line[split_at:].lstrip() + wrapped_lines.append(line) + text = "\n".join(wrapped_lines) + + if links: + text += "\nLinks: " + ", ".join(links) # type: ignore + + content_lines.append(text) + + content_text = "\n\n".join(content_lines) + posts.append({"header": header_text, "content": content_text}) + + # Build final response + response = "" + for post in posts: + response += f"{post['header']}\n\n{post['content']}\n\n" + + return render_template("now.ascii", date=date_formatted, content=response, header=get_header()) + + + +@app.route("/", strict_slashes=False) def index(): + if isCLI(request): + return render_curl() return render_latest(handshake_scripts=getHandshakeScript(request.host)) -@now_bp.route("/") +@app.route("/") def path(path): + if isCLI(request): + return render_curl(path) + return render(path, handshake_scripts=getHandshakeScript(request.host)) -@now_bp.route("/old") -@now_bp.route("/old/") +@app.route("/old", strict_slashes=False) def old(): now_dates = list_dates()[1:] + if isCLI(request): + response = "" + for date in now_dates: + link = date + date_fmt = datetime.datetime.strptime(date, "%y_%m_%d") + date_fmt = date_fmt.strftime("%A, %B %d, %Y") + response += f"{date_fmt} - /now/{link}\n" + return render_template("now.ascii", date="Old Now Pages", content=response, header=get_header()) + + html = '

    ' html += f'
  • {get_latest_date(True)}
  • ' @@ -80,9 +171,9 @@ def old(): ) -@now_bp.route("/now.rss") -@now_bp.route("/now.xml") -@now_bp.route("/rss.xml") +@app.route("/now.rss") +@app.route("/now.xml") +@app.route("/rss.xml") def rss(): host = "https://" + request.host if ":" in request.host: @@ -99,7 +190,7 @@ def rss(): return make_response(rss, 200, {"Content-Type": "application/rss+xml"}) -@now_bp.route("/now.json") +@app.route("/now.json") def json(): now_pages = list_page_files() host = "https://" + request.host diff --git a/blueprints/podcast.py b/blueprints/podcast.py index 2810062..3411d9f 100644 --- a/blueprints/podcast.py +++ b/blueprints/podcast.py @@ -2,9 +2,9 @@ from flask import Blueprint, make_response, request from tools import error_response import requests -podcast_bp = Blueprint('podcast', __name__) +app = Blueprint('podcast', __name__) -@podcast_bp.route("/ID1") +@app.route("/ID1") def index(): # Proxy to ID1 url req = requests.get("https://podcasts.c.woodburn.au/ID1") @@ -16,7 +16,7 @@ def index(): ) -@podcast_bp.route("/ID1/") +@app.route("/ID1/") def contents(): # Proxy to ID1 url req = requests.get("https://podcasts.c.woodburn.au/ID1/") @@ -27,7 +27,7 @@ def contents(): ) -@podcast_bp.route("/ID1/") +@app.route("/ID1/") def path(path): # Proxy to ID1 url req = requests.get("https://podcasts.c.woodburn.au/ID1/" + path) @@ -38,7 +38,7 @@ def path(path): ) -@podcast_bp.route("/ID1.xml") +@app.route("/ID1.xml") def xml(): # Proxy to ID1 url req = requests.get("https://podcasts.c.woodburn.au/ID1.xml") @@ -49,7 +49,7 @@ def xml(): ) -@podcast_bp.route("/podsync.opml") +@app.route("/podsync.opml") def podsync(): req = requests.get("https://podcasts.c.woodburn.au/podsync.opml") if req.status_code != 200: diff --git a/blueprints/sol.py b/blueprints/sol.py index f64e8a6..7ad7b94 100644 --- a/blueprints/sol.py +++ b/blueprints/sol.py @@ -9,7 +9,7 @@ import binascii import base64 import os -sol_bp = Blueprint('sol', __name__) +app = Blueprint('sol', __name__) SOLANA_HEADERS = { "Content-Type": "application/json", @@ -55,7 +55,7 @@ def get_solana_address() -> str: 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"]) +@app.route("/donate", methods=["GET", "OPTIONS"]) def sol_donate(): data = { "icon": "https://nathan.woodburn.au/assets/img/profile.png", @@ -90,7 +90,7 @@ def sol_donate(): return response -@sol_bp.route("/donate/") +@app.route("/donate/") def sol_donate_amount(amount): data = { "icon": "https://nathan.woodburn.au/assets/img/profile.png", @@ -101,7 +101,7 @@ def sol_donate_amount(amount): return jsonify(data), 200, SOLANA_HEADERS -@sol_bp.route("/donate/", methods=["POST"]) +@app.route("/donate/", methods=["POST"]) def sol_donate_post(amount): if not request.json: diff --git a/blueprints/spotify.py b/blueprints/spotify.py index 677e0ca..d4c402a 100644 --- a/blueprints/spotify.py +++ b/blueprints/spotify.py @@ -5,7 +5,7 @@ import requests import time import base64 -spotify_bp = Blueprint('spotify', __name__) +app = Blueprint('spotify', __name__, url_prefix='/spotify') CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID") CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET") @@ -48,7 +48,7 @@ def refresh_access_token(): TOKEN_EXPIRES = time.time() + token_info.get("expires_in", 3600) return ACCESS_TOKEN -@spotify_bp.route("/login") +@app.route("/login") def login(): auth_query = ( f"{SPOTIFY_AUTH_URL}?response_type=code&client_id={CLIENT_ID}" @@ -56,7 +56,7 @@ def login(): ) return redirect(auth_query) -@spotify_bp.route("/callback") +@app.route("/callback") def callback(): code = request.args.get("code") if not code: @@ -89,8 +89,8 @@ def callback(): print("Refresh Token:", REFRESH_TOKEN) return redirect(url_for("spotify.currently_playing")) -@spotify_bp.route("/") -@spotify_bp.route("/currently-playing") +@app.route("/", strict_slashes=False) +@app.route("/playing") def currently_playing(): """Public endpoint showing your current track.""" track = get_spotify_track() diff --git a/blueprints/template.py b/blueprints/template.py index beb6da9..10243a8 100644 --- a/blueprints/template.py +++ b/blueprints/template.py @@ -1,9 +1,9 @@ from flask import Blueprint, request from tools import json_response -template_bp = Blueprint('template', __name__) +app = Blueprint('template', __name__) -@template_bp.route("/") +@app.route("/", strict_slashes=False) def index(): return json_response(request, "Success", 200) \ No newline at end of file diff --git a/blueprints/wellknown.py b/blueprints/wellknown.py index 2007f4d..05edc96 100644 --- a/blueprints/wellknown.py +++ b/blueprints/wellknown.py @@ -1,15 +1,16 @@ -from flask import Blueprint, render_template, make_response, request, jsonify, send_from_directory, redirect +from flask import Blueprint, make_response, request, jsonify, send_from_directory, redirect +from tools import error_response import os -wk_bp = Blueprint('well-known', __name__) +app = Blueprint('well-known', __name__, url_prefix='/.well-known') -@wk_bp.route("/") +@app.route("/") def index(path): return send_from_directory(".well-known", path) -@wk_bp.route("/wallets/") +@app.route("/wallets/") def wallets(path): if path[0] == "." and 'proof' not in path: return send_from_directory( @@ -25,10 +26,10 @@ def wallets(path): if os.path.isfile(".well-known/wallets/" + path.upper()): return redirect("/.well-known/wallets/" + path.upper(), code=302) - return render_template("404.html"), 404 + return error_response(request) -@wk_bp.route("/nostr.json") +@app.route("/nostr.json") def nostr(): # Get name parameter name = request.args.get("name") @@ -50,7 +51,7 @@ def nostr(): ) -@wk_bp.route("/xrp-ledger.toml") +@app.route("/xrp-ledger.toml") def xrp(): # Create a response with the xrp-ledger.toml file with open(".well-known/xrp-ledger.toml") as file: diff --git a/curl.py b/curl.py index e3a196c..e1ec9b6 100644 --- a/curl.py +++ b/curl.py @@ -6,6 +6,8 @@ import requests from blueprints.spotify import get_spotify_track +MAX_WIDTH = 80 + def clean_path(path:str): path = path.strip("/ ").lower() # Strip any .html extension @@ -115,7 +117,6 @@ def curl_response(request): 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'} diff --git a/data/tools.json b/data/tools.json index cbe9483..1878421 100644 --- a/data/tools.json +++ b/data/tools.json @@ -33,55 +33,50 @@ "name": "Zellij", "type": "Terminal Tools", "url": "https://zellij.dev/", - "description": "A terminal workspace and multiplexer" + "description": "A terminal workspace and multiplexer", + "demo": "https://asciinema.c.woodburn.au/a/10" }, { "name": "Fx", "type": "Terminal Tools", "url": "https://fx.wtf/", "description": "A command-line JSON viewer and processor", - "demo": "", - "demo_url": "https://asciinema.c.woodburn.au/a/4" + "demo": "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": "", - "demo_url": "https://asciinema.c.woodburn.au/a/5" + "demo": "https://asciinema.c.woodburn.au/a/5" }, { "name": "Atuin", "type": "Terminal Tools", "url": "https://atuin.sh/", "description": "A next-generation shell history manager", - "demo": "", - "demo_url": "https://asciinema.c.woodburn.au/a/6" + "demo": "https://asciinema.c.woodburn.au/a/6" }, { "name": "Tmate", "type": "Terminal Tools", "url": "https://tmate.io/", "description": "Instant terminal sharing", - "demo": "", - "demo_url": "https://asciinema.c.woodburn.au/a/7" + "demo": "https://asciinema.c.woodburn.au/a/7" }, { "name": "Eza", "type": "Terminal Tools", "url": "https://eza.rocks/", "description": "A modern replacement for 'ls'", - "demo": "", - "demo_url": "https://asciinema.c.woodburn.au/a/8" + "demo": "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": "", - "demo_url": "https://asciinema.c.woodburn.au/a/9" + "demo": "https://asciinema.c.woodburn.au/a/9" }, { "name": "Oh My Zsh", @@ -173,4 +168,4 @@ "url": "https://github.com/dani-garcia/vaultwarden", "description": "Password manager server implementation compatible with Bitwarden clients" } -] \ No newline at end of file +] diff --git a/server.py b/server.py index b4b017f..ecaf549 100644 --- a/server.py +++ b/server.py @@ -19,13 +19,7 @@ from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_H from ansi2html import Ansi2HTMLConverter from PIL import Image # Import blueprints -from blueprints.now import now_bp -from blueprints.blog import blog_bp -from blueprints.wellknown import wk_bp -from blueprints.api import api_bp -from blueprints.podcast import podcast_bp -from blueprints.acme import acme_bp -from blueprints.spotify import spotify_bp +from blueprints import now, blog, wellknown, api, podcast, acme, spotify from tools import isCLI, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getHandshakeScript, get_tools_data from curl import curl_response @@ -33,13 +27,9 @@ app = Flask(__name__) CORS(app) # Register blueprints -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') -app.register_blueprint(podcast_bp) -app.register_blueprint(acme_bp) -app.register_blueprint(spotify_bp, url_prefix='/spotify') +for module in [now, blog, wellknown, api, podcast, acme, spotify]: + app.register_blueprint(module.app) + dotenv.load_dotenv() @@ -54,7 +44,11 @@ RATE_LIMIT_WINDOW = 3600 # 1 hour in seconds RESTRICTED_ROUTES = ["ascii"] REDIRECT_ROUTES = { - "contact": "/#contact" + "contact": "/#contact", + "old": "/now/old", + "/meet": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", + "/meeting": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", + "/appointment": "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", } DOWNLOAD_ROUTES = { "pgp": "data/nathanwoodburn.asc" @@ -187,21 +181,15 @@ def serviceWorker(): # region Misc routes - - -@app.route("/meet") -@app.route("/meeting") -@app.route("/appointment") -def meetingLink(): - return redirect( - "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", code=302 - ) - - @app.route("/links") def links(): return render_template("link.html") +@app.route("/actions.json") +def sol_actions(): + return jsonify( + {"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]} + ) @app.route("/api/") def api_legacy(function): @@ -212,13 +200,6 @@ def api_legacy(function): return redirect(f"/api/v1/{function}", code=301) return error_response(request, message="404 Not Found", code=404) - -@app.route("/actions.json") -def sol_actions(): - return jsonify( - {"rules": [{"pathPattern": "/donate**", "apiPath": "/api/v1/donate**"}]} - ) - # endregion # region Main routes @@ -262,7 +243,7 @@ def index(): 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_AUTH") if os.getenv("GIT_AUTH") else os.getenv("git_token")}, + headers={"Authorization": os.getenv("GIT_AUTH")}, ) git = git.json() git = git[0] @@ -337,7 +318,7 @@ def index(): repo_name = "Nathan.Woodburn/" html_url = git["repo"]["html_url"] - repo = '' + repo_name + "" + repo = '' + repo_name + "" # Get time timezone_offset = datetime.timedelta(hours=NC_CONFIG["time-zone"]) @@ -385,7 +366,7 @@ def index(): sites=SITES, projects=PROJECTS, time=time, - message=NC_CONFIG.get("message",""), + message=NC_CONFIG.get("message", ""), ), 200, {"Content-Type": "text/html"}, @@ -395,6 +376,8 @@ def index(): return resp # region Donate + + @app.route("/donate") def donate(): if isCLI(request): @@ -560,6 +543,7 @@ def qrcodee(data): # endregion + @app.route("/supersecretpath") def supersecretpath(): ascii_art = "" @@ -685,6 +669,7 @@ def resume_pdf(): return send_file("data/resume.pdf") return error_response(request, message="Resume not found") + @app.route("/tools") def tools(): if isCLI(request): @@ -695,11 +680,9 @@ def tools(): # region Error Catching # Catch all for GET requests - - @app.route("/") def catch_all(path: str): - + if path.lower().replace(".html", "") in RESTRICTED_ROUTES: return error_response(request, message="Restricted route", code=403) diff --git a/templates/assets/css/tools.min.css b/templates/assets/css/tools.min.css new file mode 100644 index 0000000..9278d8f --- /dev/null +++ b/templates/assets/css/tools.min.css @@ -0,0 +1 @@ +.card:hover{transform:translateY(-5px);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);transition:transform .2s,box-shadow .2s}.btn:hover{transform:scale(1.05);transition:transform .2s} \ No newline at end of file diff --git a/templates/header.ascii b/templates/header.ascii index e349bed..e7f21ab 100644 --- a/templates/header.ascii +++ b/templates/header.ascii @@ -9,4 +9,5 @@ Contact [/contact] Projects [/projects] Tools [/tools] Donate [/donate] +Now [/now] diff --git a/templates/index.ascii b/templates/index.ascii index ea46740..51afe40 100644 --- a/templates/index.ascii +++ b/templates/index.ascii @@ -9,7 +9,8 @@ Contact [/contact] Projects [/projects] Tools [/tools] Donate [/donate] -API [/api/v1/] +Now [/now] +API [/api/v1] ───────────────────────────────────────────────  ABOUT ME  diff --git a/templates/index.html b/templates/index.html index ab06300..5b25b97 100644 --- a/templates/index.html +++ b/templates/index.html @@ -432,7 +432,7 @@ let currentTrackId = null; // --- Spotify fetch --- async function updateSpotifyWidget() { try { - const res = await fetch('/spotify/'); + const res = await fetch('/api/v1/playing'); if (!res.ok) return; const data = await res.json(); diff --git a/templates/now.ascii b/templates/now.ascii new file mode 100644 index 0000000..e42b6ce --- /dev/null +++ b/templates/now.ascii @@ -0,0 +1,6 @@ +{{header}} +─────────────────────────────────────────────── + Now {{ date }}  +──────────── + +{{content | safe}} \ No newline at end of file diff --git a/templates/tools.ascii b/templates/tools.ascii index 5840c19..a5145fc 100644 --- a/templates/tools.ascii +++ b/templates/tools.ascii @@ -11,7 +11,7 @@ Here are some of the tools I use regularly — most of them are open source! {{tool.name}} {{tool.description}} Website: {{tool.url}} -{% if tool.demo_url %}Demo: {{tool.demo_url}}{% endif %} +{% if tool.demo %}Demo: {{tool.demo}}{% endif %} {% endfor %} ─────────────────────────────────────────────── diff --git a/templates/tools.html b/templates/tools.html index 479752a..886df95 100644 --- a/templates/tools.html +++ b/templates/tools.html @@ -35,6 +35,7 @@ + @@ -76,11 +77,15 @@
    {% for tool in tools_in_type %}
    -
    +

    {{tool.name}}

    {{ tool.description }}

    -
    {% if tool.demo %}{% endif %}{{tool.name}} Website
    +
    {% if tool.demo %}{% endif %}{{tool.name}} Website
    @@ -89,49 +94,110 @@ {% for tool in tools_in_type %} {% if tool.demo %} - {% endif %} {% endfor %} {% endfor %}
    diff --git a/tools.py b/tools.py index 5f09a6a..0354241 100644 --- a/tools.py +++ b/tools.py @@ -210,7 +210,6 @@ def json_response(request: Request, message: Union[str, Dict] = "404 Not Found", "ip": getClientIP(request), }), code - def error_response( request: Request, message: str = "404 Not Found",