#!/usr/bin/python3
import datetime
import os
import json
import sys
import argparse
import shutil
import subprocess
import random
from string import hexdigits
from pathlib import Path
from urllib import parse
import time
# verbosity flag
V = False
DRYRUN = False
# defines all the metadata a page needs
class FuturePage:
def __init__(self):
self.name = ""
self.type = ""
self.filename = ""
self.title = ""
self.nav = ""
self.header = ""
self.article = ""
self.template = ""
def __str__(self):
return self.name + " " + self.filename
# contains all info relevant to a post
class Post:
# all our useful fields
def __init__(self, userid="", title="", content=""):
self.userid: str = userid
self.title: str = str(title)
self.content: str = content
self.creationdate = datetime.datetime.now().timestamp()
self.modifieddate = self.creationdate
self.postid = self.creationdate
self.collection = ""
self.tags = []
self.files = []
self.links = []
self.post_map_filename = str(self.creationdate) + ".map"
self.html_filename = str(self.creationdate) + ".html"
# populate post from post map file
def from_post_map(self, filename):
self.post_map_filename = filename
# check if json file exists
if not os.path.exists(self.post_map_filename):
print("WARNING: Could not find post map file '" + str(self.post_map_filename) + "', ignoring")
return None
else:
# deserialize json
with open(self.post_map_filename, 'r', encoding='UTF-8') as j:
post_map = json.load(j, strict=False)
# TODO: properly convert markdown to html
# TODO: convert links
# read in fields
self.userid = str(post_map.get("userid"))
self.postid = str(post_map.get("postid"))
self.title = str(post_map.get("title"))
self.content = post_map.get("content")
self.creationdate = str(post_map.get("creationdate"))
self.modifieddate = str(post_map.get("modifieddate"))
# if verbose
if V:
print("Successfully read in " + str(self))
return self
# check if post is still empty
def is_empty(self):
if self.userid == "" and self.postid == "":
return True
return False
# writes post to html and returns it
def to_html(self, output_dir="content/posts/", template_dir="primitives/"):
self.html_filename = str(self.postid) + ".html"
filename = output_dir + self.html_filename
# generate html
new_post = get_primitive(template_dir, "post.html")
new_post = new_post.replace("|TITLE|", self.title)
new_post = new_post.replace("|POSTCONTENTS|", self.content)
# TODO: img lazy loading (remove later)
new_post = new_post.replace(" User :
for u in self.users:
if u.userid == userid:
return u
# defines what macros are available to a page
class Macro:
def __init__(self, name, tag, filename):
self.name = name
self.tag = tag
self.filename = filename
def __str__(self):
return self.name + " " + self.tag + " " + self.filename
class Trido:
def __init__(self, site_map='site.map'):
self.s = {}
self.posts = Posts()
self.users = Users()
self.home_filenames = []
self.htmls = {}
self.files = {}
self.read_site_map(site_map)
self.init_users()
# parse api url
def parse_api(self, url: str):
split = url.split('/')
if len(split) > 3:
if split[1] == 'api':
return split[2], split[3]
# handles cgi post requests
def handle_post_request(self, rt: (str, str), form: dict) -> str:
redirect = "user/" + rt[0]
if rt[0] == 'home':
redirect = ""
# resetcolors.py
if rt[1] == 'resetcolors.py':
# read in from default colors map
with open("primitives/defaultcolors.map", 'r') as defaultcolors:
with open("users/" + rt[0] + "/colors.map", 'w') as data:
json.dump(json.load(defaultcolors), data)
self.generate_user_homepage(rt[0])
return redirect
# colors.py
elif rt[1] == 'colors.py':
colors = {"|BGCOLOR|": form["bgcolor"][0], "|TITLECOLOR|": form["titlecolor"][0],
"|TEXTCOLOR|": form["textcolor"][0], "|BOXCOLOR|": form["boxcolor"][0]}
with open("users/" + rt[0] + "/colors.map", 'w') as data:
json.dump(dict(colors), data)
self.generate_user_homepage(rt[0])
return redirect
# randomcolors.py
elif rt[1] == 'randomcolors.py':
fields = ['|BGCOLOR|', '|TITLECOLOR|', '|TEXTCOLOR|', '|BOXCOLOR|']
# only python 3.9+ :\
#colors = [(f, '#' + random.randbytes(3).hex()) for f in fields]
# python 3.7 fix
colors = [(f, '#' + ''.join([random.choice(hexdigits) for n in range(6)])) for f in fields]
with open("users/" + rt[0] + "/colors.map", 'w') as data:
json.dump(dict(colors), data)
self.generate_user_homepage(rt[0])
return redirect
elif rt[1] == 'uploadfile.py':
return redirect
# setbackground.py
elif rt[1] == 'setbackground.py' or rt[1] == 'uploadbackground.py':
bgurl: str = form.get("bgurl")[0]
user = self.users.get_user_by_id(rt[0])
if not bgurl.startswith("http"):
#bgurl = user.userid + "/" +
pass
#bgurl = bgurl.replace("|USER|", str(user.userid))
# TODO: fix bgurl prefix discrepency
if len(bgurl) > 0:
if user:
user.backgroundimage = bgurl
user.to_user_map(self.s["output_dir"] + self.s["users_dir"])
self.generate_user_homepage(user.userid)
return redirect
# resetposts.py
elif rt[1] == 'resetposts.py':
self.users.reset_user_posts(self.s, rt[0])
# regenerate html pages
self.post_maps_to_html(postmaps_dir="content/postmaps/")
return redirect
# createprofile.py
elif rt[1] == 'createprofile.py':
userid = form.get("userid", ["anonymous"])[0]
user = self.users.get_user_by_id(userid)
if user is None:
user = self.users.create_user(self.s, userid=userid)
self.users.users.append(user)
user.to_user_map(self.s["output_dir"] + self.s["users_dir"])
self.generate_user_homepages()
return "user/" + userid
else:
return "/"
# submitpost.py
elif rt[1] == 'submitpost.py':
title = form.get("title", ["Default title"])[0]
userid = form.get("userid", ["anonymous"])[0]
content = form.get("content", ["Whoops! this post seems to be empty!"])[0]
user = self.users.get_user_by_id(userid)
# check if user doesn't exist
if user is None:
user = self.users.create_user(self.s, userid=userid)
self.users.users.append(user)
# create and save new post
np = Post(userid, title, content)
np.to_post_map()
np.to_html()
self.posts.add(np)
user.postlist.append(np.postid)
# save user map
user.to_user_map(self.s["output_dir"] + self.s["users_dir"])
self.post_maps_to_html(self.s["users_dir"] + userid + "/postmaps/")
self.generate_user_homepages()
return "user/" + user.userid
self.generate_home_page()
# TODO: split trido into api and ssg
# TODO: decide on source of truth .map or postmaps folder
# TODO: edit post portal
# TODO: make post links clickable
# TODO: file upload action
# TODO: fix reset posts
# TODO: fix compile arg
# TODO: look into macro replacement engine
# TODO: user export and backup
# TODO: add prefix for links
# TODO: support different deployment configurations
# TODO: page to manage user's files
# TODO: fix filename on save
# TODO: POST reply multi part data with filename
# generate html posts
def post_maps_to_html(self, postmaps_dir="users/home/postmaps/"):
# sort posts in chronological order
post_map_files = [fi for fi in os.listdir(postmaps_dir) if fi.endswith(".map")]
post_map_files.sort()
if V:
print("Found " + str(len(post_map_files)) + " post map files in " + postmaps_dir)
#primitive_post = open(self.s['template_dir'] + 'post.html')
# generate individual htmls
for pmf in post_map_files:
new_post = Post()
new_post.from_post_map(postmaps_dir + pmf)
new_post.to_html(self.s['output_dir'] + self.s['content_dir'] + "posts/" + new_post.userid + "/")
self.posts.add(new_post)
# regenerate main.css
# generates all user's homepages
def generate_user_homepages(self):
for u in self.users.users:
self.post_maps_to_html(self.s["users_dir"] + "/" + u.userid + "/postmaps/")
self.generate_user_homepage(u.userid)
# generate user home pages
def generate_user_homepage(self, user: str):
template = self.get_page("user.html")
html_buffer = ""
location = self.s['output_dir'] + self.s['content_dir'] + 'posts/' + user + "/"
user_posts = [fi for fi in os.listdir(location) if fi.endswith(".html")]
user_posts.sort()
user_posts.reverse()
for u in user_posts:
#with open(location + post.html_filename, 'r') as f:
with open(location + u, 'r') as f:
html_buffer += f.read() + "\n"
# fetch colors
with open(self.s["output_dir"] + self.s["users_dir"] + user + "/" + "colors.map", 'r') as data:
colors = json.load(data)
users_buffer = ""
for u in self.users.users:
users_buffer = "