From a041e6a21f3f789257ecb8fe45334bc09cf7985f Mon Sep 17 00:00:00 2001 From: TestingPlant <> Date: Wed, 12 Jul 2023 01:55:30 +0000 Subject: [PATCH 01/22] fix!: remove /api/post The code handling /api/post is similar to the code handling /post, but /api/post has some bugs such as allowing banned users to upload files. Making /post the only API able to upload files would reduce duplicated code which reduces the chance of bugs. Although this removes /api/post, it shouldn't cause any issues since /api/post isn't currently usable because it requires a multipart request, but the code uses get_json which only works when the mimetype is application/json. --- main | 54 +----------------------------------------- templates/apidocs.html | 4 ++-- 2 files changed, 3 insertions(+), 55 deletions(-) diff --git a/main b/main index 64a1f11..42c3be4 100644 --- a/main +++ b/main @@ -394,58 +394,6 @@ def apilogin(): "error": "https://http.cat/images/400.jpg" }, 400 -@app.route("/api/post", methods=("GET", "POST")) -def apipost(): - usersession = request.cookies.get("session_DO_NOT_SHARE") - if usersession: - if request.method == "POST": - - data = request.get_json() - title = data["id"] - - if title == "": - return { - "error": "no title" - }, 403 - - if "file" not in request.files: - return { - "error": "no file" - }, 403 - - file = request.files["file"] - if file.filename == "": - return { - "error": "no file" - }, 403 - - if not allowed_file(file.filename): - return { - "error": "invalid file format" - }, 403 - - filename = secure_filename(file.filename) - finalfilename = secrets.token_hex(64) + filename - - file.save(os.path.join(UPLOAD_FOLDER, finalfilename)) - imgurl = "/cdn/" + finalfilename - - userCookie = get_session(usersession) - user = get_user(userCookie["id"]) - - if not user["banned"] == "0": - return { - "error": "banned" - }, 403 - - conn = get_db_connection() - conn.execute("INSERT INTO posts (textstr, imageurl, creator, created) VALUES (?, ?, ?, ?)", - (title, imgurl, userCookie["id"], str(time.time()))) - conn.commit() - conn.close() - - return "success", 200 - @app.route("/apidocs", methods=("GET", "POST")) def apidocs(): usersession = request.cookies.get("session_DO_NOT_SHARE") @@ -750,4 +698,4 @@ if __name__ == "__main__": sock.bind(('', int(PORT))) serve(app, sockets=[sock]) - print("[INFO] Server stopped") \ No newline at end of file + print("[INFO] Server stopped") diff --git a/templates/apidocs.html b/templates/apidocs.html index 28c7118..5cb8f28 100644 --- a/templates/apidocs.html +++ b/templates/apidocs.html @@ -35,7 +35,7 @@ for API things that require authentication, you will need to set the session_DO_NOT_SHARE cookie. the key might expire after 180 days.

GET /api/frontpage - returns frontpage

- POST /api/post - post ctas - authentication required
+ POST /post - post ctas - authentication required
title, being the title of the post and file, being an image file.
Supported file extensions: "png", "apng", "jpg", "jpeg", "gif", "svg", "webp"

