Multiroom support

This commit is contained in:
Maxwell Jeffress
2025-03-14 08:44:06 +11:00
parent e463d3dd2b
commit eea257d617
6 changed files with 914 additions and 226 deletions

View File

@@ -127,7 +127,7 @@ body {
overflow-y: auto;
border: 0px;
padding: 20px;
margin: 10px 0; /* Remove horizontal margin */
margin: 10px 0;
flex-grow: 1;
width: 100%;
box-sizing: border-box;
@@ -155,7 +155,6 @@ body {
box-sizing: border-box;
}
/* Update input styles */
input {
color: white;
background: rgba(0, 0, 0, 0.5);
@@ -350,3 +349,71 @@ input {
margin: 10px;
}
.room-list {
width: 200px;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
margin-right: 10px;
padding: 10px;
height: 100%;
overflow-y: auto;
}
.room-header {
font-weight: bold;
text-align: center;
padding: 5px;
margin-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
.room-item {
padding: 10px;
margin: 5px 0;
border-radius: 5px;
cursor: pointer;
transition: background 0.2s;
}
.room-item:hover {
background: rgba(255, 255, 255, 0.1);
}
.room-item.active {
background: rgba(104, 79, 255, 0.3);
font-weight: bold;
}
.create-room-button {
width: 100%;
padding: 8px;
margin-top: 10px;
background: rgba(73, 255, 145, 0.3);
border: none;
border-radius: 5px;
color: white;
cursor: pointer;
transition: background 0.2s;
}
.create-room-button:hover {
background: rgba(73, 255, 145, 0.5);
}
.room-title {
font-weight: bold;
font-size: 16px;
margin-bottom: 5px;
padding: 5px 10px;
background: rgba(0, 0, 0, 0.3);
border-radius: 5px;
}
.message.system-message {
color: rgba(73, 255, 145, 0.8);
font-style: italic;
}
.message.history-message {
color: rgba(255, 255, 255, 0.7);
}

View File

@@ -9,26 +9,26 @@
<link type="text/css" rel="stylesheet" href="index.css">
<link type="text/css" rel="stylesheet" href="gradient.css">
<link rel="shortcut icon" type="image/jpg" href="favicon.ico"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body class="gradient">
<div id="login">
<div class="section">
<div class="box">
<h3>Chookchat</h3>
<input type="text" id="username" placeholder="Username"><br>
<input type="password" id="password" placeholder="Password"><br>
<button class="bluebutton" onclick="connect()">Log in</button>
<button class="greenbutton" onclick="register()">Register</button>
<button class="redbutton" onclick="showConfig()">Show Server Config</button>
<input type="text" id="username" placeholder="Username"><br>
<input type="password" id="password" placeholder="Password"><br>
<button class="bluebutton" onclick="connect()">Log in</button>
<button class="greenbutton" onclick="register()">Register</button>
<button class="redbutton" onclick="showConfig()">Show Server Config</button>
<div id="serverStatus"></div>
</div>
<div class="box" style="display: none;" id="serverconfig">
<input type="text" id="serverUrl" value="bobcompass.online" placeholder="Server URL"><br>
<input type="text" id="serverPort" value="443" placeholder="Server Port"><br>
<input type="checkbox" id="securityStatus" checked>
<label for="securityStatus">Use HTTPS/WSS</label>
<div class="box" style="display: none;" id="serverconfig">
<input type="text" id="serverUrl" value="bobcompass.online" placeholder="Server URL"><br>
<input type="text" id="serverPort" value="443" placeholder="Server Port"><br>
<input type="checkbox" id="securityStatus" checked>
<label for="securityStatus">Use HTTPS/WSS</label>
</div>
</div>
</div>
@@ -37,25 +37,32 @@
<div id="meet"></div>
<div class="box">
<div id="users" class="suttle"></div>
<button id="meeting" class="bluebutton" onclick="startMeeting()">📞</button>
<div id="messagebox" class="box" style="height: 600px;"><div></div></div>
<button id="meeting" class="bluebutton" onclick="startMeeting()">📞</button>
<div id="messagebox" class="box" style="height: 600px;">
<div></div>
</div>
<div id="typing" class="suttle"></div>
<div id="upload" class="suttle" style="display: none;"><input type="file" id="fileupload"><button class="bluebutton" onclick="uploadFile()">Upload</button></input></div>
<div class="input-container">
<button onclick="showFileUpload()" class="bluebutton">📁</button>
<button onclick="showEggs()" class="bluebutton">🥚</button>
<input type="text" id="messageInput" placeholder="Send a message..." autofocus>
<button onclick="sendMessage()" class="bluebutton">Send</button>
<div id="upload" class="suttle" style="display: none;">
<input type="file" id="fileupload" accept="image/*,text/*">
<button class="bluebutton" onclick="uploadFile()">Upload</button>
</input>
</div>
<div class="input-container">
<button onclick="showFileUpload()" class="bluebutton">📁</button>
<button onclick="showEggs()" class="bluebutton">🥚</button>
<input type="text" id="messageInput" placeholder="Send a message..." autofocus>
<button onclick="sendMessage()" class="bluebutton">Send</button>
</div>
</div>
<div id="eggs" style="display: none;">
<div id="eggs-list" class="eggs-list">
<button class="egg-item" onclick="eggNotepad()">📝 Notepad</button>
<div id="eggs-list" class="eggs-list">
<button class="egg-item" onclick="eggNotepad()">📝 Notepad</button>
</div>
<!-- Eggs Start Here -->
</div>
</div>
</div>
<script src="index.js"></script>
<script src="index.js"></script>
</body>
</html>

View File

@@ -6,6 +6,8 @@ let password;
let typingTimeout;
let typingPeople = new Array();
let api;
let currentRoom = "general";
let availableRooms = ["general"];
function resizeMessaging() {
const messagingDiv = document.getElementById('messaging');
@@ -17,7 +19,6 @@ function resizeMessaging() {
resizeMessaging();
// Add resize listener to handle window resizing
window.addEventListener('resize', resizeMessaging);
function showConfig() {
@@ -61,7 +62,72 @@ function getUploadUrl() {
return `${protocol}://${cleanUrl}:${serverPort}/api/upload`;
}
function connect() {
async function getRooms() {
try {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'https' : 'http';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
const url = `${protocol}://${cleanUrl}:${serverPort}/api/rooms`;
const response = await fetch(url, {
mode: "no-cors"
});
const data = await response.json();
return JSON.parse(data.content);
} catch (error) {
console.error('Error fetching rooms:', error);
return ["general"];
}
}
async function getRoomHistory(roomName) {
try {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'https' : 'http';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
const url = `${protocol}://${cleanUrl}:${serverPort}/api/room/${roomName}/history`;
const response = await fetch(url, {
mode: "no-cors"
});
const history = await response.text();
return history;
} catch (error) {
console.error('Error fetching room history:', error);
return '';
}
}
const imageMimeTypes = [
'image/webp',
'image/tiff',
'image/svg+xml',
'image/png',
'image/jpeg',
'image/vnd.microsoft.icon',
'image/gif',
'image/bmp',
];
const imageTypes = [
'png',
'jpg',
'jpeg',
'svg',
'tiff',
'gif',
'webp',
'bmp'
];
function isImage(file) {
const fileSplit = file.split(".");
return imageTypes.includes(fileSplit[fileSplit.length - 1]);
}
async function connect() {
username = document.getElementById('username').value;
password = document.getElementById('password').value;
@@ -79,13 +145,22 @@ function connect() {
var incorrectDetail = 0;
ws.onopen = () => {
ws.onopen = async () => {
if (typeof Notification !== "undefined") {
Notification.requestPermission();
}
console.log('Connected!');
document.getElementById('login').style.display = 'none';
document.getElementById('messaging').style.display = 'block';
try {
availableRooms = await getRooms();
updateRoomList();
} catch (error) {
console.error('Failed to get room list:', error);
availableRooms = ["general"];
}
const connectMessage = {
"type": "connect",
"username": username,
@@ -93,6 +168,9 @@ function connect() {
"content": `${username} joined the room!`
}
ws.send(JSON.stringify(connectMessage));
joinRoom("general");
ws.onmessage = (event) => {
if (event.data === "ping") {
ws.send("pong");
@@ -140,24 +218,88 @@ function connect() {
}
return;
}
else if (message.type == "file") {
else if (message.type == "roomUsers" && message.username == "system") {
const usersInRoom = message.content;
const roomName = message.room;
usersDiv = document.getElementById("users");
if (usersDiv) {
usersDiv.textContent = `Users in ${roomName}: ${usersInRoom}`;
if (roomName !== currentRoom) {
currentRoom = roomName;
updateCurrentRoomDisplay();
}
}
return;
}
else if (message.type == "roomsList" && message.username == "system") {
try {
availableRooms = JSON.parse(message.content);
updateRoomList();
} catch (error) {
console.error('Error parsing rooms list:', error);
}
return;
}
else if (message.type == "roomCreated" || message.type == "roomJoin") {
if (message.room) {
currentRoom = message.room;
updateCurrentRoomDisplay();
clearMessages();
loadRoomHistory(currentRoom);
updateRoomList();
}
// Display the system message
const messagesDiv = document.getElementById('messagebox');
const fileElement = document.createElement('embed');
if (fileElement) {
if (messagesDiv) {
messagesDiv.appendChild(fileElement);
fileElement.className = "file";
fileElement.src = message.content;
fileElement.height = 200;
fileElement.addEventListener("click", function() {
window.open(message.content, "_blank");
});
messagesDiv.scrollTop = messagesDiv.scrollHeight;
const messageElement = document.createElement('div');
if (messageElement && messagesDiv) {
messagesDiv.appendChild(messageElement);
messageElement.className = 'message system-message';
messageElement.textContent = message.content;
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
return;
}
else if (message.type == "file") {
// Only show file if it's for current room
if (message.room && message.room !== currentRoom) {
return;
}
const messagesDiv = document.getElementById('messagebox');
let filename = message.content.replace("https://maxwellj.xyz/chookchat/uploads/", "");
if (isImage(filename)) {
const imagePreview = document.createElement('img');
if (imagePreview) {
if (messagesDiv) {
messagesDiv.appendChild(imagePreview);
imagePreview.src = message.content;
imagePreview.height = 300;
imagePreview.addEventListener("click", function() {
window.open(message.content, "_blank");
});
}
}
} else {
const fileButton = document.createElement('button');
if (fileButton) {
if (messagesDiv) {
messagesDiv.appendChild(fileButton);
fileButton.textContent = `Open ${filename} in new tab`;
fileButton.className = "bluebutton";
fileButton.addEventListener("click", function() {
window.open(message.content, "_blank");
});
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
}
}
return;
}
else if (message.type == "call") {
if (message.room && message.room !== currentRoom) {
return;
}
const messagesDiv = document.getElementById('messagebox');
const callButton = document.createElement('div');
if (callButton) {
@@ -189,6 +331,10 @@ function connect() {
}
}
else if (message.type == "message") {
if (message.room && message.room !== currentRoom) {
return;
}
const messagesDiv = document.getElementById('messagebox');
const messageElement = document.createElement('div');
if (messageElement) {
@@ -203,7 +349,6 @@ function connect() {
const notifiction = new Notification("Chookchat", {body: messageElement.textContent});
}
}
// Egg message logic
else {
return
}
@@ -213,7 +358,40 @@ function connect() {
alert("Chookchat has disconnected :/ Refresh the page to try again");
}
}
async function createRoom(roomName) {
const message = {
type: 'createRoom',
username: username,
token: md5(password),
room: roomName,
content: ""
};
ws.send(JSON.stringify(message));
setTimeout(updateRoomList, 500);
}
async function joinRoom(roomName) {
if (roomName === currentRoom) {
const usersMessage = {
type: 'getUsersInRoom',
username: username,
token: md5(password),
room: roomName,
content: ""
};
ws.send(JSON.stringify(usersMessage));
return;
}
const message = {
type: 'joinRoom',
username: username,
token: md5(password),
room: roomName,
content: ""
};
ws.send(JSON.stringify(message));
}
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value.trim();
@@ -222,10 +400,11 @@ function sendMessage() {
"type": "message",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": message
}
}
if (processedMessage && ws && ws.readyState === WebSocket.OPEN) {
if (processedMessage.content && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(processedMessage));
messageInput.value = '';
@@ -236,21 +415,13 @@ function sendMessage() {
"type": "typing",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": "0"
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(stoppedTypingMessage));
}
}
const processedMessage2 = {
"type": "typing",
"username": username,
"token": md5(password),
"content": "0"
}
if (processedMessage && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(processedMessage2));
}
}
function showFileUpload() {
@@ -262,13 +433,14 @@ function showFileUpload() {
async function uploadFile() {
const fileInput = document.getElementById("fileupload");
if (!fileInput.files.length) {
alert("Please add a file!");
return;
}
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("room", currentRoom);
try {
const response = await fetch(getUploadUrl(), {
method: 'POST',
@@ -281,6 +453,7 @@ async function uploadFile() {
"type": "message",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": `Sent a file`
}
if (processedMessage && ws && ws.readyState === WebSocket.OPEN) {
@@ -310,7 +483,11 @@ document.getElementById('password').addEventListener('keypress', (event) => {
}
});
function register() {
async function doRegister(username, password) {
return fetch(`${getSignupUrl()}username:{${username}}token:{${md5(password)}}`).then((response)=>response.json()).then((responseJson)=>{return responseJson});
}
async function register() {
username = document.getElementById('username').value;
password = document.getElementById('password').value;
@@ -319,7 +496,12 @@ function register() {
return;
}
window.open(`${getSignupUrl()}username:{${username}}token:{${md5(password)}}`);
const response = await this.doRegister(username, password);
if (response.type == "success") {
alert("Account created! Click 'log in' to access Chookchat!")
} else {
alert(`We couldn't create your account :( Reason: ${response.content}`)
}
}
function sleep(ms) {
@@ -335,6 +517,7 @@ function startTypingIndicator() {
"type": "typing",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": "1"
};
@@ -347,6 +530,7 @@ function startTypingIndicator() {
"type": "typing",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": "0"
};
@@ -390,6 +574,7 @@ function startMeeting() {
"type": "call",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": link
};
if (ws && ws.readyState === WebSocket.OPEN) {
@@ -401,11 +586,95 @@ function startMeeting() {
document.getElementById('messageInput').addEventListener('input', startTypingIndicator);
async function updateRoomList() {
let roomListDiv = document.getElementById('room-list');
if (!roomListDiv) {
const messagingDiv = document.querySelector('.messaging-container');
roomListDiv = document.createElement('div');
roomListDiv.id = 'room-list';
roomListDiv.className = 'room-list';
messagingDiv.insertBefore(roomListDiv, messagingDiv.firstChild);
}
try {
availableRooms = await getRooms();
} catch (error) {
console.error('Error updating room list:', error);
}
roomListDiv.innerHTML = '<div class="room-header">Rooms</div>';
availableRooms.forEach(room => {
const roomElement = document.createElement('div');
roomElement.className = `room-item ${room === currentRoom ? 'active' : ''}`;
roomElement.textContent = room;
roomElement.addEventListener('click', () => joinRoom(room));
roomListDiv.appendChild(roomElement);
});
const createRoomButton = document.createElement('button');
createRoomButton.className = 'create-room-button';
createRoomButton.textContent = '+ New Room';
createRoomButton.addEventListener('click', promptCreateRoom);
roomListDiv.appendChild(createRoomButton);
}
function updateCurrentRoomDisplay() {
const roomTitle = document.getElementById('room-title');
if (roomTitle) {
roomTitle.remove();
}
const roomItems = document.querySelectorAll('.room-item');
roomItems.forEach(item => {
if (item.textContent === currentRoom) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
function clearMessages() {
const messagebox = document.getElementById('messagebox');
if (messagebox) {
messagebox.innerHTML = '';
}
}
async function loadRoomHistory(roomName) {
try {
const history = await getRoomHistory(roomName);
if (history) {
const messagebox = document.getElementById('messagebox');
const lines = history.split('\n');
lines.forEach(line => {
if (line.trim()) {
const messageElement = document.createElement('div');
messageElement.className = 'message history-message';
messageElement.textContent = line;
messagebox.appendChild(messageElement);
}
});
messagebox.scrollTop = messagebox.scrollHeight;
}
} catch (error) {
console.error('Error loading room history:', error);
}
}
function promptCreateRoom() {
const roomName = prompt('Enter a name for the new room:');
if (roomName && roomName.trim()) {
createRoom(roomName.trim());
}
}
function showEggs() {
const eggsPanel = document.getElementById('eggs');
eggsPanel.style.display = "block";
// Adjust main box width when eggs panel is toggled
const mainBox = document.querySelector('#messaging .box');
if (eggsPanel.classList.contains('visible')) {
mainBox.style.width = 'calc(100% - 310px)';
@@ -418,6 +687,15 @@ function closeEggs() {
const eggsPanel = document.getElementById('eggs');
eggsPanel.style.display = "block";
}
const uploadField = document.getElementById("fileupload");
uploadField.onchange = function() {
if(this.files[0].size > 10485760) {
alert("That file is too big bro. Not as big as my (message terminated)");
this.value = "";
}
};
// Eggs begin here