#!/bin/python3 ''' Basic blog server with comments. Copyright (C) 2025 Swirly "Stoner" Curly This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . this is a basic blog server. it takes only one battery: a base.html with a `` somewhere in it that will be replaced. there's also an optional battery: a emaillogin.txt with the username then the password both in plaintext on separate lines (only needed if email on comment is enabled) this html file cannot access other files within the server it is on -- you should use inline css/js if possible or use some external server for other files (catbox, sgfs...) this reads from a data.json with a specific format and formats it for use in a webpage, then distributes that webpage. the data.json is in the following format: { "posts": [ { "title": "post 1", "date": "this is just a string", "content": "mamma mia", "comments": [ { "name": "shitass", "date": "ergearg", "content": "pepperoni" } ] }, { "title": "post 2", "date": "again", "content": "wowee", "comments": [ ] } ] } if needed, i have made a python script that takes this data.json and puts something else at the top with the date in YYYY-MM-DD HH:MM format: import json, datetime, time title = input("title: ") content = input("content: ") with open("data.json", "r") as f: ass = json.load(f)['posts'] ass.insert(0, {'title': title, 'date': time.strftime("%Y-%m-%d") + " " + time.strftime("%H:%M",time.localtime()), 'content': content, 'comments': []}) ass = {"posts": ass} with open("data.json", "w") as f: f.write(json.dumps(ass, indent=4)) if anything bad or confusing arises, contact me! (https://swirly.architectenterprises.net) ''' from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler import os, socket, sys, json, cgi, datetime, time from redmail import EmailSender def intersperse(lst, item): result = [item] * (len(lst) * 2 - 1) result[0::2] = lst return result def generate_blog(): #configure everything here! if not os.path.exists("data.json"): with open("data.json", "w") as f: f.write('{"posts": []}') with open("data.json", "r") as f: funkyshit = json.load(f) myfuckinggod = [] for post in funkyshit['posts']: comments_formatted = [] for comment in post['comments']: comments_formatted.append(f"""

{comment['name']}

{comment['date']}

{comment['content']}

""") comments_formatted = intersperse(comments_formatted, '


') myfuckinggod.append(f"""

{post['title']}

{post['date']}

{post['content']}

Comments: {len(post['comments'])}

{"
" if post['comments'] else ""} {" See comments" if post['comments'] else ""} {'
' if post['comments'] else ""} {'
' if post['comments'] else ""}
Post a comment


{'
' if post['comments'] else ""} {''' '''.join(comments_formatted)} {"
" if post['comments'] else ""} {'
' if post['comments'] else ""} {"
" if post['comments'] else ""} """) return '\n'.join(myfuckinggod) class bitch(BaseHTTPRequestHandler): def do_GET(self): if self.path == "/": self.send_response(200) self.send_header('content-type', 'text/html; charset=utf-8') with open("./base.html", "r") as f: contents = f.read() contents = bytes(contents.replace("", f""" {generate_blog()} """), "utf-8") self.end_headers() self.wfile.write(contents) elif self.path == "/source.py": self.send_response(200) self.send_header('content-type', 'text/plain') self.end_headers() with open(os.path.abspath(os.path.realpath(sys.argv[0])), "rb") as f: contents = f.read() self.wfile.write(contents) else: self.send_response(400) self.send_header('content-type', 'text/plain') self.end_headers() self.wfile.write(bytes("mrrp :3c", "utf-8")) def do_HEAD(self): bitch.do_GET(self) def do_POST(self): if self.path == "/": ''' important! this variable determines whether or not this script will email you upon every comment made! ''' email_on_comment = True #change to False to disable content_type, _ = cgi.parse_header(self.headers['content-type']) if content_type == 'multipart/form-data': form_data = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST'} ) if 'name' in form_data and 'content' in form_data and 'replydate' in form_data: cname = form_data['name'].value content = form_data['content'].value creply = form_data['replydate'].value replacelist = { "&": "&", "<": "<", ">": ">", "'": "'", "\"": """, } for i1, i2 in replacelist.items(): cname = cname.replace(i1, i2) content = content.replace(i1, i2) with open("data.json", "r") as f: funkyshitepisode2 = json.load(f) uavgfuhavguh = False foundpostnum = 0 for idx, post in enumerate(funkyshitepisode2['posts']): if post['date'] == creply: uavgfuhavguh = True foundpostnum = idx if uavgfuhavguh: cdate = time.strftime("%Y-%m-%d") + " " + time.strftime("%H:%M",time.localtime()) funkyshitepisode2['posts'][foundpostnum]['comments'].append({"name": cname, "date": cdate, "content": content}) with open("data.json", "w") as f: f.write(json.dumps(funkyshitepisode2, indent=4)) if email_on_comment: with open("emaillogin.txt", "r") as f: bals = f.read() un = bals.split("\n")[0] pswd = bals.split("\n")[1] email = EmailSender( host="disroot.org", #change email server here! (could be gmail.com, etc...) port=587, #should stay the same unless chosen email server changed port (in that case why????????) username=un, password=pswd ) email.send( sender="swirlystone5877@disroot.org", #your email here! receivers=["swirlystone5877@disroot.org"], #here as well! subject="New posts comment!", text=f"From {cname} on {cdate}\n\n{content}", html=f"

New comment!


From {cname} on {cdate}

{content}

" ) else: self.send_response_only(400) return else: self.send_response_only(400) return self.send_response(200) self.send_header('content-type', 'text/html') self.end_headers() self.wfile.write(bytes('', 'utf-8')) httpd = ThreadingHTTPServer(("0.0.0.0", 6502), bitch) #this will serve at port 6502. replace with 80 if you don't want to type in :6502 at the end of your address. if port 80 fails, likely another (probably better) web server is using port 80. on windows you must disable the HTTP service/feature. httpd.serve_forever()