added file uploads and commented the config file a bit
|
@ -1 +1 @@
|
|||
{"title": "aesdgd fgdfgsd fgdfdgdfhgsfsdfsdfsdhfjskjhfkjshfkjshjfkshfkhdfgjdhfjjkgsdhfkjghdfgjj hsfafsghjf gshdf gfhsghasf shafg aa aa a gdshjsha", "date": "08-01-2023-3", "importance": "a", "categories": ["a"], "description": "aeae", "author": "test", "editors": []}
|
||||
{"title": "A Standard Post With A Standard Title And A Normal Stock Image Background", "date": "08-01-2023-3", "importance": "a", "categories": ["a"], "description": "aeae", "author": "test", "editors": []}
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 1.3 MiB |
BIN
blogs/08-01-2023-3/images/og-noedits.png
Normal file
After Width: | Height: | Size: 953 KiB |
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 372 KiB |
1
blogs/08-02-2023-2/db.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"title": "This is a test title for the gradient", "date": "08-02-2023-2", "importance": "a", "categories": ["a"], "description": "aaa", "author": "test", "editors": []}
|
BIN
blogs/08-02-2023-2/images/banner.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
blogs/08-02-2023-2/images/og.png
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
blogs/08-02-2023-2/images/thumbnail.png
Normal file
After Width: | Height: | Size: 536 KiB |
1
blogs/08-02-2023-2/page.md
Normal file
|
@ -0,0 +1 @@
|
|||
aaa
|
29
config.py
|
@ -1,20 +1,21 @@
|
|||
# This is just a simple python script that gets imported when the program starts, make sure to follow proper python syntax and dont delete any variables or youll fuck up the server
|
||||
# This is just a simple python script that gets imported when the program starts, make sure to follow proper python syntax and dont delete or make any new variables or youll fuck up the server.
|
||||
|
||||
CONF_SITE_NAME = "Blogging Framework"
|
||||
CONF_MAX_CATEGORIES_PER_POST = 10
|
||||
CONF_MAX_CATEGORY_NAME_LENGTH = 20
|
||||
CONF_MAX_TITLE_LENGTH = 800
|
||||
CONF_MAX_IMPORTANCE_NAME_LENGTH = 15
|
||||
CONF_MAX_DESCRIPTION_LENGTH = 200
|
||||
CONF_SITE_NAME = "Blogging Framework" # set this to your site name, gets used in the page title and such
|
||||
CONF_MAX_CATEGORIES_PER_POST = 10 # this and the following line are important to understand, having a long category length and high category count will increase the size of those categories on the page exponentially because they multiply eachother
|
||||
CONF_MAX_CATEGORY_NAME_LENGTH = 20
|
||||
CONF_MAX_TITLE_LENGTH = 80
|
||||
CONF_MAX_IMPORTANCE_NAME_LENGTH = 15 # shouldnt be too long to prevent formatting issues
|
||||
CONF_MAX_DESCRIPTION_LENGTH = 200 # shouldnt be too long to prevent formatting issues
|
||||
CONF_STAY_LOGGED_IN_DURATION = 60 * 60 # 1 hour - resets on refresh
|
||||
CONF_TOTAL_STAY_LOGGED_IN_DURATION = 60 * 60 * 24 * 3 # 3 days - if the user has been logged in for longer than this amount of time, they will be logged out no matter what unless keepalive keeps the user logged in
|
||||
CONF_ENABLE_LOGIN_KEEPALIVE = True # login keepalive allows the max total stay logged in duration to be extended by 120 seconds every 110 seconds. this happens automatically on the post editor page to ensure a logout does not occur while a post is being edited before it can be saved
|
||||
CONF_DEBUG_ENABLE = True
|
||||
CONF_HOST = "127.0.0.1"
|
||||
CONF_DEBUG_ENABLE = True # turn this on in production for free vbux, totally wont allow remote code execution on your server :)
|
||||
CONF_HOST = "127.0.0.1" # if the unix socket is disabled the server will listen on this interface
|
||||
CONF_USE_SOCKET = False
|
||||
CONF_UNIX_SOCKET = "./socket.sock"
|
||||
CONF_UNIX_SOCKET_PERMS = "777"
|
||||
CONF_PORT = 8080
|
||||
CONF_URL_PREFIX = ""
|
||||
CONF_OG_IMG_FONT = "/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BlackIt.otf"
|
||||
CONF_OG_IMG_FONT_SIZE = 50
|
||||
CONF_UNIX_SOCKET_PERMS = "777" # please restrict this, i have it set 777 for debugging and because im lazy, it should never be this in production
|
||||
CONF_PORT = 8080 # port to listen on
|
||||
CONF_URL_PREFIX = "" # if behind a reverse proxy at say example.com/blog, set this to "blog", otherwise leave it be
|
||||
CONF_OG_IMG_FONT = "/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BlackIt.otf" # path to the font for the og:image
|
||||
CONF_OG_IMG_FONT_SIZE = 50 # font size for the og:image when text is overlaid on the banner image, if you have really long titles then make this smaller but otherwise just keep it as is
|
||||
CONF_ALLOWED_FILETYPES = [".png", ".jpg", ".jpeg", ".pdf", ".zip", ".tar.gz"] # highly advise against adding executables, not really for user safety, but because inevitably some asshole will get them to run on the server
|
||||
|
|
2
db.json
|
@ -1 +1 @@
|
|||
{"categories": {"announcements": ["07-31-2023-2", "07-31-2023-3"], "information": ["07-30-2023-1"], "service updates": [], "a": ["08-01-2023-3"]}, "urgencies": {"urgent": ["07-31-2023-0", "07-31-2023-2", "07-31-2023-3"], "important": [], "unimportant": ["07-30-2023-1"], "other_importanc": [], "a": ["08-01-2023-0", "08-01-2023-3"], "": []}, "featured": "08-01-2023-3"}
|
||||
{"categories": {"announcements": ["07-31-2023-2", "07-31-2023-3"], "information": ["07-30-2023-1"], "service updates": [], "a": ["08-02-2023-2", "08-01-2023-3"]}, "urgencies": {"urgent": ["07-31-2023-0", "07-31-2023-2", "07-31-2023-3"], "important": [], "unimportant": ["07-30-2023-1"], "other_importanc": [], "a": ["08-01-2023-0", "08-02-2023-0", "08-02-2023-2", "08-01-2023-3"], "": []}, "featured": "08-02-2023-2"}
|
BIN
fileuploads/.Gr_pQCuvKToTpMxApGVLhK14/file.PNG
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
fileuploads/.LNnj19Em.q6TbETVwJktGzkd/file.PNG
Normal file
After Width: | Height: | Size: 9.2 KiB |
1
fileuploads/.LNnj19Em.q6TbETVwJktGzkd/name.txt
Normal file
|
@ -0,0 +1 @@
|
|||
HOME.PNG
|
BIN
fileuploads/.LNnj19Em.q6TbETVwJktGzkd/thumbnail.png
Normal file
After Width: | Height: | Size: 512 B |
59
main.py
|
@ -3,8 +3,8 @@
|
|||
# use http://www.patorjk.com/software/taag to make labels
|
||||
|
||||
from flask import Flask, render_template, abort, send_from_directory, session, redirect, request, flash, make_response, url_for
|
||||
from misc_functions import read_cached, get_post_content, get_post_info, limit_content_length, get_gradient_2d, get_gradient_3d, random_gradient, crop_scale_image, break_text
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageEnhance
|
||||
from misc_functions import read_cached, get_post_content, get_post_info, limit_content_length, get_gradient_2d, get_gradient_3d, random_gradient, crop_scale_image, break_text, random_word
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
|
||||
import numpy as np
|
||||
import json, hashlib, os, time, shutil, base64, random
|
||||
|
||||
|
@ -15,6 +15,8 @@ abspath = os.path.abspath(__file__)
|
|||
dname = os.path.dirname(abspath)
|
||||
os.chdir(dname)
|
||||
|
||||
PIL_SUPPORTED_FILETYPES = supported_extensions = {ex for ex, f in Image.registered_extensions().items() if f in Image.OPEN}
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
|
@ -61,6 +63,12 @@ def viewpost(postname):
|
|||
def serve_image(postname, imgname):
|
||||
return send_from_directory("blogs", f"{postname}/images/{imgname}")
|
||||
#
|
||||
# OTHER FILE HANDLER
|
||||
#
|
||||
@app.route("/fileuploads/<container>/<filename>")
|
||||
def serve_file(container, filename):
|
||||
return send_from_directory("fileuploads", f"{container}/{filename}")
|
||||
#
|
||||
# PLAINTEXT VIEWER
|
||||
#
|
||||
@app.route("/posts/<postname>/plaintext", methods=["GET"])
|
||||
|
@ -136,6 +144,33 @@ def admin(page=None, argument=None):
|
|||
else:
|
||||
abort(422)
|
||||
#
|
||||
# Image Upload Page
|
||||
#
|
||||
elif page == "fileupload":
|
||||
if request.method == "GET":
|
||||
return render_template("fileupload.html", filelinks = []) # dont worry, jijja2 automatically sanitises the stuff in imglinks
|
||||
elif request.method == "POST":
|
||||
file = request.files["file"]
|
||||
if file == '':
|
||||
abort(400)
|
||||
fileext = os.path.splitext(file.filename)[1]
|
||||
if fileext.lower() not in CONF_ALLOWED_FILETYPES:
|
||||
abort(400)
|
||||
filelinks = json.loads(request.form["filelinks"]) if request.form["filelinks"] else {}
|
||||
used_names = os.listdir("fileuploads")
|
||||
while True:
|
||||
rand_name = random_word(25)
|
||||
if rand_name not in used_names: break
|
||||
os.makedirs(f"fileuploads/{rand_name}")
|
||||
file.save(f"fileuploads/{rand_name}/file{fileext}")
|
||||
with open(f"fileuploads/{rand_name}/name.txt", "w") as name: name.write(file.filename)
|
||||
if fileext.lower() in PIL_SUPPORTED_FILETYPES:
|
||||
thumbnail = Image.open(f"fileuploads/{rand_name}/file{fileext}")
|
||||
thumbnail.thumbnail((256, 256))
|
||||
thumbnail.save(f"fileuploads/{rand_name}/thumbnail.png")
|
||||
filelinks[f"{rand_name}/file{fileext}"] = {"filename":file.filename, "thumbnail":f"{rand_name}/thumbnail.png"}
|
||||
return render_template("fileupload.html", filelinks = filelinks, linksjson=json.dumps(filelinks))
|
||||
#
|
||||
# NEW POST OR EDIT POST
|
||||
#
|
||||
elif page in ["new", "edit"]:
|
||||
|
@ -181,7 +216,7 @@ def admin(page=None, argument=None):
|
|||
for category in db["categories"]: # remove the post from its category in the DB
|
||||
try: db["categories"][category].pop(db["categories"][category].index(name))
|
||||
except ValueError: pass
|
||||
for urgency in db["urgencies"]: # remove the post from its category in the DB
|
||||
for urgency in db["urgencies"]: # remove the post from its urgency in the DB
|
||||
try: db["urgencies"][urgency].pop(db["urgencies"][urgency].index(name))
|
||||
except ValueError: pass
|
||||
|
||||
|
@ -270,14 +305,18 @@ def admin(page=None, argument=None):
|
|||
ogimg = False
|
||||
if og:
|
||||
ogimg = crop_scale_image(og.stream, 1200, 630)
|
||||
elif page == "edit": # if we are editing, regenerate the og-image
|
||||
if not banner:
|
||||
banner_temp = f"blogs/{name}/images/banner.png"
|
||||
else:
|
||||
else:
|
||||
if banner:
|
||||
banner.seek(0)
|
||||
banner_temp = banner.stream
|
||||
ogimg = crop_scale_image(banner_temp, 1200, 630)
|
||||
og_temp = banner.stream
|
||||
elif os.path.exists(f"blogs/{name}/images/og-noedits.png"):
|
||||
og_temp = f"blogs/{name}/images/og-noedits.png"
|
||||
else: # this should only happen with the gradient banners
|
||||
og_temp = f"blogs/{name}/images/banner.png"
|
||||
ogimg = crop_scale_image(og_temp, 1200, 630)
|
||||
ogimg.save(f"blogs/{name}/images/og-noedits.png")
|
||||
ogimg = ImageEnhance.Brightness(ogimg).enhance(0.3)
|
||||
ogimg = ogimg.filter(ImageFilter.GaussianBlur(radius=5))
|
||||
draw = ImageDraw.Draw(ogimg)
|
||||
font = ImageFont.truetype(CONF_OG_IMG_FONT, CONF_OG_IMG_FONT_SIZE)
|
||||
text_broken = list(break_text(title, font, 1200))
|
||||
|
@ -299,7 +338,7 @@ def admin(page=None, argument=None):
|
|||
# TODO: make a cool page that shows confetti or something idk
|
||||
return "Post made sucessfully!"
|
||||
|
||||
except: # deletes the post folder if something goes wrong, because an incomplete post folder causes errors
|
||||
except SyntaxError: # deletes the post folder if something goes wrong, because an incomplete post folder causes errors
|
||||
# the or is a failsafe because im paranoid that if name isnt made for some reason itll delete the entire directory
|
||||
if page != "edit": shutil.rmtree(f"blogs/{name or 'SOMETHING HAS GONE TERRIBLY WRONG'}")
|
||||
abort(500)
|
||||
|
|
|
@ -97,6 +97,7 @@ def random_gradient(width, height):
|
|||
"""Generates a random gradient at the specified resolution"""
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import random
|
||||
array = get_gradient_3d(width, height,\
|
||||
(random.randint(0, 255),\
|
||||
random.randint(0, 255),\
|
||||
|
@ -116,9 +117,9 @@ def crop_scale_image(inputimg, width, height):
|
|||
imgwidth, imgheight = img.size
|
||||
if imgwidth / imgheight < width / height:
|
||||
img = img.crop((0,\
|
||||
(imgwidth-(imgwidth*(height/width)))/2,\
|
||||
(imgheight-(imgwidth*(height/width)))/2,\
|
||||
imgwidth,\
|
||||
((imgwidth-(imgwidth*(height/width)))/2) + (imgwidth*(height/width))))
|
||||
((imgheight-(imgwidth*(height/width)))/2) + (imgwidth*(height/width))))
|
||||
elif imgwidth / imgheight > width / height:
|
||||
img = img.crop(((imgwidth-(imgheight*(width/height)))/2,\
|
||||
0,\
|
||||
|
@ -182,3 +183,8 @@ def break_text(txt, font, max_width):
|
|||
yield txttmp
|
||||
txt = txt[subset+1:]
|
||||
subset = 1
|
||||
|
||||
def random_word(length):
|
||||
import random, string
|
||||
letters = string.ascii_letters + string.digits + "-."
|
||||
return ''.join(random.choice(letters) for i in range(length))
|
||||
|
|
20
templates/fileupload.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
function copyText(link2copy) {
|
||||
url = new URL(link2copy, document.baseURI).href;
|
||||
navigator.clipboard.writeText(url).then(function() {
|
||||
console.log('Async: Copying to clipboard was successful!');
|
||||
}, function(err) {
|
||||
console.error('Async: Could not copy text: ', err);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{% for filename in filelinks %}
|
||||
<a href="/fileuploads/{{filename}}" onclick="return copyText('/fileuploads/{{filename}}')"><img src="/fileuploads/{{filelinks[filename]['thumbnail']}}" alt="{{filelinks[filename]['filename']}}"></a><br>
|
||||
{% endfor %}
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" value="{{linksjson}}" name="filelinks">
|
||||
<input type="file" name="file">
|
||||
<input type="submit">
|
||||
</form>
|