we are so back

This commit is contained in:
maaa 2024-04-29 22:36:42 +02:00
parent 8ca281b1d3
commit e6359db97d
13 changed files with 173 additions and 346 deletions

View File

@ -1,5 +1,7 @@
burgercat: burger social media
now back again!
### self hosting:
this guide assumes you have git, python3, and redis installed on your server
@ -10,10 +12,6 @@ python init_db
python main
```
### zero downtime restarts:
- launch new burgercat server
- close previous server
### contribution guidelines:
- please check that your PR does not break anything
- unless absolutely nessecary, avoid adding dependecies

View File

@ -3,5 +3,4 @@ PORT = 8080
SECRET_KEY = placeholder
UPLOAD_FOLDER = uploads
PASSWORD_REQUIREMENT = 12
UPLOAD_LIMIT = 4
REDIS_URL = redis://localhost
UPLOAD_LIMIT = 12

4
hashpass.py Normal file
View File

@ -0,0 +1,4 @@
from werkzeug.security import generate_password_hash, check_password_hash
passwordthing = input("insert pass: ")
print(generate_password_hash(passwordthing))

206
main
View File

@ -9,15 +9,13 @@ import datetime
import socket
import threading
import subprocess
import asyncio
from hypercorn.config import Config
from hypercorn.asyncio import serve
from itertools import groupby
from waitress import serve
from werkzeug.utils import secure_filename
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.middleware.proxy_fix import ProxyFix
from flask import Flask, render_template, request, url_for, flash, redirect, session, make_response, send_from_directory, stream_with_context, Response, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_sse import sse
from quart import Quart, render_template, request, url_for, flash, redirect, session, make_response, send_from_directory, stream_with_context, Response, request, jsonify, websocket
from apscheduler.schedulers.background import BackgroundScheduler
# read config file
@ -29,23 +27,10 @@ SECRET_KEY = config["config"]["SECRET_KEY"]
UPLOAD_FOLDER = config["config"]["UPLOAD_FOLDER"]
UPLOAD_LIMIT = config["config"]["UPLOAD_LIMIT"]
PASSWORD_REQUIREMENT = config["config"]["PASSWORD_REQUIREMENT"]
REDIS_URL = config["config"]["REDIS_URL"]
app = Flask(__name__)
app = Quart(__name__)
app.config["SECRET_KEY"] = SECRET_KEY
app.config["REDIS_URL"] = REDIS_URL
app.config["MAX_CONTENT_LENGTH"] = int(UPLOAD_LIMIT) * 1000 * 1000
app.register_blueprint(sse, url_prefix="/stream")
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
limiter = Limiter(
get_remote_address,
app = app,
default_limits = ["3 per second"],
storage_uri = "memory://",
strategy = "fixed-window"
)
if SECRET_KEY == "placeholder":
print("[WARNING] Secret key is not set")
@ -136,15 +121,14 @@ def get_session(id):
full_hash = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip()
short_hash = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"]).decode().strip()
ALLOWED_EXTENSIONS = {"png", "apng", "jpg", "jpeg", "gif", "svg", "webp"}
ALLOWED_EXTENSIONS = {"png", "apng", "jpg", "jpeg", "gif", "svg", "webp", "jxl"}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route("/", methods=("GET", "POST"))
def main():
async def main():
usersession = request.cookies.get("session_DO_NOT_SHARE")
conn = get_db_connection()
posts = conn.execute("SELECT * FROM posts ORDER BY created DESC;").fetchall()
@ -153,22 +137,22 @@ def main():
if usersession:
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
return render_template("main.html", userdata=user, posts=posts, full_hash=full_hash, short_hash=short_hash)
return await render_template("main.html", userdata=user, posts=posts, full_hash=full_hash, short_hash=short_hash)
else:
return render_template("main.html", posts=posts, full_hash=full_hash, short_hash=short_hash)
return await render_template("main.html", posts=posts, full_hash=full_hash, short_hash=short_hash)
@app.route("/chat", methods=("GET", "POST"))
def chat():
async def chat():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
return render_template("chat.html", userdata=user)
return await render_template("chat.html", userdata=user)
else:
return render_template("chat.html")
return await render_template("chat.html")
@app.route("/api/chat/listrooms")
def chatlistrooms():
async def chatlistrooms():
conn = get_db_connection()
rooms = conn.execute("SELECT * FROM chatrooms ORDER BY id ASC;").fetchall()
conn.close()
@ -185,8 +169,7 @@ def chatlistrooms():
return(template), 200
@app.route("/api/chat/getmessages/<roomid>")
@limiter.exempt
def chatget(roomid):
async def chatget(roomid):
messages = get_messages(roomid, 150)
template = []
@ -209,13 +192,14 @@ def chatget(roomid):
return(template), 200
@app.route("/api/chat/send/<roomid>", methods=("GET", "POST"))
def chatsend(roomid):
async def chatsend(roomid):
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
if request.method == "POST":
data = request.get_json()
data = await request.get_json()
content = data["content"]
userCookie = get_session(usersession)
@ -233,7 +217,7 @@ def chatsend(roomid):
"created": str(time.time())
}
sse.publish({"message": chatMessageContent}, type="publish")
#message_queue.append({"message": chatMessageContent})
conn = get_db_connection()
conn.execute("INSERT INTO chatmessages (content, chatroom_id, creator, created) VALUES (?, ?, ?, ?)",
@ -245,7 +229,7 @@ def chatsend(roomid):
@app.route("/@<pageusername>", methods=("GET", "POST"))
def user(pageusername):
async def user(pageusername):
usersession = request.cookies.get("session_DO_NOT_SHARE")
checkusername = check_username_taken(pageusername)
@ -255,20 +239,20 @@ def user(pageusername):
if usersession:
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
return render_template("user.html", userdata=user, createddate=datetime.datetime.utcfromtimestamp(int(str(pageuser["created"]).split(".")[0])).strftime("%Y-%m-%d"), pageuser=pageuser)
return await render_template("user.html", userdata=user, createddate=datetime.datetime.utcfromtimestamp(int(str(pageuser["created"]).split(".")[0])).strftime("%Y-%m-%d"), pageuser=pageuser)
else:
return render_template("user.html", createddate=datetime.datetime.utcfromtimestamp(int(str(pageuser["created"]).split(".")[0])).strftime("%Y-%m-%d"), pageuser=pageuser)
return await render_template("user.html", createddate=datetime.datetime.utcfromtimestamp(int(str(pageuser["created"]).split(".")[0])).strftime("%Y-%m-%d"), pageuser=pageuser)
else:
return """<img src="https://http.cat/images/404.jpg">""", 404
@app.route("/api/page/<userid>", methods=("GET", "POST"))
def apipageuser(userid):
async def apipageuser(userid):
pageuser = get_user(userid)
addhtml = """
<!DOCTYPE html><script>
window.stop()
</script>
<base target="_blank"/> <head><meta http-equiv="Content-Security-Policy" default-src='none'; content="img-src cdn.discordapp.com cdn.discordapp.net media.tenor.com; style-src: 'self';" /></head>"""
<base target="_blank"/> <head><meta http-equiv="Content-Security-Policy" default-src='none'; content="img-src cdn.discordapp.com cdn.discordapp.net media.tenor.com; media-src: fonts.gstatic.com fonts.googleapis.com; style-src: 'self';" /></head>"""
if not pageuser == "error":
return addhtml + pageuser["htmldescription"]
@ -276,7 +260,7 @@ def apipageuser(userid):
return """<img src="https://http.cat/images/404.jpg">""", 404
@app.route("/@<pageusername>/edit", methods=("GET", "POST"))
def edituser(pageusername):
async def edituser(pageusername):
usersession = request.cookies.get("session_DO_NOT_SHARE")
checkusername = check_username_taken(pageusername)
@ -288,7 +272,9 @@ def edituser(pageusername):
user = get_user(userCookie["id"])
if pageuser["username"] == user["username"]:
if request.method == "POST":
code = request.form["code"].replace("Content-Security-Policy", "").replace("<iframe>", "")
requestData = await request.form
code = requestData["code"].replace("Content-Security-Policy", "").replace("<iframe>", "")
conn = get_db_connection()
conn.execute("UPDATE users SET htmldescription = ? WHERE id = ?",
(code, user["id"]))
@ -296,7 +282,7 @@ def edituser(pageusername):
conn.close()
return redirect("/@" + user["username"])
else:
return render_template("edituser.html", userdata=user, pageuser=pageuser)
return await render_template("edituser.html", userdata=user, pageuser=pageuser)
else:
return """<img src="https://http.cat/images/403.jpg">""", 403
else:
@ -306,7 +292,7 @@ def edituser(pageusername):
@app.route("/api/frontpage", methods=("GET", "POST"))
def apifrontpage():
async def apifrontpage():
conn = get_db_connection()
posts = conn.execute("SELECT * FROM posts ORDER BY created DESC;").fetchall()
conn.close()
@ -346,7 +332,7 @@ def apifrontpage():
return result
@app.route("/api/userinfo", methods=("GET", "POST"))
def apiuserinfo():
async def apiuserinfo():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
userCookie = get_session(usersession)
@ -363,12 +349,11 @@ def apiuserinfo():
}, 403
@limiter.limit("10/minute", override_defaults=False)
@app.route("/api/login", methods=("GET", "POST"))
def apilogin():
async def apilogin():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if request.method == "POST":
data = request.get_json()
data = await request.get_json()
username = data["username"]
password = data["password"]
@ -402,56 +387,56 @@ def apilogin():
}, 400
@app.route("/apidocs", methods=("GET", "POST"))
def apidocs():
async def apidocs():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
return render_template("apidocs.html", userdata=user)
return await render_template("apidocs.html", userdata=user)
else:
return render_template("apidocs.html")
return await render_template("apidocs.html")
@app.route("/post", methods=("GET", "POST"))
def post():
async def post():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
if request.method == "POST":
title = request.form["title"]
formData = await request.form
formFiles = await request.files
title = formData["title"]
if title == "":
flash("Text required :3")
await flash("Text required :3")
return redirect(url_for("post"))
if len(title) > 300:
flash("Too long title!")
await flash("Too long title!")
return redirect(url_for("post"))
if "file" not in request.files:
flash("No file selected :3")
return redirect(url_for("post"))
file = request.files["file"]
if file.filename == "":
flash("No file selected :3")
return redirect(url_for("post"))
file = formFiles["file"]
if (file):
if not allowed_file(file.filename):
flash("File is not an image!")
await flash("File is not an image!")
return redirect(url_for("post"))
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
if not user["banned"] == "0":
flash("Your account has been banned. You may no longer perform this action.")
await flash("Your account has been suspended. You may no longer post")
return redirect(url_for("post"))
filename = secure_filename(file.filename)
finalfilename = secrets.token_hex(32) + filename
file.save(os.path.join(UPLOAD_FOLDER, finalfilename))
await file.save(os.path.join(UPLOAD_FOLDER, finalfilename))
imgurl = "/cdn/" + finalfilename
if (not file):
imgurl = ""
conn = get_db_connection()
conn.execute("INSERT INTO posts (textstr, imageurl, creator, created) VALUES (?, ?, ?, ?)",
(title, imgurl, userCookie["id"], str(time.time())))
@ -462,20 +447,19 @@ def post():
else:
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
return render_template("post.html", userdata=user)
return await render_template("post.html", userdata=user)
else:
flash("A burgercat account is required to post :3")
return redirect(url_for("login"))
@app.route("/api/comment", methods=("GET", "POST"))
@limiter.limit("1/second", override_defaults=False)
def comment():
async def comment():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
if request.method == "POST":
data = request.get_json()
data = await request.get_json()
uid = data["id"]
title = data["title"]
@ -510,7 +494,7 @@ def comment():
}, 401
@app.route("/api/post/<post_id>/comments", methods=("GET", "POST"))
def apicomments(post_id):
async def apicomments(post_id):
postthing = get_comments(int(post_id))
if not postthing == "error":
@ -528,61 +512,62 @@ def apicomments(post_id):
return comments
@app.route("/cdn/<filename>", methods=("GET", "POST"))
@limiter.exempt
def cdn(filename):
async def cdn(filename):
if os.path.exists(os.path.join(UPLOAD_FOLDER, filename)):
return send_from_directory(UPLOAD_FOLDER, filename)
return await send_from_directory(UPLOAD_FOLDER, filename)
else:
return "file doesn't exist!!"
@app.route("/signup", methods=("GET", "POST"))
@limiter.limit("5/minute", override_defaults=False)
def signup():
async def signup():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
return redirect(url_for("main"))
if request.method == "POST":
if not check_username_taken(request.form["username"]) == "error":
flash("Username already taken :3")
requestData = await request.form
if not check_username_taken(requestData["username"]) == "error":
await flash("Username already taken :3")
return redirect(url_for("signup"))
if not request.form["username"].isalnum():
flash("Username must be alphanumeric :3")
if not requestData["username"].isalnum():
await flash("Username must be alphanumeric :3")
return redirect(url_for("signup"))
if not len(request.form["password"]) > int(PASSWORD_REQUIREMENT):
flash("Password must contain at least " + PASSWORD_REQUIREMENT + " characters")
if not len(requestData["password"]) > int(PASSWORD_REQUIREMENT):
await flash("Password must contain at least " + PASSWORD_REQUIREMENT + " characters")
return redirect(url_for("signup"))
hashedpassword = generate_password_hash(request.form["password"])
hashedpassword = generate_password_hash(requestData["password"])
conn = get_db_connection()
conn.execute("INSERT INTO users (username, password, created, htmldescription) VALUES (?, ?, ?, ?)",
(request.form["username"], hashedpassword, str(time.time()), ""))
(requestData["username"], hashedpassword, str(time.time()), ""))
conn.commit()
conn.close()
return redirect(url_for("login"))
else:
return render_template("signup.html")
return await render_template("signup.html")
@app.route("/login", methods=("GET", "POST"))
@limiter.limit("10/minute", override_defaults=False)
def login():
async def login():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
redirect(url_for("main"))
if request.method == "POST":
userID = check_username_taken(request.form["username"])
requestData = await request.form
userID = check_username_taken(requestData["username"])
user = get_user(userID)
if user == "error":
flash("Wrong username or password :3")
return redirect(url_for("login"))
if not check_password_hash(user["password"], (request.form["password"])):
if not check_password_hash(user["password"], (requestData["password"])):
flash("Wrong username or password :3")
return redirect(url_for("login"))
@ -594,32 +579,37 @@ def login():
conn.commit()
conn.close()
resp = make_response(redirect("/"))
resp.set_cookie("session_DO_NOT_SHARE", randomCharacters, expires=datetime.datetime.now() + datetime.timedelta(days=float(180)))
response = Response("""<script>window.location.href = "/";</script>""")
response.set_cookie("session_DO_NOT_SHARE", randomCharacters)
return response
#resp = await make_response(redirect("/"))
#resp.set_cookie("session_DO_NOT_SHARE", randomCharacters, expires=datetime.datetime.now() + datetime.timedelta(days=float(180)))
#return redirect("/")
return resp
else:
return render_template("login.html")
return await render_template("login.html")
@app.route("/settings", methods=("GET", "POST"))
def settings():
async def settings():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
userCookie = get_session(usersession)
user = get_user(userCookie["id"])
return render_template("settings.html", userdata=user, createddate=datetime.datetime.utcfromtimestamp(int(str(user["created"]).split(".")[0])).strftime("%Y-%m-%d %H:%m:%S"))
return await render_template("settings.html", userdata=user, createddate=datetime.datetime.utcfromtimestamp(int(str(user["created"]).split(".")[0])).strftime("%Y-%m-%d %H:%m:%S"))
else:
return redirect("/")
@app.route("/api/delete", methods=("GET", "POST"))
def delete():
async def delete():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if request.method == "POST":
data = request.get_json()
data = await request.get_json()
postid = int(data["id"])
post = get_post(postid)
@ -645,7 +635,7 @@ def delete():
}, 400
@app.route("/listusers", methods=("GET", "POST"))
def listusers():
async def listusers():
usersession = request.cookies.get("session_DO_NOT_SHARE")
if usersession:
userCookie = get_session(usersession)
@ -667,7 +657,7 @@ def listusers():
@app.route("/settings/logout", methods=("GET", "POST"))
def logout():
async def logout():
resp = redirect(url_for("main"))
session = request.cookies.get("session_DO_NOT_SHARE")
resp.delete_cookie("session_DO_NOT_SHARE")
@ -675,34 +665,32 @@ def logout():
return resp
@app.errorhandler(500)
def page_not_found(e):
async def page_not_found(e):
return """<img src="https://http.cat/images/500.jpg">""", 500
@app.errorhandler(400)
def page_not_found(e):
async def page_not_found(e):
return """<img src="https://http.cat/images/400.jpg">""", 400
@app.errorhandler(429)
def page_not_found(e):
async def page_not_found(e):
return """<img src="https://http.cat/images/429.jpg">""", 429
@app.errorhandler(404)
def page_not_found(e):
async def page_not_found(e):
return """<img src="https://http.cat/images/404.jpg">""", 404
@app.errorhandler(413)
def page_not_found(e):
async def page_not_found(e):
return "Images can't be larger than " + str(UPLOAD_LIMIT) + "MB", 413
if __name__ == "__main__":
print("[INFO] Server started")
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(('', int(PORT)))
serve(app, sockets=[sock], threads=9999)
hypercornconfig = Config()
hypercornconfig.bind = ("0.0.0.0" + ":" + PORT)
asyncio.run(serve(app, hypercornconfig))
print("[INFO] Server stopped")

View File

@ -1,7 +1,4 @@
Flask
Flask-Limiter
quart
werkzeug
waitress
hypercorn
requests
Flask-SSE
APScheduler

View File

@ -1,4 +1,5 @@
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap");
body {
margin: 0;
@ -10,13 +11,18 @@ body {
margin: 0;
padding: 10px;
border: solid;
border-color: grey;
border-color: rgb(195, 195, 195);
border-width: 0;
border-bottom-width: 1px;
background-color: white;
width: 100%;
position: fixed;
z-index: 2;
font-family: "Space Grotesk", sans-serif;
font-optical-sizing: auto;
}
.navbar h1 {
font-weight: 600;
}
.bottom {
@ -33,11 +39,16 @@ body {
text-decoration: none;
}
.navbar a {
padding-left: 5px;
padding-right: 5px;
}
.navbar .selected {
border: solid;
border-color: #f1b739;
border-width: 0;
border-bottom-width: 2px;
border-bottom-width: 3px;
}
.postDiv {
@ -80,6 +91,11 @@ body {
margin-right: 20%;
}
.accountform h1 {
font-family: "Space Grotesk", sans-serif;
font-optical-sizing: auto;
}
.accountform input {
padding: 7px;
border: solid;
@ -128,12 +144,17 @@ body {
.post {
margin: 10px;
padding: 5px;
padding: 10px;
margin-bottom: 10px;
border-radius: 10px;
background-color: rgb(250, 250, 250);
}
.post h2, .post h3 {
font-family: "Space Grotesk", sans-serif;
font-optical-sizing: auto;
}
.post img {
min-height: 200px;
max-height: 300px;

File diff suppressed because one or more lines are too long

View File

@ -76,25 +76,6 @@ function selectChannel(id) {
updateMessages(id)
}
function selectChannel(id) {
channelID = id
let selectedButton = channelDiv.querySelector(".selected");
if (selectedButton) {
selectedButton.classList.remove("selected");
}
let channelButton = document.getElementById("channelButton" + id)
if (channelButton) {
channelButton.classList.add("selected")
}
else {
console.log("channelButton not found")
}
updateMessages(id)
}
async function updateRooms() {
let response = await fetch("/api/chat/listrooms");
let rooms = await response.json()
@ -122,32 +103,34 @@ async function sendMessage(content, id) {
})
}
let updateInterval = 4100
messageBox.addEventListener("keyup", function onEvent(event) {
if (event.key === "Enter") {
if (!messageBox.value == "") {
if (messageBox.value.length < 140) {
sendMessage(messageBox.value, channelID)
messageBox.value = ""
updateInterval = 1300
}
}
}
})
let messageStream = new EventSource("/stream")
window.addEventListener("load", function () {
updateRooms()
updateMessages(channelID)
messageStream.addEventListener("publish", function (event) {
results = JSON.parse(event.data)
if (Number(results["message"]["roomid"]) == channelID) {
addMessage(results["message"]["content"], results["message"]["created"], results["message"]["creator"], results["message"]["roomid"])
}
})
}
})
window.addEventListener("beforeunload", function () {
messageStream.close()
})
function messageTimer(){
updateMessages(channelID)
if (updateInterval < 6000) {
updateInterval = updateInterval + 100
}
console.log(updateInterval)
setTimeout(messageTimer, updateInterval);
}
messageTimer();
updateRooms()
updateMessages()

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>burgercat</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
</head>
<body>
<div class="post">
<h2>burgercat is permanently shutting down on July 21th.</h2>
</div>
<div class="post">
<p>After 19 days of service, burgercat is permanently shutting down on July 21th 2023. 😔<br><br>
After July 21th, you will no longer be able to access burgercat. Please back up anything you want to keep before July 21th.</p>
<p>The Codeberg repository will be archived. Third-party instances will not be affected.</p>
<p><b>Thank you for using burgercat</b>, and thank <a href="https://codeberg.org/ffqq">@ffqq</a>, <a href="https://codeberg.org/carsand101">@carsand101</a>, <a href="https://codeberg.org/Mollomm1">@Mollomm1</a>, <a href="https://codeberg.org/TestingPlant">@TestingPlant</a>, <a href="https://codeberg.org/sewn">@sewn</a>, and <a href="/@Anonymous">@Anonymous</a> for contributing to the project.</p>
</div>
</body>
</html>

View File

@ -13,8 +13,9 @@
<div class="navbar">
<h1>burgercat</h1>
<a href="/">home</a>
<a href="/chat">chat</a>
<a href="/post">post</a>
<a class="selected" href="/apidocs">API</a>
<a class="selected" href="/apidocs">api</a>
{% if userdata %}
<a href="/settings/logout" class="right r">log out</a>
<a href="/settings" class="right">{{ userdata.username }}</a>
@ -32,7 +33,7 @@
<div class="postDiv">
<div class="post">
<h2>burgercat API documentation</h2>
for API things that require authentication, you will need to set the <code>session_DO_NOT_SHARE</code> cookie. the key might expire after 180 days.<br><br>
for API endpoints that require authentication, you'll need to set the <code>session_DO_NOT_SHARE</code> cookie. key might expire after 180 days.<br><br>
GET <code>/api/frontpage</code> - returns frontpage<br><br>
POST <code>/post</code> - post ctas - authentication required<br>
@ -43,9 +44,9 @@
POST <code>/api/delete</code> - remove posts - authentication required<br>
<code>id</code>, being the id of the post. you must be the owner of the post to delete.<br><br>
POST <code>/api/login</code> - get authentication key<br>
<code>username</code>, being the username and <code>password</code>, being the password. Returns authentication key.<br><br>
<code>username</code>, being the username and <code>password</code>, being the password.<br><br>
GET <code>/api/userinfo</code> - authentication required - Returns user info, username, ID, and account creation date.<br><br>
GET <code>/api/post/1/comments</code> - Returns comments of post
GET <code>/api/post/1/comments</code> - Returns comments of post. Replace 1 with the post ID.
</div>
</div>
</body>

View File

@ -36,7 +36,6 @@
<p class="warning">Your account has been banned. Reason: "{{ userdata.banned }}". <a href="/settings/logout">Log out</a></p>
{% endif %}
{% endif %}
<p class="warning">⚠️ burgercat will permanently shut down on July 21th. ⚠️ <a href="/static/shutdown.html">Read more</a></p>
{% for post in posts %}
<div class="post" id="post">
<p><a href='/@{{ getUser(post["creator"])["username"] }}' class="username usernamelink">{{ getUser(post["creator"])["username"] }}</a></p>
@ -44,7 +43,9 @@
{% if userdata %}
<p class="hidden" id="usernameElement">{{ userdata.username }}</p>
{% endif %}
{% if post["imageurl"] != "" %}
<img loading="lazy" src='{{ post["imageurl"] }}'>
{% endif %}
<p class="text">{{ post["textstr"] }}</p>
{% if getComments(post["id"]) | length > 0 %}
<div id="commentBurgerDiv" class="commentsdiv">
@ -114,12 +115,6 @@
</div>
{% endfor %}
</div>
<div class="bottom">
<span>
<a href="https://codeberg.org/burger-software/burgercat">burgercat</a>
<a href="https://codeberg.org/burger-software/burgercat/commit/{{ full_hash }}" style="float: right;">commit hash: {{ short_hash }}</a>
</span>
</div>
<script src="/static/js/main.js"></script>
</body>

View File

@ -34,28 +34,28 @@
{% endif %}
{% endwith %}
<form method="post" enctype="multipart/form-data">
<h2>Post</h2>
<h2>New post</h2>
<p>Image</p>
<input type="file" name="file" required>
<input type="file" name="file">
<br>
<p>Text</p>
<input name="title" type="text" placeholder="Text" required>
<p>Text (required)</p>
<input name="title" type="text" placeholder="Type something here.." required>
<br><br>
<input class="submit" type="submit" value="Post">
</form>
</div>
<div class="post">
<form method="post" enctype="multipart/form-data">
<h2>Rules</h2>
<h2>Posting Guidelines</h2>
<p>keep burgercat amazing: follow the posting guidelines</p>
<ul>
<li>Please do not spam</li>
<li>Treat everyone with respect, respect each others opinions</li>
<li>Posting NSFW content is strictly prohibited, doing so will get you an instant ban</li>
<li>Discussions regarding politics and related controversial topics are disallowed</li>
<li>Advertising is not allowed</li>
<li>Do not post links</li>
<li>Do not create alt-accounts to evade bans</li>
<li>Spam content isn't allowed, this includes promoting services/products.</li>
<li>Treat everyone with respect, please respect others opinions.</li>
<li>Posting NSFW content is strictly prohibited. This includes suggestive content.</li>
<li>We have zero-tolerance for racism, homophobia, transphobia etc.</li>
<li>Avoid politics and rage-bait.</li>
</ul>
<p>You might be moderated for things not listed here, use common sense.</p>
</form>
</div>
</div>

View File

@ -26,13 +26,14 @@
<div class="postDiv">
<div class="post">
<h3>Account</h3>
<p>Account Type: {% if userdata.administrator == 1 %}Administrator{% else %}Normal{% endif %}<br>
<h2>Your account</h2>
<a href="/@{{ userdata.username }}">View my public profile</a><br>
<h3>Information</h3>
<p>Account Type: {% if userdata.administrator == 1 %}Administrator{% else %}Regular{% endif %}<br>
Username: {{ userdata.username }}<br>
User ID: {{ userdata.id }}<br>
Created on: {{ createddate }}</p>
<br>
<a href="/@{{ userdata.username }}">View my public profile</a><br>
<h3>Actions</h3>
<a href="/settings/logout">Log out</a>
</div>
</div>