we are so back
This commit is contained in:
parent
8ca281b1d3
commit
e6359db97d
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
passwordthing = input("insert pass: ")
|
||||
print(generate_password_hash(passwordthing))
|
210
main
210
main
|
@ -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 = formFiles["file"]
|
||||
|
||||
file = request.files["file"]
|
||||
if file.filename == "":
|
||||
flash("No file selected :3")
|
||||
return redirect(url_for("post"))
|
||||
|
||||
if not allowed_file(file.filename):
|
||||
flash("File is not an image!")
|
||||
return redirect(url_for("post"))
|
||||
if (file):
|
||||
if not allowed_file(file.filename):
|
||||
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")
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
Flask
|
||||
Flask-Limiter
|
||||
quart
|
||||
werkzeug
|
||||
waitress
|
||||
requests
|
||||
Flask-SSE
|
||||
APScheduler
|
||||
hypercorn
|
||||
requests
|
|
@ -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
|
@ -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
|
||||
}
|
||||
updateMessages(channelID)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let messageStream = new EventSource("/stream")
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
updateRooms()
|
||||
function messageTimer(){
|
||||
updateMessages(channelID)
|
||||
if (updateInterval < 6000) {
|
||||
updateInterval = updateInterval + 100
|
||||
}
|
||||
|
||||
messageStream.addEventListener("publish", function (event) {
|
||||
results = JSON.parse(event.data)
|
||||
console.log(updateInterval)
|
||||
setTimeout(messageTimer, updateInterval);
|
||||
}
|
||||
|
||||
if (Number(results["message"]["roomid"]) == channelID) {
|
||||
addMessage(results["message"]["content"], results["message"]["created"], results["message"]["creator"], results["message"]["roomid"])
|
||||
}
|
||||
})
|
||||
})
|
||||
messageTimer();
|
||||
|
||||
|
||||
updateRooms()
|
||||
updateMessages()
|
||||
|
||||
window.addEventListener("beforeunload", function () {
|
||||
messageStream.close()
|
||||
})
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Reference in New Issue