import { SHA512_256, type ServerWebSocket } from "bun"; import { SnowflakeGenerator } from "./snowflake.ts"; function getContentType(path: string): string { if (path.endsWith(".html")) return "text/html"; if (path.endsWith(".css")) return "text/css"; if (path.endsWith(".js")) return "application/javascript"; if (path.endsWith(".json")) return "application/json"; if (path.endsWith(".png")) return "image/png"; if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg"; if (path.endsWith(".svg")) return "image/svg+xml"; if (path.endsWith(".ico")) return "image/x-icon"; return "application/octet-stream"; } enum OpcodesClientbound { authorizeLogin, initMessages, updateMessages, registerSuccess, error, users, keepAlive = 9, } enum OpcodesServerbound { identify, sendMessage, register, getUsers, keepAlive = 9, } interface user { name: string; username: string; pfp: string; id: number; token: string; password: string; dev: boolean; bot: boolean; } interface message { id: string; author: user; content: string; timestamp: number; editedTimestamp?: number; replyTo?: string; hasSent: true; // client sends false but server should always say true } const messages: message[] = await Bun.file("./db/messages.json").json(); const users: user[] = await Bun.file("./db/users.json").json(); const generator = new SnowflakeGenerator(); function sendError(ws: ServerWebSocket, reason: string) { ws.send( JSON.stringify({ op: OpcodesClientbound.error, data: { reason, }, }) ); ws.close(); } const server = Bun.serve({ port: 80, websocket: { open(ws) { console.log("connected to ws"); ws.subscribe("messages"); // @ts-expect-error ws.interval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send( JSON.stringify({ op: OpcodesClientbound.keepAlive, data: null, }) ); console.log("sending keeaplivve"); } else { // @ts-expect-error clearInterval(ws.interval); } // @ts-expect-error guhh clearTimeout(ws.pingTimeout); // @ts-expect-error guhh ws.pingTimeout = setTimeout(() => { ws.close(); }, 30_000); }, 60_000); // @ts-expect-error guhh ws.pingTimeout = null; }, message(ws, msg) { const obj = JSON.parse(msg.toString()); let user: string | any[]; switch (obj.op) { case OpcodesServerbound.identify: user = users.filter(user => user.token === obj.data.token); if (!(user && user.length !== 0)) { user = users.filter( (user) => user.password === new SHA512_256().update(obj.data.password ?? "").digest("base64") ); } if (user && user.length !== 0) { ws.send( JSON.stringify({ op: OpcodesClientbound.authorizeLogin, data: user[0], }) ); ws.send( JSON.stringify({ op: OpcodesClientbound.initMessages, data: messages, }) ); } else { sendError(ws, "Not a real user"); } break; case OpcodesServerbound.sendMessage: user = users.filter((user) => user.token === obj.data.token) as user[]; if (user && user.length !== 0) { obj.op = OpcodesClientbound.updateMessages; obj.data.id = generator.generateSnowflake(); obj.data.author = JSON.parse(JSON.stringify(user[0])); obj.data.author.password = null; obj.data.author.token = null; obj.data.hasSent = true; obj.data.token = null; obj.data.author.dev = user[0].dev; ws.publish("messages", JSON.stringify(obj)); ws.send(JSON.stringify(obj)); messages.push(obj.data); Bun.write("./db/messages.json", JSON.stringify(messages)); } else { sendError(ws, "Not a real user"); } break; case OpcodesServerbound.getUsers: let mmiow = JSON.parse(JSON.stringify(users)) as user[]; mmiow.forEach(user => { // @ts-expect-error shush user.token = null; // @ts-expect-error shush user.password = null; }) ws.send(JSON.stringify({ op: OpcodesClientbound.users, data: mmiow })) break; case OpcodesServerbound.keepAlive: // @ts-expect-error guhh clearTimeout(ws.pingTimeout); // @ts-expect-error guhh ws.pingTimeout = setTimeout(() => { ws.close(); }, 90_000); break; default: break; } }, close(ws) { console.log("client disconnected"); }, }, async fetch(req, server) { const url = new URL(req.url); let path = url.pathname; if (path === "/") { path = "/index.html"; } if (url.pathname === "/ws") { const upgraded = server.upgrade(req, { // @ts-expect-error guhh whqt data: { name: new URL(req.url).searchParams.get("name"), }, }); if (!upgraded) { return new Response("Upgrade failed", { status: 400 }); } return; } path = path.replace(/\.\./g, ""); try { const file = Bun.file(`./dist${path}`); if (!(await file.exists())) { return new Response( "
Bun
Bun