From 42aff1f455d3439e58dfcf8fcc3e091f078b5b05 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 31 Oct 2024 21:11:13 +1100 Subject: [PATCH] feat: Add api routes for email and other basic info --- mail.py | 88 +++++++++++++++++++++++ now.py | 46 ++++++++++++ server.py | 207 ++++++++++++++++++++++++++++++++++-------------------- 3 files changed, 263 insertions(+), 78 deletions(-) create mode 100644 mail.py create mode 100644 now.py diff --git a/mail.py b/mail.py new file mode 100644 index 0000000..22a6c15 --- /dev/null +++ b/mail.py @@ -0,0 +1,88 @@ +import smtplib +import re +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.utils import formataddr +from flask import jsonify +import os + + +def validateSender(email): + domains = os.getenv("EMAIL_DOMAINS").split(",") + for domain in domains: + if re.match(r".+@" + domain, email): + return True + + return False + +def sendEmail(data): + fromEmail = "noreply@woodburn.au" + if "from" in data: + fromEmail = data["from"] + + if not validateSender(fromEmail): + return jsonify({ + "status": 400, + "message": "Bad request 'from' email invalid" + }) + + + if "to" not in data: + return jsonify({ + "status": 400, + "message": "Bad request 'to' json data missing" + }) + to = data["to"] + + if "subject" not in data: + return jsonify({ + "status": 400, + "message": "Bad request 'subject' json data missing" + }) + subject = data["subject"] + + if "body" not in data: + return jsonify({ + "status": 400, + "message": "Bad request 'body' json data missing" + }) + body = data["body"] + + if not re.match(r"[^@]+@[^@]+\.[^@]+", to): + raise ValueError("Invalid recipient email address.") + + if not subject: + raise ValueError("Subject cannot be empty.") + + if not body: + raise ValueError("Body cannot be empty.") + + fromName = "Nathan Woodburn" + if 'sender' in data: + fromName = data['sender'] + + # Create the email message + msg = MIMEMultipart() + msg['From'] = formataddr((fromName, fromEmail)) + msg['To'] = to + msg['Subject'] = subject + msg.attach(MIMEText(body, 'plain')) + + # 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")) + server.sendmail(fromEmail, to, msg.as_string()) + print("Email sent successfully.") + return jsonify({ + "status": 200, + "message": "Send email successfully" + }) + except Exception as e: + return jsonify({ + "status": 500, + "error": "Sending email failed", + "exception":e + }) + + \ No newline at end of file diff --git a/now.py b/now.py new file mode 100644 index 0000000..dc1e0b7 --- /dev/null +++ b/now.py @@ -0,0 +1,46 @@ +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) + 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] + print(now_page) + return render_now_page(now_page,handshake_scripts=handshake_scripts) + +#endregion \ No newline at end of file diff --git a/server.py b/server.py index ff068a4..be0d8eb 100644 --- a/server.py +++ b/server.py @@ -31,6 +31,8 @@ from solders.message import MessageV0 from solders.transaction import VersionedTransaction from solders.null_signer import NullSigner from PIL import Image +from mail import sendEmail +import now app = Flask(__name__) CORS(app) @@ -295,7 +297,7 @@ def donateAmount(amount): "icon": "https://nathan.woodburn.au/assets/img/profile.png", "label": f"Donate {amount} SOL to Nathan.Woodburn/", "title": "Donate to Nathan.Woodburn/", - "description": "Donate {amount} SOL to Nathan.Woodburn/", + "description": f"Donate {amount} SOL to Nathan.Woodburn/", } return jsonify(data) @@ -343,12 +345,90 @@ def donateAmountPost(amount): # endregion + +#region Other API routes +@app.route("/api/time") +def time(): + 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") +def timezone(): + return jsonify({"timezone": ncConfig["time-zone"]}) + +@app.route("/api/timezone", methods=["POST"]) +def timezonePost(): + # 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") +def nc(): + return jsonify({"message": ncConfig["message"]}) + +@app.route("/api/ip") +def ip(): + return jsonify({"ip": request.remote_addr}) + + +@app.route("/api/email", methods=["POST"]) +def email(): + # 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 "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) + + + +#endregion # endregion # region Main routes @app.route("/") -def index(): +def index(): + global handshake_scripts + global projects + global projectsUpdated + # Check if host if podcast.woodburn.au if "podcast.woodburn.au" in request.host: return render_template("podcast.html") @@ -361,6 +441,16 @@ def index(): # Check if crawler if request.headers: + # Check if curl + if "curl" in request.headers.get("User-Agent"): + return jsonify( + { + "message": "Welcome to Nathan.Woodburn/! This is a personal website. For more information, visit https://nathan.woodburn.au", + "ip": request.remote_addr, + "dev": handshake_scripts == "", + } + ) + if "Googlebot" not in request.headers.get( "User-Agent" ) and "Bingbot" not in request.headers.get("User-Agent"): @@ -376,11 +466,7 @@ def index(): ) resp.set_cookie("loaded", "true", max_age=604800) return resp - - global handshake_scripts - global projects - global projectsUpdated - + try: git = requests.get( "https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1", @@ -477,26 +563,15 @@ def index():