package xyz.maxwellj.chookpen import io.javalin.Javalin import io.javalin.websocket.WsContext import java.util.concurrent.ConcurrentHashMap import java.util.UUID import kotlin.concurrent.fixedRateTimer import java.io.File import java.io.BufferedReader 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') } object WsSessionManager { var peopleOnline = mutableListOf("") var sessionsList = mutableListOf("") private val sessions = ConcurrentHashMap() private val sessionIds = ConcurrentHashMap() init { fixedRateTimer("websocket-ping", period = 5000) { sendPing() } } private fun sendPing() { val deadSessions = mutableListOf() 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() { broadcast("!users:{${peopleOnline.joinToString(",")}}") } fun handleUserLogin(username: String) { peopleOnline += username broadcastOnlineUsers() } fun addSession(ctx: WsContext) { try { val sessionId = UUID.randomUUID().toString() sessionsList += sessionId sessions[ctx] = sessionId sessionIds[sessionId] = ctx } catch (e: Exception) { println("Error adding session: ${e.message}") } } fun removeSession(ctx: WsContext) { try { val sessionId = sessions[ctx] if (sessionId != null) { peopleOnline.removeAt(sessionsList.indexOf(sessionId)) sessionsList.removeAt(sessionsList.indexOf(sessionId)) sessions.remove(ctx) sessionIds.remove(sessionId) broadcastOnlineUsers() } } catch (e: Exception) { println("Error removing session: ${e.message}") } } fun broadcast(message: String) { val deadSessions = mutableListOf() 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 } } return("$username: $message") } 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 command = "" var commandArg = "" 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 if (dataType == "command") { command += character } else if (dataType == "commandArg") { commandArg += 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("That account does not exist on this server.") } var usernameInDatabase = "" var tokenInDatabase = "" var saltInDatabase = "" var currentStage = 0 for (char in userLine) { if (char == ':') { currentStage ++ } if (currentStage == 0) { usernameInDatabase += char } else if (currentStage == 1) { tokenInDatabase += char } else if (currentStage == 2) { saltInDatabase += char } } tokenInDatabase = tokenInDatabase.replace(":", "") saltInDatabase = saltInDatabase.replace(":", "") 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") } // Make the message to respond to the client val chatHistoryView = File("chatHistory") var fullMessage = "" if (message != "") { fullMessage = "${chatHistoryView.readText()}$username: $message" // Add the client's message to the chat history val chatHistory = File("chatHistory") chatHistory.appendText("$username: $message ${System.lineSeparator()}") message = "" return("Success") } else if (command != "") { if (command == "sync") { return(chatHistoryView.readText()) } else if (command == "login") { WsSessionManager.handleUserLogin(commandArg) return("Login successful") } } else { return("No data provided") } return("System: Welcome to Chookpen, $username!") } 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()) } fun createAccount(inputData: String): String { println("Account creation 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 } } val userDatabaseParser = BufferedReader(File("userDatabase").reader()) var lineNumber = 1 var userExists = 0 // Search the user database to find required information about the user var response = "" userDatabaseParser.forEachLine { line -> if (line.contains(username)) { response = "Username already exists" } lineNumber++ } if (response != "") { return(response) } userDatabaseParser.close() if (username == "") { return("No username") } if (token == "") { return("No token") } val userDatabaseFile = File("userDatabase") userDatabaseFile.appendText("${System.lineSeparator()}$username:$token") return("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") } 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") } 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") } fun main(args: Array) { WsSessionManager.peopleOnline.removeAt(0) WsSessionManager.sessionsList.removeAt(0) 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) } 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 { val messageContent = extractMessageContent(ctx.message()) WsSessionManager.broadcast(messageContent) } } } } } .start(7070) }