diff --git a/client-cli/build.gradle.kts b/client-cli/build.gradle.kts index 87b5362..2f84be0 100644 --- a/client-cli/build.gradle.kts +++ b/client-cli/build.gradle.kts @@ -24,7 +24,7 @@ tasks.withType { dependencies { testImplementation(kotlin("test")) - implementation("com.github.kittinunf.fuel:fuel:3.0.0-alpha03") + implementation("com.squareup.okhttp3:okhttp:4.12.0") } tasks.test { diff --git a/client-cli/src/main/kotlin/Main.kt b/client-cli/src/main/kotlin/Main.kt index 0387c12..03801f6 100644 --- a/client-cli/src/main/kotlin/Main.kt +++ b/client-cli/src/main/kotlin/Main.kt @@ -1,7 +1,9 @@ package xyz.maxwellj.chookpen.client -import fuel.Fuel -import fuel.get +import okhttp3.* +import java.util.Scanner +import kotlin.system.exitProcess + import java.io.File import kotlin.system.exitProcess @@ -14,7 +16,7 @@ fun md5(input:String): String { return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0') } -suspend fun main(args: Array) { +fun main() { // Variables var name = "" var server = "" @@ -23,50 +25,6 @@ suspend fun main(args: Array) { var password = "" var configFile = File("${System.getProperty("user.home")}/chookpen.profile") - if (!configFile.exists()) { - println("You don't have a Chookpen profile set up yet. If you've got a Chookpen profile, type 'l' to login and press enter. Otherwise, just press enter.") - val userInput = readln() - if (userInput == "l") { - println("Username:") - name = readln() - println("Password:") - password = readln() - val request = Fuel.get("http://localhost:7070/api/logintest/username:{$name}token:{${md5(password)}}").body.string() - if (request == "Invalid token") { - println("Invalid password. Please rerun the program and put in the right password") - exitProcess(1) - } else { - configFile.createNewFile() - configFile.writeText("$name:$password:localhost:7070:0") - println("Logged in! Run the command again to start talking!") - exitProcess(0) - } - } else { - println("Choose a username:") - val newName = readln() - if (newName == "") { - println("Please choose a username! Rerun the program and try again") - exitProcess(1) - println("Choose a password:") - val newPassword = readln() - if (newPassword == "") { - println("Please choose a password! Rerun the program and try again") - exitProcess(1) - } - val request = Fuel.get("http://localhost:7070/api/createAccount/username:{$newName}token:{${md5(newPassword)}}").body.string() - if (request == "That username already exists on the server! Please choose a different username") { - println("That username already exists on the server! Rerun the program and choose a different username") - exitProcess(1) - } else { - configFile.createNewFile() - configFile.writeText("$newName:$newPassword:localhost:7070:0") - println("Account created!") - exitProcess(0) - } - } - } -} - var configStage = 0 for (char in configFile.readText()) { if (char == ':') {configStage ++} @@ -88,22 +46,67 @@ suspend fun main(args: Array) { hasHTTPS = hasHTTPS.replace(":", "") password = password.replace(":", "") - // Actual code - println("Chookpen Client initialised!") - println("Hello $name@$server") - val protocol = "http" - if (hasHTTPS == "1") { - val protocol = "https" + if (password == "x") { + println("Enter your password:") + password = readln() } - if (args.count() == 0) { - println(Fuel.get("$protocol://$server:$port/api/syncmessages/username:{$name}token:{${md5(password)}}").body.string()) - } else { - val message = args[0] - val isSuccessful = Fuel.get("$protocol://$server:$port/api/send/username:{$name}token:{${md5(password)}}message:{$message}").body.string() - if (isSuccessful != "Success") { - println(isSuccessful) + + 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 } - println(Fuel.get("$protocol://$server:$port/api/syncmessages/username:{$name}token:{${md5(password)}}").body.string()) } - exitProcess(0) } diff --git a/server/src/main/kotlin/Main.kt b/server/src/main/kotlin/Main.kt index ff3480f..0c117bf 100644 --- a/server/src/main/kotlin/Main.kt +++ b/server/src/main/kotlin/Main.kt @@ -1,23 +1,75 @@ package xyz.maxwellj.chookpen import io.javalin.Javalin - -import com.sun.net.httpserver.HttpExchange -import com.sun.net.httpserver.HttpHandler -import com.sun.net.httpserver.HttpServer -import java.net.InetSocketAddress +import io.javalin.websocket.WsContext +import java.util.concurrent.ConcurrentHashMap +import java.util.UUID import java.io.File import java.io.BufferedReader +/* +object WsSessionManager { + val sessions = ConcurrentHashMap() + 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) + } + } +} +*/ +object WsSessionManager { + private val sessions = ConcurrentHashMap() -fun main(args: Array) { - val app = Javalin.create() - .get("/") { ctx -> ctx.result("dingus") } - .get("/api/send/{content}") { ctx -> ctx.result(handleSentMessage(ctx.pathParam("content")))} - .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")))} - .start(7070) + 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 + } + + fun removeSession(ctx: WsContext) { + // Find and remove the session by context + sessions.entries.removeIf { it.value === ctx } + } + + fun broadcast(message: String) { + sessions.values.forEach { ctx -> + 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 + } + } + + return("$username: $message") } fun handleSentMessage(inputData: String): String { @@ -306,3 +358,78 @@ fun authKey(inputData: String): String { return("Success") } +fun main(args: Array) { + 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) + 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") + } + } + } + .start(7070) +} + +/* +fun main(args: Array) { + 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) +}*/ + +