112 lines
No EOL
5.7 KiB
Python
112 lines
No EOL
5.7 KiB
Python
#!/bin/env python3
|
|
import requests, traceback
|
|
from urllib.parse import urlparse, urlunparse, quote
|
|
from flask import Flask, redirect, abort, render_template, request, make_response, send_file
|
|
app = Flask(__name__)
|
|
|
|
def createSpecial(url):
|
|
temp = [url, url]
|
|
temp[0] = "/".join(temp[0].split("/")[:3]) + "/.well-known/fursona"
|
|
temp[1] = "/".join(temp[0].split("/")[:3]) + "/.well-known/fursona.json"
|
|
return temp
|
|
|
|
@app.errorhandler(404)
|
|
def notfound(e):
|
|
theme = request.cookies.get('theme') # The current theme
|
|
return render_template("error.html", message=f"Error 404 - You seem lost.", theme=theme), 404
|
|
|
|
@app.errorhandler(500)
|
|
def notfound(e):
|
|
theme = request.cookies.get('theme') # The current theme
|
|
return render_template("error.html", message=f"Error 500 - Went kaboom.", theme=theme), 500
|
|
|
|
@app.route("/logo.svg")
|
|
def logo_svg():
|
|
return send_file("logo.svg")
|
|
|
|
@app.route("/fallback.svg")
|
|
def fallback_avatar():
|
|
return send_file("fallback.svg")
|
|
|
|
@app.route("/logo.png")
|
|
def logo_png():
|
|
return send_file("logo.png")
|
|
|
|
@app.route("/favicon.ico")
|
|
def favicon_logo_bind():
|
|
return logo_png()
|
|
|
|
@app.route("/")
|
|
def home():
|
|
theme = request.cookies.get('theme') # The current theme
|
|
if theme == None:
|
|
theme = "light" # Fallback
|
|
return render_template("home.html", theme=theme, color="#000000" if theme == "light" else "#FFFFFF")
|
|
|
|
@app.route("/switch")
|
|
def switchTheme():
|
|
theme = request.cookies.get('theme') # The current theme
|
|
resp = make_response(redirect("/"))
|
|
resp.set_cookie("theme", "dark" if theme in ["light", None] else "light")
|
|
return resp
|
|
|
|
@app.route("/search")
|
|
def search():
|
|
theme = request.cookies.get('theme') # The current theme
|
|
url_raw = request.args.get('url')
|
|
if url_raw in [None, ""]:
|
|
return redirect("/")
|
|
url = urlparse(url_raw)
|
|
if url.scheme in ["http", "https"]:
|
|
try:
|
|
html = f'<!DOCTYPE html><html lang="en-us"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="View the fursona(s) in {url.hostname}"><link href="//cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><title>{url.hostname} - Bootstrap \'Sona</title></head><body data-bs-theme="{theme}"><center>'
|
|
stat_urls = [ # Grab some URLs to try
|
|
url_raw,
|
|
*createSpecial(url_raw)
|
|
]
|
|
ok = False
|
|
for uri in stat_urls:
|
|
req = requests.get(uri)
|
|
try:
|
|
res = req.json() # Pass if JSON parses correctly
|
|
ok2 = True
|
|
except:
|
|
ok2 = False
|
|
|
|
if req.ok and ok2: # If status code is OK too
|
|
ok = True
|
|
break
|
|
if not ok:
|
|
return render_template("error.html", message="I was unable to find a correct URL for this site, does a fursona.json exist?\nall URLs attempted:\n" + "\n".join(stat_urls), theme=theme), 404
|
|
if not "sonas" in res: # If sonas doesn't exist in the JSON file (shown in schema)
|
|
return render_template("error.html", message="This site has a fursona file, but the JSON doesn't have a \"sonas\" property.", theme=theme), 500
|
|
for sona in res["sonas"]:
|
|
# Avatar image
|
|
img = f'<img src="{sona["avatar"]}" alt="{sona["avatarAlt"] if "avatarAlt" in sona else "No alt text"}" title="{sona["avatarAlt"] if "avatarAlt" in sona else "No alt text"}" class="card-img-top">' if "avatar" in sona else f'<img src="/fallback.svg" alt="NO AVATAR" title="NO AVATAR" class="card-img-top">'
|
|
if "avatarAttribution" in sona:
|
|
img += f'<div class="card-body text-muted">{sona["avatarAttribution"]}</div>'
|
|
|
|
# Colors
|
|
if not "colors" in sona:
|
|
colors = ""
|
|
else:
|
|
colors = ""
|
|
for color in sona["colors"]:
|
|
# TODO: Replace the info icons with bootstrap-sona logos
|
|
colors += f'<svg width="24px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="{color}" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg> {color}<br><br>'
|
|
# Reference sheet (link)
|
|
ref = f'<a href="{sona["ref"]}" title="{sona["refAlt"] if "refAlt" in sona else "No alt text"}" class="btn btn-primary">Reference sheet</a>' if "ref" in sona else ""
|
|
# Age
|
|
age = f'<p>Age: {sona["age"]}</p>' if "age" in sona else ""
|
|
# Everything else.
|
|
html += f'<div class="card" style="width: 24rem;">{img}<div class="card-body"><h5 class="card-title">{sona["name"]}</h5><p class="card-text text-muted">{sona["species"]} ∙ {sona["gender"]} ∙ {sona["pronouns"]}</p><p class="card-text">{sona["description"]}</p>{age}{colors}{ref}</div></div>'
|
|
return html + "<hr><a href=\"/\">Back</a></center></body></html>"
|
|
except:
|
|
return render_template("error.html", message=traceback.format_exc(), theme=theme), 500
|
|
else:
|
|
return render_template("error.html", message=f"Unrecognised URL sheme {url.scheme}", theme=theme), 500
|
|
|
|
# If not called by a WSGI/ASGI framework...
|
|
if __name__ == '__main__':
|
|
# ...Run the app with Flask's development server
|
|
app.run(host='127.0.0.1',port=2011) |