Create MessageParser, use in Reader

This commit is contained in:
2026-08-12 13:27:40 +01:00
parent fcac3d5431
commit 25333482c1
2 changed files with 117 additions and 7 deletions
@@ -0,0 +1,83 @@
package com.shr4pnel.ferretirc.net
import com.shr4pnel.ferretirc.net.messages.ServerMessage
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.util.toUpperCasePreservingASCIIRules
import kotlinx.coroutines.channels.Channel
class MessageParser(val incoming: Channel<String>) {
private val logger = KotlinLogging.logger("MessageParser")
val incomingParsedMessages = Channel<ServerMessage>()
private companion object MessageBuilder {
sealed interface Command {
fun toServerMessage(): ServerMessage
class NamedCommand(val name: String, val parameters: List<String>) : Command {
override fun toServerMessage(): ServerMessage {
return when (name.toUpperCasePreservingASCIIRules()) {
// PONG :PREFIX COMMAND HOSTNAME :TOKEN
"PONG" -> {
val tokenIndex = parameters.indexOfLast { it.contains(":") }
if (tokenIndex != -1) ServerMessage.Pong(
parameters.subList(tokenIndex, parameters.size).joinToString(" ").removePrefix(":")
)
else ServerMessage.Pong()
}
else -> ServerMessage.UNIMPLEMENTED("$name ${parameters.joinToString(" ")}")
}
}
}
class NumericCommand(val number: Int, val parameters: List<String>?) : Command {
override fun toServerMessage(): ServerMessage {
return ServerMessage.UNIMPLEMENTED("$number ${parameters?.joinToString(" ")}")
}
}
}
private var tags: String? = null
private var prefix: String? = null
private lateinit var command: Command
/**
* Messages have this format, as rough ABNF:
*
* message ::= ['@' <tags> SPACE] [':' <source> SPACE] <command> <parameters> <crlf>
* SPACE ::= %x20 *( %x20 ) ; space character(s)
* crlf ::= %x0D %x0A ; "carriage return" "linefeed"
*
* The specific parts of an IRC message are:
*
* tags: Optional metadata on a message, starting with ('@', 0x40).
*
* source: Optional note of where the message came from, starting with (':', 0x3A).
*
* command: The specific command this message represents.
*
* parameters: If it exists, data relevant to this specific command.
*
* ignoring tags for now ;-;.. still making space for them
*/
fun build(commandList: List<String>): ServerMessage {
val tokens = commandList.toMutableList()
tags = if (tokens.first().startsWith("@")) tokens.removeFirst() else null
prefix = if (tokens.first().startsWith(":")) tokens.removeFirst() else null
val commandStr = tokens.removeFirst()
command = if (commandStr.toIntOrNull() != null) Command.NumericCommand(commandStr.toInt(), tokens)
else Command.NamedCommand(commandStr, tokens)
return command.toServerMessage()
}
fun build(command: String) = build(command.split(" "))
}
suspend fun start() {
for (msg in incoming) {
logger.debug { msg }
incomingParsedMessages.send(build(msg))
}
}
}