from flask import request, session, Blueprint, Response, redirect, escape from htmlpy import * from config import CONFIG from os.path import exists import os import datetime import requests import gnupg main_pages = Blueprint("main", __name__) def download_file(url, file): os.makedirs("/app/static/", exist_ok=True) r = requests.get(url, allow_redirects=True) open(file, "wb").write(r.content) # Downloads all bootstrap files to flasks static directory def cache_bootstrap(): download_file( "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css", "/app/static/bootstrap.min.css", ) download_file( "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css", "/app/static/bootstrap-icons.css", ) download_file( "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js", "/app/static/bootstrap.bundle.min.js", ) # Bootstrap CSS BOOTSTRAP = [ Reference( "/static/bootstrap.min.css", "stylesheet", ), Reference( "/static/bootstrap-icons.css", "stylesheet", ), Script(src="/static/bootstrap.bundle.min.js"), ] # Encrypt msg with GPG def encrypt(msg): pgp = gnupg.GPG() pgp.import_keys(open("/config/pub.key").read()) return str(pgp.encrypt(msg, pgp.list_keys()[0]["fingerprint"])) # Save msg in `/data/messages` def save_message(msg, name=""): os.makedirs("/data/messages", exist_ok=True) dt = datetime.datetime.now().strftime("%Y-%m-%d.%H-%M") f = open(f"/data/messages/{name}-{dt}.asc", "w") f.write(msg) # Send Notification to all handlers def notify(msg, title=None): try: gotify_notification(msg, title) except: pass # Gotify Notification Handler def gotify_notification(msg, title=None): token = CONFIG["notify"]["gotify"]["token"] url = CONFIG["notify"]["gotify"]["host"] requests.post( f"https://{url}/message?token={token}", {"title": title, "message": msg, "priority": "5"}, ) # Message Sending Page @main_pages.route("/message", methods=["GET", "POST"]) def send_message(): if request.method == "POST": # If POST handle message msg = request.form["message"] name = request.form["msg_name"] if msg is not None: cipher = encrypt(msg) save_message(cipher, name) notify(f"New Message from {name}") return redirect(request.url_root) else: # Return Message Form return buildSite( [ Div( [ Heading(1, "Message"), LineBreak(), Form( destination=request.url, inner=[ Input( "", placeholder="Name", name="msg_name", global_attr=GlobalAttributes( css_class="form-control bg-dark text-white", style="margin-bottom: 15px", ), ), TextArea( inner="", placeholder="Message", name="message", global_attr=GlobalAttributes( css_class="form-control bg-dark text-white", style="margin-bottom: 15px;", ), ), Input( type=InputType.Submit, value="Send Message", name="submit", global_attr=GlobalAttributes( css_class="btn btn-danger text-white text-decoration-none" ), ), ], ), ], global_attr=GlobalAttributes( css_class="container", style="margin-top: 25px" ), ) ], "Send a message", ).to_code() # Wrapper for Base HTML def buildSite(content, title=None): return Document( head=Head( [ Title(title), Meta( name="viewport", content="user-scalable=no, width=device-width, initial-scale=1.0", ), BOOTSTRAP, ] ), body=Body( content, global_attr=GlobalAttributes( css_class="bg-dark text-white justify-content-center text-center" ), ), ) # Public Key Page @main_pages.route("/public_key", methods=["GET"]) def public_key(): try: ret = open("/config/pub.key").read() pgp = gnupg.GPG() pgp.import_keys(open("/config/pub.key").read()) key_id = ( str(pgp.list_keys()[0]["uids"][0]).replace("<", "[ ").replace(">", " ]") ) ua = request.user_agent.string.lower() if "chrome" in ua or "safari" in ua or "firefox" in ua: # If request is from common browser return stylized html return buildSite( [ Div( Div( [ Bold("To Import: "), Span( f'curl -sL "{request.base_url}"|gpg --import', global_attr=GlobalAttributes( style="display: block;font-family: monospace,monospace;margin-top: 10px; font-size: 20px;overflow-wrap: break-word;" ), ), ], global_attr=GlobalAttributes(css_class="alert alert-info"), ), global_attr=GlobalAttributes( css_class="container", style="margin-top: 25px" ), ), Heading( 4, key_id, global_attr=GlobalAttributes( css_class="container card", style="padding-top: 10px; padding-bottom: 10px; background: black; margin-bottom: 15px;", ), ), Div( Paragraph(ret.replace("\n", "
")), global_attr=GlobalAttributes( css_class="container card bg-primary" ), ), ], "Public Key", ).to_code() # Return raw key resp = Response(response=ret, status=200, mimetype="application/pgp-keys") return resp except: # If key does not exist return error return Response(response="", status=502) # Contact def build_contact_block(): return Div( [ Heading(1, "Contact"), ThematicBreak(), [ Link("/public_key", "My PGP Key"), LineBreak(), Link("/message", "Write a message"), LineBreak(), LineBreak(), ] if exists("/config/pub.key") else None, Link(f"mailto:{CONFIG['email']}", CONFIG["email"]), LineBreak(), ], global_attr=GlobalAttributes(css_class="container border-dark"), ) # Donations def build_donation_block(): return Div( [ Heading(1, "Donation"), ThematicBreak(), Paragraph( [ Bold("Monero: "), Span( CONFIG["xmr_address"], global_attr=GlobalAttributes( style="color: orange;overflow-wrap: break-word;" ), ), ] ) if "xmr_address" in CONFIG else None, ], global_attr=GlobalAttributes(css_class="container", style="margin-top: 20px"), ) # Basic Information def build_information_block(): return Div( [ Image( "/assets/me", 200, 200, "Me", global_attr=GlobalAttributes(css_class="rounded"), ), LineBreak(), LineBreak(), Heading(1, CONFIG["name"]), ThematicBreak(), ], global_attr=GlobalAttributes( css_class="container border-dark", style="margin-top: 20px" ), ) # Main @main_pages.route("/", methods=["GET"]) def index(): return buildSite( [build_information_block(), build_contact_block(), build_donation_block()], "About Me", ).to_code()