POST /api/comment - comment on posts - authentication required
@@ -50,4 +50,4 @@ - \ No newline at end of file + -- 2.39.2 From 267d94c6af094e69bd16d6de39dc7da68f3f8b64 Mon Sep 17 00:00:00 2001 From: ffqq Date: Wed, 12 Jul 2023 18:30:53 +0300 Subject: [PATCH 02/22] feat: add scrollbar to chat and add site-wide style for scrollbar --- static/css/style.css | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/static/css/style.css b/static/css/style.css index ec8e5a7..d677f44 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -28,6 +28,7 @@ body { .postDiv { padding-top: 120px; + overflow-y: auto; } .profileDiv { @@ -224,12 +225,12 @@ body { } .messageDiv { - position: absolute; + position: fixed; width: calc(100% - 220px); - height: calc(100%); + height: calc(100% - 180px); padding-bottom: 50px; right: 0; - margin-top: -180px; + overflow-y: auto; display: flex; flex-direction: column-reverse; } @@ -237,6 +238,7 @@ body { .messageDiv p { font-size: 16px; margin-left: 10px; + z-index: 0; line-height: 8px; align-self: flex-start; } @@ -260,6 +262,20 @@ body { margin: 5px; } +::-webkit-scrollbar { + width: 10px; + background-color: transparent; +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: rgb(240, 240, 240); + border-radius: 10px; +} + .warning { width: 100%; background-color: #f1b739; @@ -275,6 +291,7 @@ body { :root { --gray-950: #030712; --gray-900: #111827; + --gray-850: #1D2533; --gray-800: #1f2937; --gray-700: #374151; --gray-600: #4b5563; @@ -387,5 +404,9 @@ body { border-color: var(--gray-700); } + ::-webkit-scrollbar-thumb { + background-color: var(--gray-850); + border-radius: 10px; + } + } -} \ No newline at end of file -- 2.39.2 From 4fe183765938da3ca08d6411b794636b07efd209 Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 12 Jul 2023 18:57:14 +0200 Subject: [PATCH 03/22] time --- static/js/chat.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/static/js/chat.js b/static/js/chat.js index c3b2400..b12c883 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -13,9 +13,21 @@ async function updateMessages(id) { document.querySelectorAll(".messageParagraph").forEach(el => el.remove()); for (let i in messages) { let messageParagraph = document.createElement("p") + let timeParagraph = document.createElement("p") messageParagraph.appendChild(document.createTextNode(messages[i]["creator"]["username"] + ": " + messages[i]["content"])) messageParagraph.classList.add("messageParagraph") messageParagraph.id = "messageParagraph" + messages[i]["id"] + messageParagraph.appendChild(timeParagraph) + + let date = new Date(Number(messages[i]["created"].split(".")[0])); + let utcHours = date.getUTCHours().toString().padStart(2, '0'); + let utcMinutes = date.getUTCMinutes().toString().padStart(2, '0'); + let timeZoneOffset = date.getTimezoneOffset(); + let hours = (utcHours - timeZoneOffset / 60 + 1).toString().padStart(2, '0'); + let minutes = utcMinutes.toString().padStart(2, '0'); + let time = (hours + ":" + minutes) + + messageParagraph.innerHTML = "" + time + " " + messageParagraph.innerHTML messageDiv.append(messageParagraph) } } -- 2.39.2 From a1224e3fb49a7ed57305956c68ec4f2aeabd18f2 Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 12 Jul 2023 18:58:24 +0200 Subject: [PATCH 04/22] d --- static/js/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/chat.js b/static/js/chat.js index b12c883..68559a8 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -23,7 +23,7 @@ async function updateMessages(id) { let utcHours = date.getUTCHours().toString().padStart(2, '0'); let utcMinutes = date.getUTCMinutes().toString().padStart(2, '0'); let timeZoneOffset = date.getTimezoneOffset(); - let hours = (utcHours - timeZoneOffset / 60 + 1).toString().padStart(2, '0'); + let hours = (utcHours - timeZoneOffset / 60 + 4).toString().padStart(2, '0'); let minutes = utcMinutes.toString().padStart(2, '0'); let time = (hours + ":" + minutes) -- 2.39.2 From a673a06479c9891c8e648a529248fcd68da73405 Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 12 Jul 2023 18:58:50 +0200 Subject: [PATCH 05/22] fr --- static/js/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/chat.js b/static/js/chat.js index 68559a8..c62c78f 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -23,7 +23,7 @@ async function updateMessages(id) { let utcHours = date.getUTCHours().toString().padStart(2, '0'); let utcMinutes = date.getUTCMinutes().toString().padStart(2, '0'); let timeZoneOffset = date.getTimezoneOffset(); - let hours = (utcHours - timeZoneOffset / 60 + 4).toString().padStart(2, '0'); + let hours = (utcHours - timeZoneOffset / 60 + 5).toString().padStart(2, '0'); let minutes = utcMinutes.toString().padStart(2, '0'); let time = (hours + ":" + minutes) -- 2.39.2 From e44b6752e708d7c1d31b66d79695a7a175e24077 Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 12 Jul 2023 18:59:43 +0200 Subject: [PATCH 06/22] u --- static/js/chat.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/chat.js b/static/js/chat.js index c62c78f..c4dc88c 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -23,6 +23,7 @@ async function updateMessages(id) { let utcHours = date.getUTCHours().toString().padStart(2, '0'); let utcMinutes = date.getUTCMinutes().toString().padStart(2, '0'); let timeZoneOffset = date.getTimezoneOffset(); + console.log(timeZoneOffset) let hours = (utcHours - timeZoneOffset / 60 + 5).toString().padStart(2, '0'); let minutes = utcMinutes.toString().padStart(2, '0'); let time = (hours + ":" + minutes) -- 2.39.2 From 81704c32e6c4cac264d2e8373c338008662e2d44 Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 12 Jul 2023 19:56:01 +0200 Subject: [PATCH 07/22] time thing --- static/js/chat.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/static/js/chat.js b/static/js/chat.js index c4dc88c..b0f7a6a 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -19,14 +19,7 @@ async function updateMessages(id) { messageParagraph.id = "messageParagraph" + messages[i]["id"] messageParagraph.appendChild(timeParagraph) - let date = new Date(Number(messages[i]["created"].split(".")[0])); - let utcHours = date.getUTCHours().toString().padStart(2, '0'); - let utcMinutes = date.getUTCMinutes().toString().padStart(2, '0'); - let timeZoneOffset = date.getTimezoneOffset(); - console.log(timeZoneOffset) - let hours = (utcHours - timeZoneOffset / 60 + 5).toString().padStart(2, '0'); - let minutes = utcMinutes.toString().padStart(2, '0'); - let time = (hours + ":" + minutes) + let time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(messages[i]["created"].split(".")[0]) * 1000 + 20275) messageParagraph.innerHTML = "" + time + " " + messageParagraph.innerHTML messageDiv.append(messageParagraph) -- 2.39.2 From 9b115bf9e0fc30c69183ab7fbb68a1664be4b51b Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 12 Jul 2023 20:23:58 +0200 Subject: [PATCH 08/22] something --- static/js/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/chat.js b/static/js/chat.js index b0f7a6a..acbb16a 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -19,7 +19,7 @@ async function updateMessages(id) { messageParagraph.id = "messageParagraph" + messages[i]["id"] messageParagraph.appendChild(timeParagraph) - let time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(messages[i]["created"].split(".")[0]) * 1000 + 20275) + let time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(messages[i]["created"].split(".")[0]) * 1000 + 20265) messageParagraph.innerHTML = "" + time + " " + messageParagraph.innerHTML messageDiv.append(messageParagraph) -- 2.39.2 From 9392841d125c6b553c69111dc6bed95bbaf62601 Mon Sep 17 00:00:00 2001 From: ffqq Date: Wed, 12 Jul 2023 23:12:36 +0300 Subject: [PATCH 09/22] feat: support for images in chat --- static/js/chat.js | 87 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/static/js/chat.js b/static/js/chat.js index acbb16a..52d97c1 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -5,29 +5,82 @@ let statusMessage = document.getElementById("statusMessage") let channelID = 0 +// create a cache to store the images (delete this once you add SSE) +const imageCache = {}; + async function updateMessages(id) { - try { - let response = await fetch("/api/chat/getmessages/" + id); - let messages = await response.json() - statusMessage.innerText = "" - document.querySelectorAll(".messageParagraph").forEach(el => el.remove()); - for (let i in messages) { - let messageParagraph = document.createElement("p") - let timeParagraph = document.createElement("p") - messageParagraph.appendChild(document.createTextNode(messages[i]["creator"]["username"] + ": " + messages[i]["content"])) - messageParagraph.classList.add("messageParagraph") - messageParagraph.id = "messageParagraph" + messages[i]["id"] - messageParagraph.appendChild(timeParagraph) + try { + const response = await fetch(`/api/chat/getmessages/${id}`); + const messages = await response.json(); + statusMessage.innerText = ""; + document.querySelectorAll(".messageParagraph").forEach((el) => el.remove()); - let time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(messages[i]["created"].split(".")[0]) * 1000 + 20265) + for (const message of messages) { + const messageParagraph = document.createElement("p"); + const timeParagraph = document.createElement("p"); + const { creator, content, id, created } = message; - messageParagraph.innerHTML = "" + time + " " + messageParagraph.innerHTML - messageDiv.append(messageParagraph) + messageParagraph.appendChild(document.createTextNode(`${creator.username}: ${content}`)); + messageParagraph.classList.add("messageParagraph"); + messageParagraph.id = `messageParagraph${id}`; + messageParagraph.appendChild(timeParagraph); + + const time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(created.split(".")[0]) * 1000 + 20265); + + messageParagraph.innerHTML = `${time} ${messageParagraph.innerHTML}`; + messageDiv.append(messageParagraph); + + const imgLinks = content?.match(/(https?:\/\/(?:cdn\.discordapp\.com|media\.discordapp\.net|media\.tenor\.com|i\.imgur\.com|burger\.ctaposter\.xyz)\/.+?\.(?:png|apng|webp|svg|jpg|jpeg|gif))(?=$|\s)/gi) || []; + + for (const link of imgLinks) { + // delete the code below once you add sse + if (imageCache[link]) { + messageParagraph.appendChild(imageCache[link].cloneNode(true)); + } else { + // delete the code above once you add sse + const img = new Image(); + img.src = link; + img.className = "messageImage"; + img.onload = () => { + const maxWidth = 400; + const maxHeight = 400; + let { width, height } = img; + if (width > maxWidth || height > maxHeight) { + const ratio = Math.min(maxWidth / width, maxHeight / height); + width *= ratio; + height *= ratio; + } + img.width = width; + img.height = height; + // delete the line below once you add sse + imageCache[link] = img.cloneNode(true); + messageParagraph.appendChild(img); + }; } + } } - catch { - statusMessage.innerText = "Not connected" + } catch { + statusMessage.innerText = "Not connected"; + } +} + +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) } function selectChannel(id) { -- 2.39.2 From 48321233c31ece264034f4595849db5f98ebb80b Mon Sep 17 00:00:00 2001 From: ffqq Date: Thu, 13 Jul 2023 00:28:28 +0000 Subject: [PATCH 10/22] feat: current git commit hash shown to visitors --- main | 18 ++++++++++++++++-- static/css/style.css | 22 ++++++++++++++++++++++ templates/main.html | 7 ++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/main b/main index 2491f71..0114a2e 100644 --- a/main +++ b/main @@ -7,6 +7,7 @@ import json import secrets import datetime import socket +import subprocess from itertools import groupby from waitress import serve from werkzeug.utils import secure_filename @@ -126,6 +127,17 @@ def get_session(id): return "error" return post +def get_current_commit(output_format="full"): + if output_format == "short": + length = 8 + else: + length = 40 + + try: + output = subprocess.check_output(["git", "rev-parse", f"--short={length}", "HEAD"]).decode().strip() + return output + except subprocess.CalledProcessError: + return "Error fetching git commit" ALLOWED_EXTENSIONS = {"png", "apng", "jpg", "jpeg", "gif", "svg", "webp"} @@ -139,14 +151,16 @@ 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() + commit_hash_long = get_current_commit() + commit_hash_short = get_current_commit(output_format="short") conn.close() if usersession: userCookie = get_session(usersession) user = get_user(userCookie["id"]) - return render_template("main.html", userdata=user, posts=posts) + return render_template("main.html", userdata=user, posts=posts, commit_hash_long=commit_hash_long, commit_hash_short=commit_hash_short) else: - return render_template("main.html", posts=posts) + return render_template("main.html", posts=posts, commit_hash_long=commit_hash_long, commit_hash_short=commit_hash_short) @app.route("/chat", methods=("GET", "POST")) def chat(): diff --git a/static/css/style.css b/static/css/style.css index d677f44..7df4fbe 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -19,6 +19,20 @@ body { z-index: 2; } +.bottom { + margin: 0; + border: solid; + border-color: grey; + border-width: 0; + border-bottom-width: 1px; + background-color: white; +} + +.bottom a { + color: lightgrey; + text-decoration: none; +} + .navbar .selected { border: solid; border-color: #f1b739; @@ -332,6 +346,14 @@ body { background-color: var(--gray-800); } + .bottom { + background-color: var(--gray-900); + } + + .bottom a { + color: var(--gray-800) + } + .navbar a, a { color: white; border-size: 0px; diff --git a/templates/main.html b/templates/main.html index 1e1b17a..f75d0c1 100644 --- a/templates/main.html +++ b/templates/main.html @@ -113,7 +113,12 @@ {% endfor %} - + -- 2.39.2 From df75e02efc359979d3bcb80b8c5ae11949f75be1 Mon Sep 17 00:00:00 2001 From: ffqq Date: Thu, 13 Jul 2023 02:50:16 +0000 Subject: [PATCH 11/22] fix: wrap links inside of for convenience --- static/js/chat.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/js/chat.js b/static/js/chat.js index 52d97c1..92116c2 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -20,7 +20,11 @@ async function updateMessages(id) { const timeParagraph = document.createElement("p"); const { creator, content, id, created } = message; - messageParagraph.appendChild(document.createTextNode(`${creator.username}: ${content}`)); + // Check if the message content contains any links that are not image links + const linkRegex = /(https?:\/\/[^\s]+(?$1"); + + messageParagraph.innerHTML = `${creator.username}: ${messageContent}`; messageParagraph.classList.add("messageParagraph"); messageParagraph.id = `messageParagraph${id}`; messageParagraph.appendChild(timeParagraph); -- 2.39.2 From 69d60208656c95d509a05d3b95b732adf810429a Mon Sep 17 00:00:00 2001 From: ffqq Date: Thu, 13 Jul 2023 03:05:33 +0000 Subject: [PATCH 12/22] fix: made commit hash consistent with the running server --- main | 19 ++++--------------- templates/main.html | 2 +- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/main b/main index 0114a2e..57a5084 100644 --- a/main +++ b/main @@ -127,17 +127,8 @@ def get_session(id): return "error" return post -def get_current_commit(output_format="full"): - if output_format == "short": - length = 8 - else: - length = 40 - - try: - output = subprocess.check_output(["git", "rev-parse", f"--short={length}", "HEAD"]).decode().strip() - return output - except subprocess.CalledProcessError: - return "Error fetching git commit" +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"} @@ -151,16 +142,14 @@ 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() - commit_hash_long = get_current_commit() - commit_hash_short = get_current_commit(output_format="short") conn.close() if usersession: userCookie = get_session(usersession) user = get_user(userCookie["id"]) - return render_template("main.html", userdata=user, posts=posts, commit_hash_long=commit_hash_long, commit_hash_short=commit_hash_short) + return render_template("main.html", userdata=user, posts=posts, full_hash=full_hash, short_hash=short_hash) else: - return render_template("main.html", posts=posts, commit_hash_long=commit_hash_long, commit_hash_short=commit_hash_short) + return render_template("main.html", posts=posts, full_hash=full_hash, short_hash=short_hash) @app.route("/chat", methods=("GET", "POST")) def chat(): diff --git a/templates/main.html b/templates/main.html index f75d0c1..2a3932f 100644 --- a/templates/main.html +++ b/templates/main.html @@ -116,7 +116,7 @@ -- 2.39.2 From 7b1e636187ad8c3418a831f178e6945ee00ae299 Mon Sep 17 00:00:00 2001 From: ffqq Date: Thu, 13 Jul 2023 03:22:47 +0000 Subject: [PATCH 13/22] feat: hide image links from messages and improve overall handling of links --- static/js/chat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/chat.js b/static/js/chat.js index 92116c2..1c7194e 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -20,8 +20,8 @@ async function updateMessages(id) { const timeParagraph = document.createElement("p"); const { creator, content, id, created } = message; - // Check if the message content contains any links that are not image links - const linkRegex = /(https?:\/\/[^\s]+(?$1"); messageParagraph.innerHTML = `${creator.username}: ${messageContent}`; -- 2.39.2 From 40fcc16cc5178b0cc81392dc4c4c6e6f780d7c16 Mon Sep 17 00:00:00 2001 From: ffqq Date: Thu, 13 Jul 2023 05:27:14 +0000 Subject: [PATCH 14/22] fix: inconsistency between dark mode and light mode --- static/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/style.css b/static/css/style.css index 7df4fbe..7ac1133 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -351,7 +351,7 @@ body { } .bottom a { - color: var(--gray-800) + color: var(--gray-700) } .navbar a, a { -- 2.39.2 From cbbdf37e57c9b5e5b19c0e08f048ab512822e526 Mon Sep 17 00:00:00 2001 From: maaa Date: Thu, 13 Jul 2023 16:51:21 +0200 Subject: [PATCH 15/22] update README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d13ed3e..206be24 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,6 @@ python main - close previous server ### contribution guidelines: -- please check that your PR does not break anything \ No newline at end of file +- please check that your PR does not break anything +- unless absolutely nessecary, avoid adding dependecies +- for consistency, use quotation marks, not apostrophes when possible \ No newline at end of file -- 2.39.2 From 7c5f7efc91a2bd5c2ecb1e1dd5e212e703e6b5f4 Mon Sep 17 00:00:00 2001 From: maaa Date: Fri, 14 Jul 2023 02:29:58 +0200 Subject: [PATCH 16/22] server side events --- .gitignore | 3 +- README.md | 2 +- config.ini | 1 + main | 32 ++++++------ requirements.txt | 6 ++- static/css/style.css | 2 +- static/js/chat.js | 116 +++++++++++++++++++++---------------------- 7 files changed, 83 insertions(+), 79 deletions(-) diff --git a/.gitignore b/.gitignore index 1149575..75f20af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ database.db -uploads \ No newline at end of file +uploads +__pycache__ \ No newline at end of file diff --git a/README.md b/README.md index 206be24..2f7fee2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ burgercat: burger social media ### self hosting: -this guide assumes you have git and python3 installed +this guide assumes you have git, python3, and redis installed on your server ``` git clone https://codeberg.org/burger-software/burgercat diff --git a/config.ini b/config.ini index b4471fb..ff3fe97 100644 --- a/config.ini +++ b/config.ini @@ -4,3 +4,4 @@ SECRET_KEY = placeholder UPLOAD_FOLDER = uploads PASSWORD_REQUIREMENT = 12 UPLOAD_LIMIT = 4 +REDIS_URL = redis://localhost \ No newline at end of file diff --git a/main b/main index 57a5084..bd57b71 100644 --- a/main +++ b/main @@ -7,6 +7,7 @@ import json import secrets import datetime import socket +import threading import subprocess from itertools import groupby from waitress import serve @@ -16,6 +17,8 @@ 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 apscheduler.schedulers.background import BackgroundScheduler # read config file config = configparser.ConfigParser() @@ -26,10 +29,13 @@ 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.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) @@ -164,7 +170,7 @@ def chat(): @app.route("/api/chat/listrooms") def chatlistrooms(): conn = get_db_connection() - rooms = conn.execute("SELECT * FROM chatrooms ORDER BY roomname ASC;").fetchall() + rooms = conn.execute("SELECT * FROM chatrooms ORDER BY id ASC;").fetchall() conn.close() template = [] @@ -181,7 +187,7 @@ def chatlistrooms(): @app.route("/api/chat/getmessages/") @limiter.exempt def chatget(roomid): - messages = get_messages(roomid, 40) + messages = get_messages(roomid, 150) template = [] @@ -203,9 +209,6 @@ def chatget(roomid): return(template), 200 -burgerMessageUpdate = True -burgerMessageCache = "" - @app.route("/api/chat/send/", methods=("GET", "POST")) def chatsend(roomid): usersession = request.cookies.get("session_DO_NOT_SHARE") @@ -215,9 +218,6 @@ def chatsend(roomid): data = request.get_json() content = data["content"] - print(content) - print(roomid) - userCookie = get_session(usersession) user = get_user(userCookie["id"]) @@ -226,17 +226,21 @@ def chatsend(roomid): "error": "banned" }, 403 + chatMessageContent = { + "content": content, + "creator": user["username"], + "roomid": roomid, + "created": str(time.time()) + } + + sse.publish({"message": chatMessageContent}, type="publish") + conn = get_db_connection() conn.execute("INSERT INTO chatmessages (content, chatroom_id, creator, created) VALUES (?, ?, ?, ?)", (content, roomid, userCookie["id"], str(time.time()))) conn.commit() conn.close() - global burgerMessageCache - global burgerMessageUpdate - burgerMessageUpdate = True - burgerMessageCache = user["username"] + ": " + content - return "success", 200 @@ -699,6 +703,6 @@ if __name__ == "__main__": 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]) + serve(app, sockets=[sock], threads=9999) print("[INFO] Server stopped") diff --git a/requirements.txt b/requirements.txt index 79aec11..ccd3104 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ -flask +Flask Flask-Limiter werkzeug waitress -requests \ No newline at end of file +requests +Flask-SSE +APScheduler \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 7ac1133..3999647 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -246,7 +246,7 @@ body { right: 0; overflow-y: auto; display: flex; - flex-direction: column-reverse; + flex-direction: column; } .messageDiv p { diff --git a/static/js/chat.js b/static/js/chat.js index 1c7194e..eed3f97 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -5,67 +5,56 @@ let statusMessage = document.getElementById("statusMessage") let channelID = 0 -// create a cache to store the images (delete this once you add SSE) -const imageCache = {}; +function addMessage(content, created, creator, roomid) { + const messageParagraph = document.createElement("p"); + const timeParagraph = document.createElement("p"); + + const hideRegex = /(https?:\/\/(?:cdn\.discordapp\.com|media\.discordapp\.net|media\.tenor\.com|i\.imgur\.com)\/.+?\.(?:png|apng|webp|svg|jpg|jpeg|gif))(?=$|\s)/gi; + let messageContent = content.replace(hideRegex, ""); + + messageParagraph.innerText = `${creator}: ${messageContent}`; + messageParagraph.classList.add("messageParagraph"); + messageParagraph.id = "messageParagraph"; + messageParagraph.appendChild(timeParagraph); + + const time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(created.split(".")[0]) * 1000 + 20265); + + messageParagraph.innerHTML = `${time} ${messageParagraph.innerHTML}`; + messageDiv.append(messageParagraph); + + const imgLinks = content?.match(/(https?:\/\/(?:cdn\.discordapp\.com|media\.discordapp\.net|media\.tenor\.com|i\.imgur\.com|burger\.ctaposter\.xyz)\/.+?\.(?:png|apng|webp|svg|jpg|jpeg|gif))(?=$|\s)/gi) || []; + + for (const link of imgLinks) { + const img = new Image(); + img.src = link; + img.className = "messageImage"; + img.onload = () => { + const maxWidth = 400; + const maxHeight = 400; + let { width, height } = img; + if (width > maxWidth || height > maxHeight) { + const ratio = Math.min(maxWidth / width, maxHeight / height); + width *= ratio; + height *= ratio; + } + img.width = width; + img.height = height; + messageParagraph.appendChild(img); + }; + } + messageDiv.scrollTop = messageDiv.scrollHeight - messageDiv.clientHeight; +} async function updateMessages(id) { - try { const response = await fetch(`/api/chat/getmessages/${id}`); const messages = await response.json(); statusMessage.innerText = ""; document.querySelectorAll(".messageParagraph").forEach((el) => el.remove()); - for (const message of messages) { - const messageParagraph = document.createElement("p"); - const timeParagraph = document.createElement("p"); - const { creator, content, id, created } = message; - - // Check if the message content contains any links that are not image links and hide image links - const linkRegex = /(https?:\/\/[^\s]+(?$1"); - - messageParagraph.innerHTML = `${creator.username}: ${messageContent}`; - messageParagraph.classList.add("messageParagraph"); - messageParagraph.id = `messageParagraph${id}`; - messageParagraph.appendChild(timeParagraph); - - const time = new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "numeric" }).format(Number(created.split(".")[0]) * 1000 + 20265); - - messageParagraph.innerHTML = `${time} ${messageParagraph.innerHTML}`; - messageDiv.append(messageParagraph); - - const imgLinks = content?.match(/(https?:\/\/(?:cdn\.discordapp\.com|media\.discordapp\.net|media\.tenor\.com|i\.imgur\.com|burger\.ctaposter\.xyz)\/.+?\.(?:png|apng|webp|svg|jpg|jpeg|gif))(?=$|\s)/gi) || []; - - for (const link of imgLinks) { - // delete the code below once you add sse - if (imageCache[link]) { - messageParagraph.appendChild(imageCache[link].cloneNode(true)); - } else { - // delete the code above once you add sse - const img = new Image(); - img.src = link; - img.className = "messageImage"; - img.onload = () => { - const maxWidth = 400; - const maxHeight = 400; - let { width, height } = img; - if (width > maxWidth || height > maxHeight) { - const ratio = Math.min(maxWidth / width, maxHeight / height); - width *= ratio; - height *= ratio; - } - img.width = width; - img.height = height; - // delete the line below once you add sse - imageCache[link] = img.cloneNode(true); - messageParagraph.appendChild(img); - }; - } - } + for (const message of messages.reverse()) { + const { creator, content, roomid, created } = message; + addMessage(content, created, creator["username"], roomid) } - } catch { - statusMessage.innerText = "Not connected"; - } } function selectChannel(id) { @@ -138,20 +127,27 @@ messageBox.addEventListener("keyup", function onEvent(event) { if (!messageBox.value == "") { if (messageBox.value.length < 140) { sendMessage(messageBox.value, channelID) - updateMessages(channelID) messageBox.value = "" } } } }) -function update() { - updateMessages(channelID) - - setTimeout(update, 1500); -} +let messageStream = new EventSource("/stream") window.addEventListener("load", function () { updateRooms() - update() + 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() }) \ No newline at end of file -- 2.39.2 From 07bbfd9530a20f02868d9ef74e4ce789a669b26c Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 19 Jul 2023 20:16:13 +0200 Subject: [PATCH 17/22] shutdown --- static/css/style.css | 2 +- static/shutdown.html | 23 +++++++++++++++++++++++ templates/main.html | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 static/shutdown.html diff --git a/static/css/style.css b/static/css/style.css index 3999647..4382dff 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -291,7 +291,7 @@ body { } .warning { - width: 100%; + width: calc(100% - 20px); background-color: #f1b739; color: black; padding: 10px; diff --git a/static/shutdown.html b/static/shutdown.html new file mode 100644 index 0000000..6b607c4 --- /dev/null +++ b/static/shutdown.html @@ -0,0 +1,23 @@ + + + + + burgercat + + + + + + + +
+

burgercat is permanently shutting down on July 21th.

+
+
+

After 19 days of service, burgercat is permanently shutting down on July 21th 2023. 😔

+ After July 21th, you will no longer be able to access burgercat. Please back up anything you want to keep before July 21th.

+

The Codeberg repository will be archived. Third-party instances will not be affected.

+

Thank you for using burgercat, and thank @ffqq, @carsand101, @Mollomm1, and @TestingPlant, @Anonymous for contributing to the project.

+
+ + \ No newline at end of file diff --git a/templates/main.html b/templates/main.html index 2a3932f..8b58fe5 100644 --- a/templates/main.html +++ b/templates/main.html @@ -36,6 +36,7 @@

Your account has been banned. Reason: "{{ userdata.banned }}". Log out

{% endif %} {% endif %} +

⚠️ burgercat will permanently shut down on July 21th. ⚠️ Read more

{% for post in posts %}

{{ getUser(post["creator"])["username"] }}

-- 2.39.2 From faa4dddc53221ec0192a1387eae84f68dc92bc3e Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 19 Jul 2023 20:21:25 +0200 Subject: [PATCH 18/22] a --- static/shutdown.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/shutdown.html b/static/shutdown.html index 6b607c4..3e42fbc 100644 --- a/static/shutdown.html +++ b/static/shutdown.html @@ -17,7 +17,7 @@

After 19 days of service, burgercat is permanently shutting down on July 21th 2023. 😔

After July 21th, you will no longer be able to access burgercat. Please back up anything you want to keep before July 21th.

The Codeberg repository will be archived. Third-party instances will not be affected.

-

Thank you for using burgercat, and thank @ffqq, @carsand101, @Mollomm1, and @TestingPlant, @Anonymous for contributing to the project.

+

Thank you for using burgercat, and thank @ffqq, @carsand101, @Mollomm1, @TestingPlant, and @Anonymous for contributing to the project.

\ No newline at end of file -- 2.39.2 From 8ca281b1d3045907e9257bf4146e4638ff407400 Mon Sep 17 00:00:00 2001 From: maaa Date: Wed, 19 Jul 2023 20:31:40 +0200 Subject: [PATCH 19/22] add sewn --- static/shutdown.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/shutdown.html b/static/shutdown.html index 3e42fbc..a9b7ee5 100644 --- a/static/shutdown.html +++ b/static/shutdown.html @@ -17,7 +17,7 @@

After 19 days of service, burgercat is permanently shutting down on July 21th 2023. 😔

After July 21th, you will no longer be able to access burgercat. Please back up anything you want to keep before July 21th.

The Codeberg repository will be archived. Third-party instances will not be affected.

-

Thank you for using burgercat, and thank @ffqq, @carsand101, @Mollomm1, @TestingPlant, and @Anonymous for contributing to the project.

+

Thank you for using burgercat, and thank @ffqq, @carsand101, @Mollomm1, @TestingPlant, @sewn, and @Anonymous for contributing to the project.

\ No newline at end of file -- 2.39.2 From e6359db97db87d8edf8aea07de2ec69d532f1e80 Mon Sep 17 00:00:00 2001 From: maaa Date: Mon, 29 Apr 2024 22:36:42 +0200 Subject: [PATCH 20/22] we are so back --- README.md | 6 +- config.ini | 3 +- hashpass.py | 4 + main | 210 +++++++++++++++++++--------------------- requirements.txt | 9 +- static/css/style.css | 27 +++++- static/css/xp.css | 137 -------------------------- static/js/chat.js | 49 +++------- static/shutdown.html | 23 ----- templates/apidocs.html | 9 +- templates/main.html | 9 +- templates/post.html | 24 ++--- templates/settings.html | 9 +- 13 files changed, 173 insertions(+), 346 deletions(-) create mode 100644 hashpass.py delete mode 100644 static/css/xp.css delete mode 100644 static/shutdown.html diff --git a/README.md b/README.md index 2f7fee2..d484197 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config.ini b/config.ini index ff3fe97..596f2a9 100644 --- a/config.ini +++ b/config.ini @@ -3,5 +3,4 @@ PORT = 8080 SECRET_KEY = placeholder UPLOAD_FOLDER = uploads PASSWORD_REQUIREMENT = 12 -UPLOAD_LIMIT = 4 -REDIS_URL = redis://localhost \ No newline at end of file +UPLOAD_LIMIT = 12 \ No newline at end of file diff --git a/hashpass.py b/hashpass.py new file mode 100644 index 0000000..d64f4fe --- /dev/null +++ b/hashpass.py @@ -0,0 +1,4 @@ +from werkzeug.security import generate_password_hash, check_password_hash + +passwordthing = input("insert pass: ") +print(generate_password_hash(passwordthing)) \ No newline at end of file diff --git a/main b/main index bd57b71..9ec863f 100644 --- a/main +++ b/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/") -@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/", 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("/@", 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 """""", 404 @app.route("/api/page/", methods=("GET", "POST")) -def apipageuser(userid): +async def apipageuser(userid): pageuser = get_user(userid) addhtml = """ - """ + """ if not pageuser == "error": return addhtml + pageuser["htmldescription"] @@ -276,7 +260,7 @@ def apipageuser(userid): return """""", 404 @app.route("/@/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("