server side events
This commit is contained in:
parent
cbbdf37e57
commit
7c5f7efc91
|
@ -1,2 +1,3 @@
|
|||
database.db
|
||||
uploads
|
||||
uploads
|
||||
__pycache__
|
|
@ -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
|
||||
|
|
|
@ -4,3 +4,4 @@ SECRET_KEY = placeholder
|
|||
UPLOAD_FOLDER = uploads
|
||||
PASSWORD_REQUIREMENT = 12
|
||||
UPLOAD_LIMIT = 4
|
||||
REDIS_URL = redis://localhost
|
32
main
32
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/<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")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
flask
|
||||
Flask
|
||||
Flask-Limiter
|
||||
werkzeug
|
||||
waitress
|
||||
requests
|
||||
requests
|
||||
Flask-SSE
|
||||
APScheduler
|
|
@ -246,7 +246,7 @@ body {
|
|||
right: 0;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.messageDiv p {
|
||||
|
|
|
@ -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()
|
||||
})
|
Reference in New Issue