diff --git a/src/main/kotlin/com/shr4pnel/ferretirc/net/MessageParser.kt b/src/main/kotlin/com/shr4pnel/ferretirc/net/MessageParser.kt index c0745ac..973f5c6 100644 --- a/src/main/kotlin/com/shr4pnel/ferretirc/net/MessageParser.kt +++ b/src/main/kotlin/com/shr4pnel/ferretirc/net/MessageParser.kt @@ -9,66 +9,70 @@ import org.slf4j.helpers.NOPLogger class MessageParser(val incoming: Channel) { val incomingParsedMessages = Channel() - - private companion object MessageBuilder { - fun getTrailingParameterIndex(params: List) = params.indexOfLast { it.startsWith(":") } - - sealed interface Command { - fun toServerMessage(): ServerMessage - - class NamedCommand(val name: String, val parameters: List) : Command { - override fun toServerMessage(): ServerMessage { - logger.debug { "Parsing ${name.toUpperCasePreservingASCIIRules()}, with params $parameters" } - - return when (name.toUpperCasePreservingASCIIRules()) { - // PONG :PREFIX COMMAND HOSTNAME :TOKEN - "PONG" -> { - val tokenIndex = getTrailingParameterIndex(parameters) - if (tokenIndex != -1) ServerMessage.Pong( - parameters.subList(tokenIndex, parameters.size).joinToString(" ").removePrefix(":") - ) - else ServerMessage.Pong() - } - // MODE :PREFIX NICK :MODES - "MODE" -> { - ServerMessage.Mode(parameters.first(), parameters.last().removePrefix(":")) // TODO MODE, CHANMODE, LOCALMODE - } - - "NOTICE", "PRIVMSG" -> { - val cmd = name.toUpperCasePreservingASCIIRules() - val trailingIndex = getTrailingParameterIndex(parameters) - if (trailingIndex < 1) { - logger.warn { "Received malformed $cmd. Returning unimplemented as fallback." } - return ServerMessage.UNIMPLEMENTED("$cmd ${parameters.joinToString(" ")}") - } - val targets = parameters.subList(0, trailingIndex - 1) - if (cmd == "NOTICE") - ServerMessage.Notice(targets, "") - else - ServerMessage.PrivMsg(targets, "") - - } - - else -> { - logger.warn { "$name left unparsed" } - ServerMessage.UNIMPLEMENTED("$name ${parameters.joinToString(" ")}") - } - } - } - } private val logger = if (Helpers.enableLogging) KotlinLogging.logger {} else KotlinLogging.logger(NOPLogger.NOP_LOGGER) - class NumericCommand(val number: Int, val parameters: List?) : Command { - override fun toServerMessage(): ServerMessage { - return ServerMessage.UNIMPLEMENTED("$number ${parameters?.joinToString(" ")}") - } - } - } - + class MessageBuilder { private var tags: String? = null private var prefix: String? = null private lateinit var command: Command + abstract class Command(open val command: String, open val parameters: List) { + abstract fun toServerMessage(): ServerMessage + fun getTrailingParameterIndex() = parameters.indexOfFirst { it.startsWith(":") } + + fun getTrailingParameterString() = + parameters + .subList(getTrailingParameterIndex(), parameters.size) + .joinToString(" ") + .removePrefix(":") + + fun getNonTrailingParameterString() = + parameters + .subList(0, getTrailingParameterIndex()) + .joinToString(" ") + .removePrefix(":") + + fun getNonTrailingParameters() = getNonTrailingParameterString().split(" ") + + class NamedCommand(override val command: String, override val parameters: List) : Command(command, parameters) { + override fun toServerMessage() = when (command.uppercase()) { + "PONG" -> ServerMessage.Pong(getTrailingParameterString().removePrefix(":")) + "MODE" -> ServerMessage.Mode( + parameters.first(), + parameters.last().removePrefix(":") + ) // TODO MODE, CHANMODE, LOCALMODE + + "NOTICE" -> ServerMessage.Notice( + getNonTrailingParameters(), + getTrailingParameterString() + ) + + "PRIVMSG" -> ServerMessage.PrivMsg( + getNonTrailingParameters(), + getTrailingParameterString() + ) + + else -> { + ServerMessage.UNIMPLEMENTED("$command ${parameters.joinToString(" ")}") + } + } + } + + class NumericCommand(override val command: String, override val parameters: List) : Command(command, parameters) { + override fun toServerMessage() = when (command.toInt()) { + 5 -> ServerMessage.Numeric.RPL_ISUPPORT(getNonTrailingParameterString()) + 321 -> ServerMessage.Numeric.RPL_LISTSTART() + 322 -> ServerMessage.Numeric.RPL_LIST( + parameters[1], + parameters[2].toInt(), + getTrailingParameterString() + ) + 323 -> ServerMessage.Numeric.RPL_LISTEND() + else -> ServerMessage.UNIMPLEMENTED(parameters.toString()) + } + } + } + /** * Messages have this format, as rough ABNF: * @@ -86,15 +90,30 @@ class MessageParser(val incoming: Channel) { * * parameters: If it exists, data relevant to this specific command. * - * ignoring tags for now ;-;.. still making space for them + * =================== + * + * Convert line from server into tokens split on strings and remove elements that belong + * to different segments of the IRC message e.g. tags, prefixes etc */ fun build(commandList: List): ServerMessage { val tokens = commandList.toMutableList() - tags = if (tokens.first().startsWith("@")) tokens.removeFirst() else null - prefix = if (tokens.first().startsWith(":")) tokens.removeFirst() else null + + // remove tags from strlist if present + tags = if (tokens.first().startsWith("@")) { + tokens.removeFirst() + } else null + + // remove client prefix (nick&opt hostname) if present + prefix = if (tokens.first().startsWith(":")) { + tokens.removeFirst() + } else null + + // get command e.g. PING val commandStr = tokens.removeFirst() - command = if (commandStr.toIntOrNull() != null) Command.NumericCommand(commandStr.toInt(), tokens) - else Command.NamedCommand(commandStr, tokens) + + // Check if command is a numeric (RPL/ERR) or named + command = if (commandStr.toIntOrNull() == null) Command.NamedCommand(commandStr, tokens) + else Command.NumericCommand(commandStr, tokens) return command.toServerMessage() } @@ -103,9 +122,11 @@ class MessageParser(val incoming: Channel) { } suspend fun start() { + + val builder = MessageBuilder() for (msg in incoming) { - incomingParsedMessages.send(build(msg)) logger.trace { "Receiving: $msg" } + incomingParsedMessages.send(builder.build(msg)) } } } \ No newline at end of file