server side events

This commit is contained in:
maaa 2023-07-14 02:29:58 +02:00
parent cbbdf37e57
commit 7c5f7efc91
7 changed files with 83 additions and 79 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
database.db
uploads
__pycache__

View File

@ -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

View File

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

32
main
View File

@ -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/<roomid>")
@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/<roomid>", 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")

View File

@ -1,5 +1,7 @@
flask
Flask
Flask-Limiter
werkzeug
waitress
requests
Flask-SSE
APScheduler

View File

@ -246,7 +246,7 @@ body {
right: 0;
overflow-y: auto;
display: flex;
flex-direction: column-reverse;
flex-direction: column;
}
.messageDiv p {

View File

@ -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 = `<span style="color: #515051; font-size: 14px;">${time}</span> ${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]+(?<!\.(?:png|apng|webp|svg|jpg|jpeg|gif)))(?=\s|$)|(?<=\s|^)(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;
let messageContent = content.replace(linkRegex, "<a href='$1' target='_blank'>$1</a>");
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 = `<span style="color: #515051; font-size: 14px;">${time}</span> ${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()
})