Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e463d3dd2b | ||
|
7ff5447388 | ||
|
23fc71bd45 | ||
|
90cf9f4325 | ||
|
857a05b9ca | ||
|
c07a2c7fce | ||
|
500ac029df | ||
|
83fc6052ad | ||
|
dae4bb380f | ||
|
78b885b22e | ||
|
f69a3fb599 | ||
|
d839652916 | ||
|
b1b1c0accc | ||
|
61c60f7906 | ||
|
a4b6721ebc | ||
|
41c9e7f53f | ||
378cb7c867 | |||
|
e06f3891c8 | ||
|
97a26f7615 | ||
|
18be6c6fd4 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
.swp
|
||||
server/build
|
||||
server/.gradle
|
||||
server/uploads
|
||||
server/userDatabase
|
||||
server/chatHistory
|
||||
client-cli/build
|
||||
|
|
98
README.md
98
README.md
|
@ -1,97 +1,31 @@
|
|||
# Chookpen - A simple messaging service
|
||||
# Chookchat - A simple messaging service
|
||||
|
||||
## What is Chookpen?
|
||||
## What is Chookchat?
|
||||
|
||||
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.
|
||||
Chookchat is a lightweight, secure-ish chat server implementation focused on simplicity and real-time communication. It features user authentication, persistent message history and real-time updates via WebSockets. 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?
|
||||
### [OpenJDK](https://openjdk.org)
|
||||
|
||||
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.
|
||||
The best way to run Chookpen. Free and open source!
|
||||
|
||||
### 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.
|
||||
There's also all the other various libraries I use (and these other libraries use) so check the code for those!
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# Chookpen Client (CLI)
|
||||
|
||||
This is the reference client for Chookpen. It is quite a basic client. This is a good starting point if you need an example, or are building a GUI Kotlin client.
|
||||
|
||||
## Features
|
||||
|
||||
* Profile file
|
||||
* Create account (buggy)
|
||||
* Log into existing account
|
||||
* Sync messages
|
||||
* Send message
|
||||
* Auto hash password
|
||||
* Configure whether HTTPS is used
|
||||
|
||||
## Documentation
|
||||
|
||||
To run this client, do the following:
|
||||
|
||||
1. Install Java 17+ and Gradle.
|
||||
2. `git clone https://git.maxwellj.xyz/max/chookpen`
|
||||
3. In the client-cli directory of the git repo, run `gradle installDist`.
|
||||
5. Inside build/install/bin, create a .chookpen.profile file that looks like this: ```name:password:server:port:0```
|
||||
6. Run the script in build/install/bin, or the bat file if you're a crazy weird guy who uses Windows (Make sure to use the command line otherwise it'll close instantly)
|
||||
7. If you'd like to send a message, add that as your argument after specifying the script.
|
||||
|
||||
Note: The password in .chookpen.profile is hashed by the client. If you're just testing the server, set your password to `dingus`. The hash recognised by the server for that password is `750c63441033127dccaa91c16b21614e`. Create an account with the method specified in the root README using that password.
|
|
@ -1,35 +0,0 @@
|
|||
plugins {
|
||||
kotlin("jvm") version "2.0.0"
|
||||
application
|
||||
distribution
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("xyz.maxwellj.chookpen.client.MainKt")
|
||||
layout.buildDirectory.dir("distributions/")
|
||||
}
|
||||
|
||||
group = "xyz.maxwellj.chookpen.client"
|
||||
version = "0.0.2"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes["Main-Class"] = "xyz.maxwellj.chookpen.client.MainKt"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
rootProject.name = "chookpen.client"
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
package xyz.maxwellj.chookpen.client
|
||||
|
||||
import okhttp3.*
|
||||
import java.util.Scanner
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
import java.io.File
|
||||
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
|
||||
fun md5(input:String): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// Variables
|
||||
var name = ""
|
||||
var server = ""
|
||||
var port = ""
|
||||
var hasHTTPS = ""
|
||||
var password = ""
|
||||
var configFile = File("${System.getProperty("user.home")}/chookpen.profile")
|
||||
|
||||
var configStage = 0
|
||||
for (char in configFile.readText()) {
|
||||
if (char == ':') {configStage ++}
|
||||
if (configStage == 0) {
|
||||
name += char
|
||||
} else if (configStage == 1) {
|
||||
password += char
|
||||
} else if (configStage == 2) {
|
||||
server += char
|
||||
} else if (configStage == 3) {
|
||||
port += char
|
||||
} else if (configStage == 4) {
|
||||
hasHTTPS += char
|
||||
}
|
||||
}
|
||||
|
||||
server = server.replace(":", "")
|
||||
port = port.replace(":", "")
|
||||
hasHTTPS = hasHTTPS.replace(":", "")
|
||||
password = password.replace(":", "")
|
||||
|
||||
if (password == "x") {
|
||||
println("Enter your password:")
|
||||
password = readln()
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
//.pingInterval(30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("ws://localhost:7070/api/websocket")
|
||||
.build()
|
||||
|
||||
var webSocket: WebSocket? = null
|
||||
|
||||
val listener = object : WebSocketListener() {
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
println(password)
|
||||
println(md5(password))
|
||||
println("Connection opened")
|
||||
webSocket.send("username:{$name}token:{${md5(password)}}message:{Joined the room}")
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
println("$text")
|
||||
}
|
||||
|
||||
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||
println("Connection closing: $reason")
|
||||
webSocket.close(1000, null)
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
println("Connection failed: ${t.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// Set up shutdown hook for Ctrl+C handling
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
println("\nShutting down gracefully...")
|
||||
webSocket?.close(1000, "Client shutting down")
|
||||
client.dispatcher.executorService.shutdown()
|
||||
})
|
||||
|
||||
// Initialize WebSocket connection
|
||||
webSocket = client.newWebSocket(request, listener)
|
||||
|
||||
// Set up input handling
|
||||
val scanner = Scanner(System.`in`)
|
||||
println("Type your messages (press Enter to send, Ctrl+C to quit):")
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
val input = scanner.nextLine()
|
||||
if (input.isNotEmpty()) {
|
||||
webSocket?.send("username:{$name}token:{${md5(password)}}message:{$input}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Handle any input-related exceptions
|
||||
println("Error reading input: ${e.message}")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
BIN
client-web/InterVariable.ttf
Normal file
BIN
client-web/InterVariable.ttf
Normal file
Binary file not shown.
21
client-web/gradient.css
Normal file
21
client-web/gradient.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* Credit to Manuel Pinto on Codepen for the code!
|
||||
https://codepen.io/P1N2O/pen/pyBNzX */
|
||||
body {
|
||||
background: linear-gradient(-33deg, #721c7a, #612bd4, #3aa5c5, #30cf7f);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 120s ease infinite;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
352
client-web/index.css
Normal file
352
client-web/index.css
Normal file
|
@ -0,0 +1,352 @@
|
|||
@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;
|
||||
}
|
||||
|
||||
.bluebutton {
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
}
|
||||
.greenbutton {
|
||||
background: rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
.redbutton {
|
||||
background: rgba(255, 0, 0, 0.3);
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.box p {
|
||||
margin: 10px;
|
||||
}
|
||||
.box h3 {
|
||||
margin: 10px;
|
||||
}
|
||||
.box img {
|
||||
margin: 10px;
|
||||
}
|
||||
.box button {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#messaging .box {
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#messagebox {
|
||||
overflow-y: auto;
|
||||
border: 0px;
|
||||
padding: 20px;
|
||||
margin: 10px 0; /* Remove horizontal margin */
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message {
|
||||
text-align: left;
|
||||
margin: 5px 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.file {
|
||||
float: left;
|
||||
border-radius: 10px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 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;
|
||||
}
|
||||
|
||||
#messageInput {
|
||||
max-width: none;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bluebutton, .greenbutton, .redbutton {
|
||||
max-width: 200px;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
font-size: 12pt;
|
||||
font-family: inter;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
max-width: none;
|
||||
width: auto;
|
||||
margin: 5px 0;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
.bluebutton, .greenbutton, .redbutton:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
#users {
|
||||
display: flex;
|
||||
align-items: left;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#meeting {
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
margin-right: 10px;
|
||||
align-items: right;
|
||||
}
|
||||
|
||||
.suttle {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 0.9em;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
#users {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#meeting {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.message.call-notification {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
#meet {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
}
|
||||
.eggs-panel {
|
||||
position: absolute;
|
||||
right: -300px;
|
||||
top: 0;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 10px;
|
||||
transition: right 0.3s ease;
|
||||
}
|
||||
|
||||
.eggs-panel.visible {
|
||||
right: -310px;
|
||||
}
|
||||
|
||||
.eggs-list {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.egg-item {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-family: "inter";
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.egg-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.egg-content {
|
||||
padding: 10px;
|
||||
height: calc(100% - 20px);
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
margin: 10px;
|
||||
}
|
||||
#messaging {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.messaging-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.box {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.eggs-panel {
|
||||
width: 300px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
transform: translateX(100%);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.eggs-panel.visible {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.eggs-list {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.egg-item {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-family: "inter";
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.egg-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.egg-content {
|
||||
padding: 10px;
|
||||
height: calc(100% - 20px);
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
61
client-web/index.html
Normal file
61
client-web/index.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chookchat</title>
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<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="messaging-container">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<div id="eggs" style="display: none;">
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
423
client-web/index.js
Normal file
423
client-web/index.js
Normal file
|
@ -0,0 +1,423 @@
|
|||
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!")
|
||||
|
||||
let ws;
|
||||
let username;
|
||||
let password;
|
||||
let typingTimeout;
|
||||
let typingPeople = new Array();
|
||||
let api;
|
||||
|
||||
function resizeMessaging() {
|
||||
const messagingDiv = document.getElementById('messaging');
|
||||
if (messagingDiv) {
|
||||
messagingDiv.style.width = `${window.innerWidth - 40}px`;
|
||||
messagingDiv.style.height = `${window.innerHeight - 40}px`;
|
||||
}
|
||||
}
|
||||
|
||||
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 getUploadUrl() {
|
||||
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/upload`;
|
||||
}
|
||||
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 = () => {
|
||||
if (typeof Notification !== "undefined") {
|
||||
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();
|
||||
}
|
||||
if (message.content == "banned") {
|
||||
alert("kiddo you're banned lol what did you do to get banned lmaooo");
|
||||
incorrectDetail = 1;
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (message.type == "typing" && message.content == "1") {
|
||||
if (username !== message.username && !typingPeople.includes(message.username)) {
|
||||
typingPeople.push(message.username);
|
||||
updatePeopleTyping();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (message.type == "typing" && message.content == "0") {
|
||||
if (username !== message.username && typingPeople.includes(message.username)) {
|
||||
const index = typingPeople.indexOf(message.username);
|
||||
typingPeople.splice(index, 1);
|
||||
updatePeopleTyping();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (message.type == "users" && message.username == "system") {
|
||||
usersDiv = document.getElementById("users");
|
||||
if (usersDiv) {
|
||||
usersDiv.textContent = `Online users: ${message.content}`
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (message.type == "file") {
|
||||
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;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (message.type == "call") {
|
||||
const messagesDiv = document.getElementById('messagebox');
|
||||
const callButton = document.createElement('div');
|
||||
if (callButton) {
|
||||
if (messagesDiv) {
|
||||
messagesDiv.appendChild(callButton)
|
||||
callButton.className = "message call-notification";
|
||||
callButton.textContent = `${message.username} started a Jitsi call! Click to join!`;
|
||||
callButton.addEventListener('click', () => {
|
||||
window.open(message.content, '_blank');
|
||||
});
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (message.type == "connect") {
|
||||
const messagesDiv = document.getElementById('messagebox');
|
||||
const messageElement = document.createElement('div');
|
||||
if (messageElement) {
|
||||
if (messagesDiv) {
|
||||
messagesDiv.appendChild(messageElement);
|
||||
messageElement.className = 'message';
|
||||
messageElement.textContent = message.content;
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
}
|
||||
if (document.hidden) {
|
||||
const notifiction = new Notification("Chookchat", {body: messageElement.textContent});
|
||||
}
|
||||
}
|
||||
else if (message.type == "message") {
|
||||
const messagesDiv = document.getElementById('messagebox');
|
||||
const messageElement = document.createElement('div');
|
||||
if (messageElement) {
|
||||
if (messagesDiv) {
|
||||
messagesDiv.appendChild(messageElement);
|
||||
messageElement.className = 'message';
|
||||
messageElement.textContent = `${message.username}: ${message.content}` ;
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
}
|
||||
if (document.hidden) {
|
||||
const notifiction = new Notification("Chookchat", {body: messageElement.textContent});
|
||||
}
|
||||
}
|
||||
// Egg message logic
|
||||
else {
|
||||
return
|
||||
}
|
||||
};
|
||||
}
|
||||
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 = '';
|
||||
|
||||
if (typingTimeout) {
|
||||
clearTimeout(typingTimeout);
|
||||
}
|
||||
const stoppedTypingMessage = {
|
||||
"type": "typing",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"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() {
|
||||
const fileUploadElement = document.getElementById("upload");
|
||||
if (fileUploadElement) {
|
||||
fileUploadElement.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
try {
|
||||
const response = await fetch(getUploadUrl(), {
|
||||
method: 'POST',
|
||||
mode: "no-cors",
|
||||
body: formData
|
||||
});
|
||||
if (response.ok) {
|
||||
const result = await response.text();
|
||||
const processedMessage = {
|
||||
"type": "message",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": `Sent a file`
|
||||
}
|
||||
if (processedMessage && ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(processedMessage));
|
||||
}
|
||||
} else {
|
||||
alert("Something went wrong lmao");
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
const fileUploadElement = document.getElementById("upload");
|
||||
if (fileUploadElement) {
|
||||
fileUploadElement.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
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)}}`);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function startTypingIndicator() {
|
||||
if (typingTimeout) {
|
||||
clearTimeout(typingTimeout);
|
||||
}
|
||||
|
||||
const typingMessage = {
|
||||
"type": "typing",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": "1"
|
||||
};
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(typingMessage));
|
||||
}
|
||||
|
||||
typingTimeout = setTimeout(() => {
|
||||
const stoppedTypingMessage = {
|
||||
"type": "typing",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": "0"
|
||||
};
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(stoppedTypingMessage));
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function updatePeopleTyping() {
|
||||
const typingDiv = document.getElementById('typing');
|
||||
if (typingDiv) {
|
||||
if (typingPeople.length === 0) {
|
||||
typingDiv.textContent = '';
|
||||
} else if (typingPeople.length === 1) {
|
||||
typingDiv.textContent = `${typingPeople[0]} is typing...`;
|
||||
} else if (typingPeople.length === 2) {
|
||||
typingDiv.textContent = `${typingPeople[0]} and ${typingPeople[1]} are typing...`;
|
||||
} else {
|
||||
typingDiv.textContent = `${typingPeople.length} people are typing...`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const characters ='abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
function generateString(length) {
|
||||
let result = '';
|
||||
const charactersLength = characters.length;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function startMeeting() {
|
||||
const link = `https://meet.jit.si/chookchat-${generateString(15)}`;
|
||||
alert("Note: You may need to sign in to Jitsi to start the meeting. We'll take you there in a moment...")
|
||||
window.open(link, '_blank');
|
||||
const processedMessage = {
|
||||
"type": "call",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": link
|
||||
};
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(processedMessage));
|
||||
} else {
|
||||
alert("Something went wrong. Refreshing might do the trick :)")
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('messageInput').addEventListener('input', startTypingIndicator);
|
||||
|
||||
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)';
|
||||
} else {
|
||||
mainBox.style.width = '100%';
|
||||
}
|
||||
}
|
||||
|
||||
function closeEggs() {
|
||||
const eggsPanel = document.getElementById('eggs');
|
||||
eggsPanel.style.display = "block";
|
||||
}
|
||||
// Eggs begin here
|
||||
|
||||
|
|
@ -1,5 +1,62 @@
|
|||
# Chookpen Server
|
||||
# Chookchat Server
|
||||
|
||||
This is the official Chookpen server implementation. It provides a backend and exposes an API.
|
||||
This is the code for Chookchat's server program. The server program:
|
||||
|
||||
* Starts a server on port 7070
|
||||
|
||||
* Hosts the web client files (index.html at the root)
|
||||
|
||||
* Adjusts index.html based on your config file (a backup index.html is still avaliable at localhost:7070/index.html)
|
||||
|
||||
* Handles user signups
|
||||
|
||||
* Opens a websocket allowing for flexible user input
|
||||
|
||||
* Handles file uploads
|
||||
|
||||
**Note:** Chookchat does not support HTTPS natively. We recommend the usage of a reverse proxy like Caddy (caddyserver.com) to enable HTTPS on your website, as well as a firewall to block HTTP connections.
|
||||
|
||||
## Server Configuration
|
||||
|
||||
Chookchat looks for a chookchat.config file in the directory where you start Chookchat. An example is provided in this directory. Your file must look like the following:
|
||||
```
|
||||
address:localhost;port:7070;security:false;serviceName:Chookchat;
|
||||
```
|
||||
|
||||
Address: Where your server is hosted (a domain or IP address usually)
|
||||
Port: Your server's port (default 7070, 443 if you route it through Caddy with HTTPS)
|
||||
Security: Whether or not to use WSS/HTTPS (either `true` or `false`)
|
||||
ServiceName: What your server's name comes up as.
|
||||
|
||||
## API Documentation
|
||||
|
||||
Chookchat uses websockets to send and recieve messages. Each websocket request (except pinging and ponging) contains JSON to show what kind of data we are sending, what user is sending it (and token while being sent to the server), and the actual message content. A chookchat websocket message to the server looks like this:
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"username": "max",
|
||||
"token": "(hash of password)",
|
||||
"content": "dongus"
|
||||
}
|
||||
```
|
||||
And a message from the server would look like this (it's the same without the token):
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"username": "max",
|
||||
"content": "dongus"
|
||||
}
|
||||
```
|
||||
The server will send a `ping` every five seconds (without JSON) to stop the connection from timing out. When you recieve this, send back a `pong`.
|
||||
|
||||
### Message Types
|
||||
|
||||
**message**: A message. Not much going on. It's recommended to display it like ${username}: ${content}
|
||||
|
||||
**call**: A link to a Jitsi call. Treat this how you'd like.
|
||||
|
||||
**file**: A file sent by a user. For now, it appears as if the `system` user has sent the file, so in a client send a message describing who's uploaded the file shortly after sending the file.
|
||||
|
||||
**connect**: A user joining the room. If this is not sent by the `system` user, ignore.
|
||||
|
||||
**users**: A list of users currently in the room, seperated by ", ". In order of who joined first. If this is not sent by the `system` user, ignore.
|
||||
|
|
|
@ -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
server/chookchat.config
Normal file
1
server/chookchat.config
Normal file
|
@ -0,0 +1 @@
|
|||
address:localhost;port:7070;security:false;serviceName:fnkjdsnfiewnifdwsocidw;
|
1
server/chookchat.eggs.config
Normal file
1
server/chookchat.eggs.config
Normal file
|
@ -0,0 +1 @@
|
|||
notepad
|
4
server/eggs/notepad/index.html
Normal file
4
server/eggs/notepad/index.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="egg" id="egg-notepad" style="display: none;">
|
||||
<button id="egg-notepad-close" class="redbutton" onclick="closeEggNotepad()">Close</button><br>
|
||||
<textarea id="egg-notepad-textarea" placeholder="Start typing..." style="height: 500px"></textarea>
|
||||
</div>
|
21
server/eggs/notepad/index.js
Normal file
21
server/eggs/notepad/index.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
function eggNotepad() {
|
||||
const eggsList = document.getElementById("eggs-list");
|
||||
eggsList.style.display = "none";
|
||||
const eggNotepad = document.getElementById("egg-notepad");
|
||||
eggNotepad.style.display = "block";
|
||||
const eggNotepadTextArea = document.getElementById("egg-notepad-textarea");
|
||||
eggNotepadTextArea.addEventListener('input', function(event) {
|
||||
const eggNotepadMessage = {
|
||||
"type": "egg-notepad",
|
||||
"username": username,
|
||||
"token": md5(password),
|
||||
"content": event.target.value
|
||||
};
|
||||
ws.send(JSON.stringify(eggNotepadMessage));
|
||||
}, false);
|
||||
}
|
||||
|
||||
function closeEggNotepad() {
|
||||
const eggNotepad = document.getElementById("egg-notepad");
|
||||
eggNotepad.style.display = "none";
|
||||
}
|
4
server/eggs/notepad/message.js
Normal file
4
server/eggs/notepad/message.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
else if (message.type == "egg-notepad") {
|
||||
const eggNotepadTextArea = document.getElementById("egg-notepad-textarea");
|
||||
eggNotepadTextArea.value = message.content
|
||||
}
|
1
server/resources/InterVariable.ttf
Symbolic link
1
server/resources/InterVariable.ttf
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../client-web/InterVariable.ttf
|
1
server/resources/gradient.css
Symbolic link
1
server/resources/gradient.css
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../client-web/gradient.css
|
1
server/resources/index.css
Symbolic link
1
server/resources/index.css
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../client-web/index.css
|
1
server/resources/index.html
Symbolic link
1
server/resources/index.html
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../client-web/index.html
|
1
server/resources/index.js
Symbolic link
1
server/resources/index.js
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../client-web/index.js
|
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
rootProject.name = "chookpen"
|
||||
rootProject.name = "chookchat"
|
||||
|
||||
|
|
|
@ -1,106 +1,216 @@
|
|||
package xyz.maxwellj.chookpen
|
||||
package xyz.maxwellj.chookchat
|
||||
|
||||
import io.javalin.Javalin
|
||||
import io.javalin.websocket.WsContext
|
||||
import io.javalin.http.UploadedFile
|
||||
|
||||
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
|
||||
/*
|
||||
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)
|
||||
|
||||
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 config {
|
||||
var address = ""
|
||||
var port = ""
|
||||
var security = ""
|
||||
var serviceName = ""
|
||||
|
||||
fun getConfig() {
|
||||
address = ""
|
||||
port = ""
|
||||
security = ""
|
||||
serviceName = ""
|
||||
val configFile = File("chookchat.config")
|
||||
try {
|
||||
val config = configFile.readLines()
|
||||
var type = ""
|
||||
var isEditing = 0
|
||||
for (line in config) {
|
||||
for (char in line) {
|
||||
if (char == ':') {
|
||||
isEditing = 1
|
||||
} else if (char == ';') {
|
||||
isEditing = 0
|
||||
type = ""
|
||||
} else {
|
||||
if (isEditing == 0) {
|
||||
type += char
|
||||
} else if (isEditing == 1)
|
||||
if (type == "address") {
|
||||
address += char
|
||||
} else if (type == "port") {
|
||||
port += char
|
||||
} else if (type == "security") {
|
||||
security += char
|
||||
} else if (type == "serviceName") {
|
||||
serviceName += char
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Something went wrong :/ Here's the error: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
sessions[sessionId] = ctx
|
||||
try {
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
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 ->
|
||||
ctx.send(message)
|
||||
val deadSessions = mutableListOf<WsContext>()
|
||||
|
||||
sessions.keys.forEach { ctx ->
|
||||
try {
|
||||
if (ctx.session.isOpen) {
|
||||
ctx.send(message)
|
||||
} else {
|
||||
deadSessions.add(ctx)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error broadcasting to session: ${e.message}")
|
||||
deadSessions.add(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any dead sessions
|
||||
deadSessions.forEach { removeSession(it) }
|
||||
}
|
||||
|
||||
fun getSessionCount(): Int = sessions.size
|
||||
}
|
||||
|
||||
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
|
||||
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())
|
||||
}
|
||||
|
||||
return("$username: $message")
|
||||
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 +225,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 +246,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(":", "")
|
||||
if (token != tokenInDatabase) {
|
||||
return("Invalid token! Please try putting in your password right")
|
||||
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) {
|
||||
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 +327,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 +341,202 @@ 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")
|
||||
}
|
||||
return(processedData.toString())
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
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 buildHTML(): String {
|
||||
try {
|
||||
config.getConfig()
|
||||
val htmlFile = File("resources/index.html")
|
||||
val html = htmlFile.readLines()
|
||||
var editedhtml = ""
|
||||
for (line in html) {
|
||||
if (line == """ <input type="text" id="serverUrl" value="bobcompass.online" placeholder="Server URL"><br>""") {
|
||||
editedhtml += """ <input type="text" id="serverUrl" value="${config.address}" placeholder="Server URL"><br>"""
|
||||
} else if (line == """ <input type="text" id="serverPort" value="443" placeholder="Server Port"><br>""") {
|
||||
editedhtml += """ <input type="text" id="serverPort" value="${config.port}" placeholder="Server Port"><br>"""
|
||||
} else if (line == """ <input type="checkbox" id="securityStatus" checked>""" && config.security == "false") {
|
||||
editedhtml += """ <input type="checkbox" id="securityStatus">"""
|
||||
} else if (line == """ <h3>Chookchat</h3>""") {
|
||||
editedhtml += """ <h3>${config.serviceName}</h3>"""
|
||||
} else if (line == """ <!-- Eggs Start Here -->""") {
|
||||
val eggsFile = File("chookchat.eggs.config")
|
||||
val eggs = eggsFile.readLines()
|
||||
for (line in eggs) {
|
||||
val eggHTMLFile = File("eggs/$line/index.html")
|
||||
if (eggHTMLFile.exists()) {
|
||||
val eggHTML = eggHTMLFile.readText()
|
||||
editedhtml += eggHTML
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editedhtml += line
|
||||
}
|
||||
}
|
||||
|
||||
return(editedhtml)
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
return("There was an error! If you're the server's admin, here are the details: $e")
|
||||
}
|
||||
return("dingus")
|
||||
}
|
||||
|
||||
fun buildJS(): String {
|
||||
try {
|
||||
val eggsFile = File("chookchat.eggs.config")
|
||||
val eggs = eggsFile.readLines()
|
||||
val jsFile = File("resources/index.js")
|
||||
val js = jsFile.readLines()
|
||||
var editedJS = ""
|
||||
for (line in js) {
|
||||
if (line == " // Egg message logic") {
|
||||
for (line in eggs) {
|
||||
val eggJSMessageFile = File("eggs/$line/message.js")
|
||||
if (eggJSMessageFile.exists()) {
|
||||
val eggJSMessage = eggJSMessageFile.readText()
|
||||
editedJS += eggJSMessage
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editedJS += "$line\n"
|
||||
}
|
||||
}
|
||||
//editedJS += js
|
||||
for (line in eggs) {
|
||||
val eggJSFile = File("eggs/$line/index.js")
|
||||
if (eggJSFile.exists()) {
|
||||
val eggJS = eggJSFile.readText()
|
||||
editedJS += eggJS
|
||||
}
|
||||
}
|
||||
return(editedJS)
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
return("console.log(`There was an error! If you're the server's admin, here are the details: $e`)")
|
||||
}
|
||||
return("dingus")
|
||||
}
|
||||
|
||||
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.html(buildHTML())
|
||||
//ctx.redirect("/index.html")
|
||||
}
|
||||
.get("/index.js") { ctx ->
|
||||
ctx.result(buildJS())
|
||||
}
|
||||
.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")))}
|
||||
.post("/api/upload") { ctx ->
|
||||
val uploadedFiles = ctx.uploadedFiles()
|
||||
if (uploadedFiles.isEmpty()) {
|
||||
ctx.status(400).result("No files uploaded")
|
||||
return@post
|
||||
}
|
||||
val uploadedFile = uploadedFiles[0]
|
||||
val originalFilename = uploadedFile.filename()
|
||||
val uuid = UUID.randomUUID().toString()
|
||||
val fileExtension = originalFilename.substringAfterLast(".", "")
|
||||
val baseFilename = originalFilename.substringBeforeLast(".")
|
||||
val newFilename = "${baseFilename}_${uuid}${if (fileExtension.isNotEmpty()) ".$fileExtension" else ""}"
|
||||
val filePath = Paths.get("uploads", newFilename)
|
||||
Files.copy(uploadedFile.content(), filePath)
|
||||
val processedData = JSONObject().apply {
|
||||
put("type", "fileStatus")
|
||||
put("username", "system")
|
||||
put("content", "success")
|
||||
}
|
||||
ctx.result(processedData.toString())
|
||||
val processedData2 = JSONObject().apply {
|
||||
put("type", "file")
|
||||
put("username", "system")
|
||||
put("content", "https://maxwellj.xyz/chookchat/uploads/$newFilename")
|
||||
}
|
||||
WsSessionManager.broadcast(processedData2.toString())
|
||||
}
|
||||
.ws("/api/websocket") { ws ->
|
||||
ws.onConnect { ctx ->
|
||||
WsSessionManager.addSession(ctx)
|
||||
ctx.send("Websocket success")
|
||||
}
|
||||
ws.onClose { ctx ->
|
||||
WsSessionManager.removeSession(ctx)
|
||||
}
|
||||
ws.onMessage { ctx ->
|
||||
println(ctx.message())
|
||||
val successState = handleSentMessage(ctx.message())
|
||||
if (successState != "Success") {
|
||||
ctx.send(successState)
|
||||
} else {
|
||||
// Broadcast the message to all clients if successful
|
||||
val messageContent = extractMessageContent(ctx.message())
|
||||
WsSessionManager.broadcast(messageContent)
|
||||
ctx.send("Message sent successfully")
|
||||
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 {
|
||||
val messageContent = extractMessageContent(ctx.message(), ctx)
|
||||
WsSessionManager.broadcast(messageContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.start(7070)
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
}*/
|
||||
|
||||
try {
|
||||
if (args[0] == "-i") {
|
||||
println("Type a command for the server")
|
||||
while (1 == 1) {
|
||||
println(handleServerCommand(readln()))
|
||||
}
|
||||
} else {
|
||||
println("Interactive mode disabled, add -i to enable")
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
println("Interactive mode disabled, add -i to enable")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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
|
Loading…
Reference in New Issue
Block a user