Compare commits
9 Commits
31ce3f29f3
...
0.0.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d839652916 | ||
![]() |
b1b1c0accc | ||
![]() |
61c60f7906 | ||
![]() |
a4b6721ebc | ||
![]() |
41c9e7f53f | ||
378cb7c867 | |||
![]() |
e06f3891c8 | ||
![]() |
97a26f7615 | ||
![]() |
18be6c6fd4 |
94
README.md
94
README.md
@@ -2,96 +2,26 @@
|
||||
|
||||
## What is Chookpen?
|
||||
|
||||
Chookpen is a simple messaging service which allows users to talk to each other through text chat. In it's current state it has very low security and has many bugs, so please don't use this in production yet.
|
||||
Chookpen is a lightweight, secure-ish chat server implementation focused on simplicity and real-time communication. It features user authentication, persistent message history, real-time updates via WebSockets, and supports both traditional HTTP endpoints and WebSocket connections for flexible integration. It uses very little resources on a system, often using a maximum of 20mb. Chookpen is BETA SOFTWARE, and MAY BE BUGGY! Don't expect too much.
|
||||
|
||||
## How does Chookpen work?
|
||||
## A guide to this repository
|
||||
|
||||
Chookpen works by having a client and a server. The client sends a request to the server, which includes a username, hashed password and message contents. The server returns that request with previous messages.
|
||||
Chookpen is split up into multiple parts, the server and different reference clients for different platforms. For extensive documentation of all parts of Chookpen, a repository wiki is being worked on which will be released soon. For now, if you want to learn more about Chookpen, visit each folder.
|
||||
|
||||
## How do I get started?
|
||||
## Acknowledgments
|
||||
|
||||
First, you'll need to install Gradle and Java version 17 or later. These instructions vary depending on your OS, so look those up.
|
||||
Chookpen relies on a lot of FOSS software, and wouldn't be possible without it! Here's some quick links to the original projects if you're interested:
|
||||
|
||||
Next, clone the repository with `git clone https://git.maxwellj.xyz/max/chookpen`. Change to the `server` directory. Create the files `chatHistory` and `userDatabase`. Next, open a terminal and `gradle run`. Once the server starts, you can now send requests to the server! Using a web browser or `curl`, whichever is preferable, make a request to `http://localhost:8000/api/createAccount/username:{(pick a username)}token:{(pick a token)}`. If all works, you should have created an account!
|
||||
### [Javalin](https://javalin.io)
|
||||
|
||||
Once you've created an account through the API, you can send requests using your token and username. You can send a request to see the chat history like this: `http://localhost:8000/api/username:{(your username)}token:{(your password)}`. If you'd like to send a message, it's like this: `http://localhost:8000/api/username:{(your username)}token:{(your password)}message:{(a message)}`.
|
||||
An easy to use web server for Kotlin and Java. Chookpen wouldn't be possible without it!
|
||||
|
||||
If you don't want all the hassle of sending requests, you can use the experimental CLI client. Open a new terminal, and cd to `client-cli`. Run `gradle installDist`, and wait for it to build. Cd to build/install. Make a file called `.chookpen.profile` in your home directory and add the following information, styled like a Unix /etc/passwd (colons in between items):
|
||||
### [Gradle](https://gradle.org)
|
||||
|
||||
`username:password:server:port:0`
|
||||
The build tool for compiling Chookpen. It just works!
|
||||
|
||||
NOTE: When creating your account with this method, use the MD5 hash of your password. If you're unsure of what it is, just fill in all your details in .chookpen.profile and run the program. The server will tell you the hash the CLI created.
|
||||
### [Kotlin](https://kotlinlang.org)
|
||||
|
||||
Once you're set up, run the CLI program in bin. Use the .bat if you're on Windows (yucky). You should be able to send and recieve messages! If that isn't working, make sure your .chookpen.profile is in your system home directory.
|
||||
The language Chookpen is coded in. How else does it work?
|
||||
|
||||
## How can I make my own client?
|
||||
|
||||
Chookpen is very simple in how it works at present. There's only one chat on each server, and messages are sent as plaintext (unless you put your server behind a reverse proxy like Caddy). You can send a request to create an account, get messages or send a message.
|
||||
|
||||
### Brief API documentation for the server
|
||||
|
||||
#### Create account
|
||||
|
||||
**Usage**
|
||||
|
||||
`http://(address:port)/api/createAccount/username:{(username)}token:{(password-hash)}`
|
||||
|
||||
**Successful response**
|
||||
|
||||
`Success`
|
||||
|
||||
**Unsuccessful responses**
|
||||
|
||||
`Username already exists` - Choose a new username
|
||||
|
||||
`No username` - Add a username
|
||||
|
||||
`No token` - Add a token
|
||||
|
||||
#### Send a message
|
||||
|
||||
**Usage**
|
||||
|
||||
`http://(address:port)/api/send/username:{(username)}token:{(password-hash)}message:{(message to send)}`
|
||||
|
||||
**Successful response**
|
||||
|
||||
`Success`
|
||||
|
||||
**Unsuccessful responses**
|
||||
|
||||
`Unknown account` - Either you don't have an account or your username is wrong
|
||||
|
||||
`Invalid token` - Password is wrong
|
||||
|
||||
`No data provided` - Add a message
|
||||
|
||||
#### Get messages
|
||||
|
||||
**Usage**
|
||||
|
||||
`http://(address:port)/api/syncmessages/username:{(username)}token:{(password-hash)}`
|
||||
|
||||
**Successful response**
|
||||
|
||||
A successful response should contain everything in `chatHistory` in the directory you run the server in.
|
||||
|
||||
**Unsuccessful responses**
|
||||
|
||||
`Unknown account` - Either you don't have an account or your username is wrong
|
||||
|
||||
`Invalid token` - Password is wrong
|
||||
|
||||
### Websockets
|
||||
|
||||
Chookpen supports websockets for live updating of messages. You can establish a websocket connection with the URL `ws://(server):(port)/api/websocket/`. Send a login request to the websocket: `username:{(username)}password:{(password)}`. If your username and password are correct, you should start recieving messages. Send a message: `username:{(username)}password:{(password)}message:{(message)}`
|
||||
|
||||
## Some handy tips and tricks
|
||||
|
||||
Chookpen Server **does not support HTTPS!** You can put Chookpen Server behind a reverse proxy and that will sort that out for you.
|
||||
|
||||
Chookpen Server and CLI client are both in an alpha stage, keep this in mind before doing ANYTHING with it!
|
||||
|
||||
Chookpen Server is not ready for production :/
|
||||
|
||||
The port for the server is 7070.
|
||||
### [OpenJDK](https://openjdk.org)
|
||||
|
@@ -2,6 +2,8 @@ package xyz.maxwellj.chookpen.client
|
||||
|
||||
import okhttp3.*
|
||||
import java.util.Scanner
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
import java.io.File
|
||||
@@ -52,7 +54,7 @@ fun main() {
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
//.pingInterval(30, TimeUnit.SECONDS)
|
||||
.pingInterval(30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
|
BIN
client-web/InterVariable.ttf
Normal file
BIN
client-web/InterVariable.ttf
Normal file
Binary file not shown.
43
client-web/gradient.css
Normal file
43
client-web/gradient.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/* Reset default margin and ensure full viewport coverage */
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(
|
||||
30deg,
|
||||
#50C878, /* green */
|
||||
#7FFFD4, /* aqua */
|
||||
#87CEEB, /* light blue */
|
||||
#00008B, /* dark blue */
|
||||
#800080, /* purple */
|
||||
#50C878, /* green */
|
||||
#7FFFD4, /* aqua */
|
||||
#87CEEB, /* light blue */
|
||||
#00008B, /* dark blue */
|
||||
#800080, /* purple */
|
||||
#50C878 /* green */
|
||||
);
|
||||
background-size: 1000% 1000%;
|
||||
animation: gradient 120s ease infinite;
|
||||
}
|
||||
|
||||
/* Gradient animation keyframes */
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional: styling for centered content */
|
||||
.content {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
font-size: 2rem;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
208
client-web/index.css
Normal file
208
client-web/index.css
Normal file
@@ -0,0 +1,208 @@
|
||||
@font-face {
|
||||
font-family: "inter";
|
||||
src: url("InterVariable.ttf");
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
input {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
font-family: "inter";
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.tiny {
|
||||
font-size: 5pt;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35pt;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
#messagebox {
|
||||
overflow-y: auto;
|
||||
border: 0px;
|
||||
padding: 20px;
|
||||
margin: 10px 10px;
|
||||
flex-grow: 1; /* This makes it take up remaining space */
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.box p {
|
||||
margin: 10px;
|
||||
}
|
||||
.box h3 {
|
||||
margin: 10px;
|
||||
}
|
||||
.box img {
|
||||
margin: 10px;
|
||||
}
|
||||
.box button {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.bluebutton {
|
||||
color: white;
|
||||
font-size: 12pt;
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
font-family: inter;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
}
|
||||
.greenbutton {
|
||||
color: white;
|
||||
font-size: 12pt;
|
||||
background: rgba(0, 255, 0, 0.3);
|
||||
font-family: inter;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
}
|
||||
.redbutton {
|
||||
color: white;
|
||||
font-size: 12pt;
|
||||
background: rgba(255, 0, 0, 0.3);
|
||||
font-family: inter;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
}
|
||||
.backbutton {
|
||||
color: white;
|
||||
font-size: 12pt;
|
||||
background: rgba(255, 0, 0, 0.3);
|
||||
font-family: inter;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
color: white;
|
||||
background: rgb(231,255,68);
|
||||
background: linear-gradient(336deg, rgba(231,255,68,0.67) 0%, rgba(73,255,145,0.67) 43%, rgba(104,79,255,1) 100%);
|
||||
font-family: inter;
|
||||
background-attachment: fixed;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Update section styles */
|
||||
.section {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
width: calc(100vw - 40px);
|
||||
height: calc(100vh - 40px);
|
||||
box-sizing: border-box; /* Add this to include padding in width calculation */
|
||||
}
|
||||
|
||||
/* Update box styles */
|
||||
.box {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 10px;
|
||||
margin: 10px auto;
|
||||
padding: 20px;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Special handling for messaging box */
|
||||
#messaging .box {
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
position: relative; /* For absolute positioning of input container */
|
||||
}
|
||||
|
||||
#messagebox {
|
||||
overflow-y: auto;
|
||||
border: 0px;
|
||||
padding: 20px;
|
||||
margin: 10px 0; /* Remove horizontal margin */
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Style for individual messages */
|
||||
.message {
|
||||
text-align: left;
|
||||
margin: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Create a container for message input and send button */
|
||||
.input-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 10px; /* Space between input and button */
|
||||
/* padding: 10px;*/
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Update input styles */
|
||||
input {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 5px 5px;
|
||||
font-family: "inter";
|
||||
max-width: 300px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Special style for message input */
|
||||
#messageInput {
|
||||
max-width: none;
|
||||
flex-grow: 1; /* Take up remaining space */
|
||||
margin: 0; /* Remove default margins */
|
||||
}
|
||||
|
||||
/* Update button styles */
|
||||
.bluebutton, .greenbutton, .redbutton, .backbutton {
|
||||
max-width: 200px;
|
||||
width: auto; /* Allow button to size to content */
|
||||
margin: 0; /* Remove default margins */
|
||||
white-space: nowrap; /* Prevent button text from wrapping */
|
||||
}
|
44
client-web/index.html
Normal file
44
client-web/index.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chookchat</title>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="messaging">
|
||||
<div class="box">
|
||||
<div id="messagebox" class="box" style="height: 600px;"><div></div></div>
|
||||
<div class="input-container">
|
||||
<input type="text" id="messageInput" placeholder="Send a message...">
|
||||
<button onclick="sendMessage()" class="bluebutton">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
173
client-web/index.js
Normal file
173
client-web/index.js
Normal file
@@ -0,0 +1,173 @@
|
||||
alert("Chookchat is currently in early development, expect bugs! Please don't try breaking the public server, do that with your own test server (read more in the Git repo). Thanks for trying Chookchat!")
|
||||
|
||||
const width = $(document).width();
|
||||
|
||||
if (width < 512) {
|
||||
console.log("enlarging");
|
||||
const loginDiv = document.getElementById('login');
|
||||
if (loginDiv) {
|
||||
console.log(loginDiv.style.width = `${width}px`);
|
||||
console.log("enlarged?");
|
||||
}
|
||||
}
|
||||
|
||||
let ws;
|
||||
let username;
|
||||
let password;
|
||||
|
||||
function resizeMessaging() {
|
||||
const messagingDiv = document.getElementById('messaging');
|
||||
if (messagingDiv) {
|
||||
messagingDiv.style.width = `${window.innerWidth - 40}px`; // -40 for body margins
|
||||
messagingDiv.style.height = `${window.innerHeight - 40}px`; // -40 for body margins
|
||||
}
|
||||
}
|
||||
|
||||
// Call it initially
|
||||
resizeMessaging();
|
||||
|
||||
// Add resize listener to handle window resizing
|
||||
window.addEventListener('resize', resizeMessaging);
|
||||
|
||||
function showConfig() {
|
||||
const serverconfig = document.getElementById('serverconfig')
|
||||
if (serverconfig) {
|
||||
serverconfig.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function md5(string) {
|
||||
return CryptoJS.MD5(string).toString();
|
||||
}
|
||||
|
||||
function getUrl() {
|
||||
const serverUrl = document.getElementById('serverUrl').value.trim();
|
||||
const serverPort = document.getElementById('serverPort').value;
|
||||
const useWss = document.getElementById('securityStatus').checked;
|
||||
const protocol = useWss ? 'wss' : 'ws';
|
||||
|
||||
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
|
||||
|
||||
return `${protocol}://${cleanUrl}:${serverPort}/api/websocket`;
|
||||
}
|
||||
function getSignupUrl() {
|
||||
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?:\/\/)/, '');
|
||||
|
||||
return `${protocol}://${cleanUrl}:${serverPort}/api/createaccount/`;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
username = document.getElementById('username').value;
|
||||
password = document.getElementById('password').value;
|
||||
|
||||
if (!username || !password) {
|
||||
alert('Please enter a username and password');
|
||||
return;
|
||||
}
|
||||
|
||||
const wsUrl = getUrl();
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
var incorrectDetail = 0;
|
||||
|
||||
ws.onopen = () => {
|
||||
Notification.requestPermission();
|
||||
console.log('Connected!');
|
||||
document.getElementById('login').style.display = 'none';
|
||||
document.getElementById('messaging').style.display = 'block';
|
||||
const connectMessage = {
|
||||
"type": "connect",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": `${username} joined the room!`
|
||||
}
|
||||
ws.send(JSON.stringify(connectMessage));
|
||||
ws.onmessage = (event) => {
|
||||
if (event.data === "ping") {
|
||||
ws.send("pong");
|
||||
return;
|
||||
}
|
||||
const message = JSON.parse(event.data);
|
||||
if (message.type == "error") {
|
||||
if (message.username == "system") {
|
||||
if (message.content == "invalid-token") {
|
||||
alert("Your password is incorrect! Please try putting in your password right.");
|
||||
incorrectDetail = 1;
|
||||
location.reload();
|
||||
}
|
||||
if (message.content == "unknown-account") {
|
||||
alert("That username isn't on the server. Maybe try registering?");
|
||||
incorrectDetail = 1;
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
const messagesDiv = document.getElementById('messagebox');
|
||||
const messageElement = document.createElement('div');
|
||||
if (messageElement) {
|
||||
if (messagesDiv) {
|
||||
messagesDiv.appendChild(messageElement);
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
messageElement.className = 'message';
|
||||
messageElement.textContent = `${message.username}: ${message.content}` ;
|
||||
}
|
||||
}
|
||||
if (document.hidden) {
|
||||
const notifiction = new Notification("Chookchat", {body: messageElement.textContent});
|
||||
}
|
||||
};
|
||||
}
|
||||
ws.onclose = () => {
|
||||
alert("Chookchat has disconnected :/ Refresh the page to try again");
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const message = messageInput.value.trim();
|
||||
|
||||
const processedMessage = {
|
||||
"type": "message",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": message
|
||||
}
|
||||
|
||||
if (processedMessage && ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(processedMessage));
|
||||
messageInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('messageInput').addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('password').addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
connect();
|
||||
}
|
||||
});
|
||||
|
||||
function register() {
|
||||
username = document.getElementById('username').value;
|
||||
password = document.getElementById('password').value;
|
||||
|
||||
if (!username || !password) {
|
||||
alert('Please enter a username and password');
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(`${getSignupUrl()}username:{${username}}token:{${md5(password)}}`);
|
||||
}
|
127
server/README.md
127
server/README.md
@@ -1,5 +1,130 @@
|
||||
# Chookpen Server
|
||||
|
||||
This is the official Chookpen server implementation. It provides a backend and exposes an API.
|
||||
Key features:
|
||||
- Real-time messaging with WebSocket support
|
||||
- User authentication and account management
|
||||
- Persistent message history
|
||||
- Simple HTTP API for basic operations
|
||||
- Built-in static file serving for web clients
|
||||
- Support for encryption keys
|
||||
- Active user tracking and presence notifications
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java 17 or later
|
||||
- Caddy (for HTTPS reverse proxy) - [Download from caddyserver.com](https://caddyserver.com/download)
|
||||
- Some kind of OS, any should do
|
||||
- 50mb free space, 30mb avaliable ram (yes I know, very bloated and inefficient)
|
||||
|
||||
## Running the Server
|
||||
|
||||
Prebuilt binaries are available for immediate use. Simply download the latest server release and run:
|
||||
|
||||
```bash
|
||||
./run
|
||||
```
|
||||
|
||||
The server will start on port 7070 by default.
|
||||
|
||||
## Building from Source
|
||||
|
||||
First, install Gradle. Unless you want to painfully compile everything manually, Gradle is your best friend.
|
||||
|
||||
Clone the repository and CD into the server directory. Then run:
|
||||
|
||||
```bash
|
||||
gradle build
|
||||
```
|
||||
|
||||
To create a distribution (you don't need to run gradle build, it will do that for you):
|
||||
```bash
|
||||
./gradlew installDist
|
||||
```
|
||||
|
||||
To run the server, create the files `chatHistory` and `userDatabase` in the directory you're running it from, then run the script to start Chookpen.
|
||||
|
||||
## API Documentation
|
||||
|
||||
### HTTP Endpoints
|
||||
|
||||
#### Send Message
|
||||
- Endpoint: `/api/send/{content}`
|
||||
- Content Format: `username:{name}token:{token}message:{message}`
|
||||
- Response: "Success" or error message
|
||||
|
||||
#### Create Account
|
||||
- Endpoint: `/api/createaccount/{content}`
|
||||
- Content Format: `username:{name}token:{password}`
|
||||
- Response: "Success" or error message
|
||||
|
||||
#### Sync Messages
|
||||
- Endpoint: `/api/syncmessages/{content}`
|
||||
- Content Format: `username:{name}token:{token}`
|
||||
- Response: Chat history or error message
|
||||
|
||||
#### Auth Key (being worked on for E2EE)
|
||||
- Endpoint: `/api/authkey/{content}`
|
||||
- Content Format: `username:{name}token:{token}authkey:{key}`
|
||||
- Response: "Success" or error message
|
||||
|
||||
## WebSocket Interface
|
||||
|
||||
Connect to `/api/websocket` for real-time updates.
|
||||
|
||||
### WebSocket Messages
|
||||
|
||||
1. Server to Client:
|
||||
- Ping messages: "ping"
|
||||
- User updates: "!users:{user1,user2,user3}"
|
||||
- Chat messages: "username: message"
|
||||
|
||||
2. Client to Server:
|
||||
- Pong response: "pong"
|
||||
- Message format: Same as HTTP send endpoint
|
||||
|
||||
## Setting up HTTPS with Caddy
|
||||
|
||||
Caddy provides automatic HTTPS and serves as a reverse proxy for your Chookpen server. [Download from caddyserver.com](https://caddyserver.com/download) or from your Linux/BSD/Illumos/Haiku/TempleOS/whatever distribution's package manager.
|
||||
|
||||
1. Create a `Caddyfile` in your server directory:
|
||||
```
|
||||
chat.yourdomain.com {
|
||||
reverse_proxy localhost:7070
|
||||
}
|
||||
```
|
||||
|
||||
2. Start Caddy:
|
||||
```bash
|
||||
caddy run
|
||||
```
|
||||
|
||||
Caddy will automatically obtain and manage SSL certificates for your domain.
|
||||
|
||||
## Client Deployment
|
||||
|
||||
Place your client files in the `src/main/resources/public` directory. The server will automatically serve these static files, making the client accessible at your server's root URL.
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Database Files
|
||||
- `userDatabase`: Contains user credentials in format `username:token:salt`
|
||||
- `chatHistory`: Stores message history
|
||||
- Regular backups recommended
|
||||
|
||||
### Health Checks
|
||||
- Server automatically maintains WebSocket connections
|
||||
- Dead sessions are cleaned up automatically
|
||||
- Active user count is logged
|
||||
|
||||
### Security Recommendations
|
||||
- Keep database files secure
|
||||
- Regular system updates
|
||||
- Monitor for unusual login patterns
|
||||
- Back up regularly:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
backup_dir="/path/to/backups"
|
||||
date_stamp=$(date +%Y%m%d)
|
||||
cp userDatabase "${backup_dir}/userDatabase_${date_stamp}"
|
||||
cp chatHistory "${backup_dir}/chatHistory_${date_stamp}"
|
||||
```
|
||||
|
@@ -5,11 +5,11 @@ plugins {
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("xyz.maxwellj.chookpen.MainKt")
|
||||
mainClass.set("xyz.maxwellj.chookchat.MainKt")
|
||||
layout.buildDirectory.dir("distributions/")
|
||||
}
|
||||
|
||||
group = "xyz.maxwellj.chookpen"
|
||||
group = "xyz.maxwellj.chookchat"
|
||||
version = "0.0.1"
|
||||
|
||||
repositories {
|
||||
@@ -18,14 +18,16 @@ repositories {
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes["Main-Class"] = "xyz.maxwellj.chookpen.MainKt"
|
||||
attributes["Main-Class"] = "xyz.maxwellj.chookchat.MainKt"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||
implementation("io.javalin:javalin:6.3.0")
|
||||
implementation("org.slf4j:slf4j-simple:2.0.16")
|
||||
implementation("org.json:json:20230618")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
|
@@ -1,106 +1,182 @@
|
||||
package xyz.maxwellj.chookpen
|
||||
package xyz.maxwellj.chookchat
|
||||
|
||||
import io.javalin.Javalin
|
||||
import io.javalin.websocket.WsContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.UUID
|
||||
|
||||
import kotlin.concurrent.fixedRateTimer
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
|
||||
import java.io.File
|
||||
import java.io.BufferedReader
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.Files
|
||||
|
||||
fun md5(input:String): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
/*
|
||||
object WsSessionManager {
|
||||
val sessions = ConcurrentHashMap<String, WsContext>()
|
||||
fun addSession(sessionID: String, ctx: WsContext) {
|
||||
sessions[sessionID] = ctx
|
||||
}
|
||||
fun removeSession(sessionID: String) {
|
||||
sessions.remove(sessionID)
|
||||
}
|
||||
fun broadcast(message: String) {
|
||||
sessions.values.forEach { ctx ->
|
||||
ctx.send(message)
|
||||
}
|
||||
fun removeLines(fileName: String, lineNumber: String) {
|
||||
require(!fileName.isEmpty() && startLine >= 1 && numLines >= 1)
|
||||
val f = File(fileName)
|
||||
var lines = f.readLines()
|
||||
if (startLine > size) {
|
||||
println("The starting line is beyond the length of the file")
|
||||
return
|
||||
}
|
||||
lines = lines.take(startLine - 1) + lines.drop(startLine + n - 1)
|
||||
val text = lines.joinToString(System.lineSeparator())
|
||||
f.writeText(text)
|
||||
}
|
||||
*/
|
||||
object WsSessionManager {
|
||||
private val sessions = ConcurrentHashMap<String, WsContext>()
|
||||
val peopleOnline = mutableListOf("")
|
||||
val sessionsList = mutableListOf("")
|
||||
val sessions = ConcurrentHashMap<WsContext, String>()
|
||||
val sessionIds = ConcurrentHashMap<String, WsContext>()
|
||||
val userSessions = ConcurrentHashMap<String, String>()
|
||||
|
||||
init {
|
||||
fixedRateTimer("websocket-ping", period = 5000) {
|
||||
sendPing()
|
||||
}
|
||||
}
|
||||
private fun sendPing() {
|
||||
val deadSessions = mutableListOf<WsContext>()
|
||||
|
||||
sessions.keys.forEach { ctx ->
|
||||
try {
|
||||
if (ctx.session.isOpen) {
|
||||
ctx.send("ping")
|
||||
} else {
|
||||
deadSessions.add(ctx)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error sending ping: ${e.message}")
|
||||
deadSessions.add(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any dead sessions
|
||||
deadSessions.forEach { removeSession(it) }
|
||||
}
|
||||
|
||||
fun broadcastOnlineUsers() {
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "users")
|
||||
put("username", "system")
|
||||
put("content", peopleOnline.joinToString(", "))
|
||||
}
|
||||
broadcast(processedData.toString())
|
||||
}
|
||||
|
||||
fun handleUserLogin(username: String) {
|
||||
if (!peopleOnline.contains(username)) {
|
||||
peopleOnline.add(username)
|
||||
broadcastOnlineUsers()
|
||||
}
|
||||
}
|
||||
|
||||
fun addSession(ctx: WsContext) {
|
||||
// Generate our own UUID for the session since we can't access Javalin's private sessionId
|
||||
try {
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
sessions[sessionId] = ctx
|
||||
sessionsList.add(sessionId) // Changed from += to add()
|
||||
sessions[ctx] = sessionId
|
||||
sessionIds[sessionId] = ctx
|
||||
} catch (e: Exception) {
|
||||
println("Error adding session: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun removeSession(ctx: WsContext) {
|
||||
// Find and remove the session by context
|
||||
sessions.entries.removeIf { it.value === ctx }
|
||||
try {
|
||||
val sessionId = sessions[ctx]
|
||||
if (sessionId != null) {
|
||||
// Find and remove the username associated with this session
|
||||
userSessions.entries.find { it.value == sessionId }?.let { entry ->
|
||||
peopleOnline.remove(entry.key)
|
||||
userSessions.remove(entry.key)
|
||||
}
|
||||
|
||||
sessionsList.remove(sessionId)
|
||||
sessions.remove(ctx)
|
||||
sessionIds.remove(sessionId)
|
||||
broadcastOnlineUsers()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error removing session: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun associateUserWithSession(username: String, ctx: WsContext) {
|
||||
val sessionId = sessions[ctx]
|
||||
if (sessionId != null) {
|
||||
userSessions[username] = sessionId
|
||||
}
|
||||
}
|
||||
|
||||
fun broadcast(message: String) {
|
||||
sessions.values.forEach { ctx ->
|
||||
val deadSessions = mutableListOf<WsContext>()
|
||||
|
||||
sessions.keys.forEach { ctx ->
|
||||
try {
|
||||
if (ctx.session.isOpen) {
|
||||
ctx.send(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun extractMessageContent(inputData: String): String {
|
||||
var username = ""
|
||||
var message = ""
|
||||
var dataType = ""
|
||||
var isParsingData = 0
|
||||
|
||||
for (char in inputData) {
|
||||
if (char == ':') {
|
||||
isParsingData = 1
|
||||
} else if (isParsingData == 1) {
|
||||
if (char == '}') {
|
||||
isParsingData = 0
|
||||
dataType = ""
|
||||
} else if (char != '{') {
|
||||
if (dataType == "username") {
|
||||
username += char
|
||||
} else if (dataType == "message") {
|
||||
message += char
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataType += char
|
||||
deadSessions.add(ctx)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error broadcasting to session: ${e.message}")
|
||||
deadSessions.add(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return("$username: $message")
|
||||
// Clean up any dead sessions
|
||||
deadSessions.forEach { removeSession(it) }
|
||||
}
|
||||
|
||||
fun getSessionCount(): Int = sessions.size
|
||||
}
|
||||
|
||||
fun extractMessageContent(inputData: String, ctx: WsContext): String {
|
||||
val jsonInputData = JSONObject(inputData)
|
||||
if (jsonInputData.getString("type") == "connect") {
|
||||
val username = jsonInputData.getString("username")
|
||||
WsSessionManager.associateUserWithSession(username, ctx)
|
||||
WsSessionManager.handleUserLogin(username)
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "connect")
|
||||
put("username", "system")
|
||||
put("content", "${jsonInputData.getString("username")} just joined the room!")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", jsonInputData.getString("type"))
|
||||
put("username", jsonInputData.getString("username"))
|
||||
put("content", jsonInputData.getString("content"))
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
|
||||
fun handleSentMessage(inputData: String): String {
|
||||
println("API request recieved: $inputData")
|
||||
// Parse data sent to the server by client
|
||||
var username = ""
|
||||
var token = ""
|
||||
var message = ""
|
||||
var dataType = ""
|
||||
var isParsingData = 0
|
||||
for (char in inputData) {
|
||||
val character = char
|
||||
if (character == ':') {
|
||||
isParsingData = 1
|
||||
} else if (isParsingData == 1) {
|
||||
if (character == '}') {
|
||||
isParsingData = 0
|
||||
dataType = ""
|
||||
} else if (character != '{') {
|
||||
if (dataType == "username") {
|
||||
username += character
|
||||
} else if (dataType == "token") {
|
||||
token += character
|
||||
} else if (dataType == "message") {
|
||||
message += character
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataType += character
|
||||
}
|
||||
}
|
||||
var jsonInputData: JSONObject
|
||||
try {jsonInputData = JSONObject(inputData)} catch (error: JSONException){return(error.toString())}
|
||||
|
||||
val username = jsonInputData.getString("username")
|
||||
val token = jsonInputData.getString("token")
|
||||
val content = jsonInputData.getString("content")
|
||||
|
||||
val userDatabaseParser = BufferedReader(File("userDatabase").reader())
|
||||
var lineNumber = 1
|
||||
var userLine = ""
|
||||
@@ -115,11 +191,18 @@ fun handleSentMessage(inputData: String): String {
|
||||
userDatabaseParser.close()
|
||||
|
||||
if (userLine == "") {
|
||||
return("That account does not exist on this server.")
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "error")
|
||||
put("username", "system")
|
||||
put("content", "unknown-account")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
|
||||
var usernameInDatabase = ""
|
||||
var tokenInDatabase = ""
|
||||
var saltInDatabase = ""
|
||||
var banStatus = ""
|
||||
var currentStage = 0
|
||||
for (char in userLine) {
|
||||
if (char == ':') {
|
||||
@@ -129,90 +212,48 @@ fun handleSentMessage(inputData: String): String {
|
||||
usernameInDatabase += char
|
||||
} else if (currentStage == 1) {
|
||||
tokenInDatabase += char
|
||||
} else if (currentStage == 2) {
|
||||
saltInDatabase += char
|
||||
} else if (currentStage == 3) {
|
||||
banStatus += char
|
||||
}
|
||||
}
|
||||
tokenInDatabase = tokenInDatabase.replace(":", "")
|
||||
saltInDatabase = saltInDatabase.replace(":", "")
|
||||
banStatus = banStatus.replace(":", "")
|
||||
if (banStatus == "1") {
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "error")
|
||||
put("username", "system")
|
||||
put("content", "banned")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
val tokenWithSalt = (md5(token + saltInDatabase))
|
||||
/*println(saltInDatabase)
|
||||
println(tokenWithSalt)
|
||||
if (tokenWithSalt != tokenInDatabase) {*/
|
||||
if (token != tokenInDatabase) {
|
||||
return("Invalid token! Please try putting in your password right")
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "error")
|
||||
put("username", "system")
|
||||
put("content", "invalid-token")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
// Make the message to respond to the client
|
||||
val chatHistoryView = File("chatHistory")
|
||||
var fullMessage = ""
|
||||
if (message != "") {
|
||||
fullMessage = "${chatHistoryView.readText()}$username: $message"
|
||||
if (content != "") {
|
||||
fullMessage = "${chatHistoryView.readText()}$username: $content"
|
||||
// Add the client's message to the chat history
|
||||
val chatHistory = File("chatHistory")
|
||||
chatHistory.appendText("$username: $message ${System.lineSeparator()}")
|
||||
message = ""
|
||||
chatHistory.appendText("$username: $content ${System.lineSeparator()}")
|
||||
return("Success")
|
||||
} else {
|
||||
return("No data provided")
|
||||
}
|
||||
}
|
||||
|
||||
fun syncMessages(inputData: String): String {
|
||||
println("API request recieved: $inputData")
|
||||
// Parse data sent to the server by client
|
||||
var username = ""
|
||||
var token = ""
|
||||
var dataType = ""
|
||||
var isParsingData = 0
|
||||
for (char in inputData) {
|
||||
val character = char
|
||||
if (character == ':') {
|
||||
isParsingData = 1
|
||||
} else if (isParsingData == 1) {
|
||||
if (character == '}') {
|
||||
isParsingData = 0
|
||||
dataType = ""
|
||||
} else if (character != '{') {
|
||||
if (dataType == "username") {
|
||||
username += character
|
||||
} else if (dataType == "token") {
|
||||
token += character
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataType += character
|
||||
}
|
||||
}
|
||||
val userDatabaseParser = BufferedReader(File("userDatabase").reader())
|
||||
var lineNumber = 1
|
||||
var userLine = ""
|
||||
|
||||
// Search the user database to find required information about the user
|
||||
userDatabaseParser.forEachLine { line ->
|
||||
if (line.contains(username)) {
|
||||
userLine = line
|
||||
}
|
||||
lineNumber++
|
||||
}
|
||||
userDatabaseParser.close()
|
||||
|
||||
if (userLine == "") {
|
||||
return("Account not found")
|
||||
}
|
||||
|
||||
var usernameInDatabase = ""
|
||||
var tokenInDatabase = ""
|
||||
var currentStage = 0
|
||||
for (char in userLine) {
|
||||
if (char == ':') {
|
||||
currentStage ++
|
||||
}
|
||||
if (currentStage == 0) {
|
||||
usernameInDatabase += char
|
||||
} else if (currentStage == 1) {
|
||||
tokenInDatabase += char
|
||||
}
|
||||
}
|
||||
tokenInDatabase = tokenInDatabase.replace(":", "")
|
||||
if (token != tokenInDatabase) {
|
||||
return("Invalid token")
|
||||
}
|
||||
// Send back message history
|
||||
val chatHistoryView = File("chatHistory")
|
||||
return(chatHistoryView.readText())
|
||||
return("Chookchat")
|
||||
}
|
||||
|
||||
fun createAccount(inputData: String): String {
|
||||
@@ -252,7 +293,12 @@ fun createAccount(inputData: String): String {
|
||||
var response = ""
|
||||
userDatabaseParser.forEachLine { line ->
|
||||
if (line.contains(username)) {
|
||||
response = "Username already exists"
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "error")
|
||||
put("username", "system")
|
||||
put("content", "username-taken")
|
||||
}
|
||||
response = processedData.toString()
|
||||
}
|
||||
lineNumber++
|
||||
}
|
||||
@@ -261,175 +307,89 @@ fun createAccount(inputData: String): String {
|
||||
}
|
||||
userDatabaseParser.close()
|
||||
if (username == "") {
|
||||
return("No username")
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "error")
|
||||
put("username", "system")
|
||||
put("content", "no-username")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
|
||||
if (token == "") {
|
||||
return("No token")
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "error")
|
||||
put("username", "system")
|
||||
put("content", "no-token")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
|
||||
val userDatabaseFile = File("userDatabase")
|
||||
userDatabaseFile.appendText("${System.lineSeparator()}$username:$token")
|
||||
return("Success")
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "success")
|
||||
put("username", "system")
|
||||
put("content", "success")
|
||||
}
|
||||
fun authKey(inputData: String): String {
|
||||
println("API request recieved: $inputData")
|
||||
|
||||
// Parse data sent to the server by client
|
||||
var username = ""
|
||||
var token = ""
|
||||
var authKey = ""
|
||||
var dataType = ""
|
||||
var isParsingData = 0
|
||||
for (char in inputData) {
|
||||
val character = char
|
||||
if (character == ':') {
|
||||
isParsingData = 1
|
||||
} else if (isParsingData == 1) {
|
||||
if (character == '}') {
|
||||
isParsingData = 0
|
||||
dataType = ""
|
||||
} else if (character != '{') {
|
||||
if (dataType == "username") {
|
||||
username += character
|
||||
} else if (dataType == "token") {
|
||||
token += character
|
||||
} else if (dataType == "authkey") {
|
||||
authKey += character
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataType += character
|
||||
}
|
||||
}
|
||||
val userDatabaseParser = BufferedReader(File("userDatabase").reader())
|
||||
var lineNumber = 1
|
||||
var userLine = ""
|
||||
|
||||
// Search the user database to find required information about the user
|
||||
userDatabaseParser.forEachLine { line ->
|
||||
if (line.contains(username)) {
|
||||
userLine = line
|
||||
}
|
||||
lineNumber++
|
||||
}
|
||||
userDatabaseParser.close()
|
||||
|
||||
if (userLine == "") {
|
||||
return("Account not found")
|
||||
return(processedData.toString())
|
||||
}
|
||||
|
||||
var usernameInDatabase = ""
|
||||
var tokenInDatabase = ""
|
||||
fun handleServerCommand(command: String): String {
|
||||
val commandArgs = mutableListOf("")
|
||||
commandArgs.drop(1)
|
||||
var currentStage = 0
|
||||
for (char in userLine) {
|
||||
if (char == ':') {
|
||||
currentStage ++
|
||||
}
|
||||
if (currentStage == 0) {
|
||||
usernameInDatabase += char
|
||||
} else if (currentStage == 1) {
|
||||
tokenInDatabase += char
|
||||
}
|
||||
}
|
||||
tokenInDatabase = tokenInDatabase.replace(":", "")
|
||||
if (token != tokenInDatabase) {
|
||||
return("Invalid token")
|
||||
}
|
||||
if (authKey == "") {
|
||||
return("No auth key provided")
|
||||
}
|
||||
// Make the message to respond to the client
|
||||
val chatHistoryView = File("chatHistory")
|
||||
var fullMessage = ""
|
||||
if (authKey != "") {
|
||||
fullMessage = "encryptionKey:$username:$authKey"
|
||||
authKey = ""
|
||||
} else {
|
||||
fullMessage = "${chatHistoryView.readText()}"
|
||||
}
|
||||
val response = if (inputData.isNotEmpty()) {
|
||||
fullMessage
|
||||
} else {
|
||||
"No data provided"
|
||||
}
|
||||
|
||||
// Send the message to the client
|
||||
return("Success")
|
||||
for (char in command) {
|
||||
if (char == ' ') {
|
||||
currentStage ++
|
||||
commandArgs += ""
|
||||
} else {
|
||||
commandArgs[currentStage] += char
|
||||
}
|
||||
}
|
||||
return("I'm not sure how to ${commandArgs.toString()}")
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val app = Javalin.create()
|
||||
.get("/") { ctx -> ctx.result("dingus") }
|
||||
.get("/api/send/{content}") { ctx ->
|
||||
val result = handleSentMessage(ctx.pathParam("content"))
|
||||
if (result == "Success") {
|
||||
val messageContent = extractMessageContent(ctx.pathParam("content"))
|
||||
WsSessionManager.broadcast(messageContent)
|
||||
}
|
||||
ctx.result(result)
|
||||
WsSessionManager.peopleOnline.removeAt(0)
|
||||
WsSessionManager.sessionsList.removeAt(0)
|
||||
val app = Javalin.create { config ->
|
||||
config.staticFiles.add("/public")
|
||||
}.get("/") { ctx ->
|
||||
ctx.redirect("/index.html")
|
||||
}
|
||||
.get("/api/createaccount/{content}") { ctx -> ctx.result(createAccount(ctx.pathParam("content")))}
|
||||
.get("/api/syncmessages/{content}") { ctx -> ctx.result(syncMessages(ctx.pathParam("content")))}
|
||||
.get("/api/authkey/{content}") { ctx -> ctx.result(authKey(ctx.pathParam("content")))}
|
||||
.ws("/api/websocket") { ws ->
|
||||
ws.onConnect { ctx ->
|
||||
WsSessionManager.addSession(ctx)
|
||||
ctx.send("Websocket success")
|
||||
}
|
||||
ws.onClose { ctx ->
|
||||
WsSessionManager.removeSession(ctx)
|
||||
}
|
||||
ws.onMessage { ctx ->
|
||||
when (ctx.message()) {
|
||||
"pong" -> {}
|
||||
else -> {
|
||||
println(ctx.message())
|
||||
val successState = handleSentMessage(ctx.message())
|
||||
if (successState != "Success") {
|
||||
try {
|
||||
ctx.send(successState)
|
||||
} catch (e: Exception) {
|
||||
println("Error sending error message: ${e.message}")
|
||||
}
|
||||
} else {
|
||||
// Broadcast the message to all clients if successful
|
||||
val messageContent = extractMessageContent(ctx.message())
|
||||
val messageContent = extractMessageContent(ctx.message(), ctx)
|
||||
WsSessionManager.broadcast(messageContent)
|
||||
ctx.send("Message sent successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.start(7070)
|
||||
println("Type a command for the server")
|
||||
while (1 == 1) {
|
||||
println(handleServerCommand(readln()))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fun main(args: Array<String>) {
|
||||
val app = Javalin.create()
|
||||
.get("/") { ctx -> ctx.result("dingus") }
|
||||
.get("/api/send/{content}") { ctx ->
|
||||
val result = handleSentMessage(ctx.pathParam("content"))
|
||||
if (result == "Success") {
|
||||
val messageContent = extractMessageContent(ctx.pathParam("content")
|
||||
WsSessionManager.broadcast(messageContent)
|
||||
ctx.result(result)
|
||||
}
|
||||
}
|
||||
.get("/api/createaccount/{content}") { ctx -> ctx.result(createAccount(ctx.pathParam("content")))}
|
||||
.get("/api/syncmessages/{content}") { ctx -> ctx.result(syncMessages(ctx.pathParam("content")))}
|
||||
.get("/api/authkey/{content}") { ctx -> ctx.result(authKey(ctx.pathParam("content")))}
|
||||
.ws("/api/websocket") { ws ->
|
||||
ws.onConnect { ctx ->
|
||||
WsSessionManager.addSession(ctx.sessionId, ctx)
|
||||
ctx.send("Websocket success")
|
||||
}
|
||||
ws.onClose { ctx ->
|
||||
WsSessionManager.removeSession(ctx.sessionId)
|
||||
}
|
||||
ws.onMessage { ctx ->
|
||||
println(ctx.message())
|
||||
val successState = handleSentMessage(ctx.message())
|
||||
if (successState != "Success") {
|
||||
ctx.send(successState)
|
||||
} else {
|
||||
ctx.send("Message sent successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
.start(7070)
|
||||
}*/
|
||||
|
||||
|
||||
|
1
server/src/main/resources/public/InterVariable.ttf
Symbolic link
1
server/src/main/resources/public/InterVariable.ttf
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../client-web/InterVariable.ttf
|
1
server/src/main/resources/public/gradient.css
Symbolic link
1
server/src/main/resources/public/gradient.css
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../client-web/gradient.css
|
1
server/src/main/resources/public/index.css
Symbolic link
1
server/src/main/resources/public/index.css
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../client-web/index.css
|
1
server/src/main/resources/public/index.html
Symbolic link
1
server/src/main/resources/public/index.html
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../client-web/index.html
|
1
server/src/main/resources/public/index.js
Symbolic link
1
server/src/main/resources/public/index.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../client-web/index.js
|
Reference in New Issue
Block a user