diff --git a/build.gradle.kts b/build.gradle.kts
index 8051e2f1bc9038c79da7efee2c12b087a1f94ded..4b9da003ce9e5c4d29963aaf1cff39cf430e488d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -22,7 +22,7 @@ buildscript {
   }
   dependencies {
     classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.4.20")
-    classpath("org.jetbrains.kotlin", "kotlin-gradle-plugin", "1.4.30")
+    classpath("org.jetbrains.kotlin", "kotlin-gradle-plugin", "1.4.31")
   }
 }
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ChannelModeType.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ChannelModeType.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6e0f662e2e579209770674fe552cf9ade286cb60
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ChannelModeType.kt
@@ -0,0 +1,34 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.models
+
+enum class ChannelModeType(
+  /**
+   * Underlying representation
+   */
+  val value: UInt,
+) {
+  // NOT_A_CHANMODE(0x00u),
+  A_CHANMODE(0x01u),
+  B_CHANMODE(0x02u),
+  C_CHANMODE(0x04u),
+  D_CHANMODE(0x08u);
+
+  companion object {
+    private val values = enumValues<ChannelModeType>()
+      .associateBy(ChannelModeType::value)
+
+    /**
+     * Obtain from underlying representation
+     */
+    fun of(value: UInt): ChannelModeType? = values[value]
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ChannelModes.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ChannelModes.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cd07ccf0a824b693559c693697a5a991d303f380
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ChannelModes.kt
@@ -0,0 +1,90 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.models
+
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+
+data class ChannelModes(
+  val a: Map<Char, Set<String>> = emptyMap(),
+  val b: Map<Char, String> = emptyMap(),
+  val c: Map<Char, String> = emptyMap(),
+  val d: Set<Char> = emptySet()
+) {
+  fun modeString(): String {
+    if (b.isEmpty() && c.isEmpty() && d.isEmpty()) {
+      return ""
+    }
+
+    val buffer = StringBuilder("+")
+    val params = mutableListOf<String>()
+
+    d.joinTo(buffer)
+
+    for ((mode, param) in c) {
+      buffer.append(mode)
+      params.add(param)
+    }
+
+    for ((mode, param) in b) {
+      buffer.append(mode)
+      params.add(param)
+    }
+
+    buffer.append(' ')
+    params.joinTo(buffer, " ")
+    return buffer.toString()
+  }
+
+  fun toVariantMap(): QVariantMap {
+    return mapOf(
+      "A" to qVariant(
+        a.map { (key, value) ->
+          key.toString() to qVariant(value.toList(), QtType.QStringList)
+        }.toMap(),
+        QtType.QVariantMap
+      ),
+      "B" to qVariant(
+        b.map { (key, value) ->
+          key.toString() to qVariant(value, QtType.QString)
+        }.toMap(),
+        QtType.QVariantMap
+      ),
+      "C" to qVariant(
+        c.map { (key, value) ->
+          key.toString() to qVariant(value, QtType.QString)
+        }.toMap(),
+        QtType.QVariantMap
+      ),
+      "D" to qVariant(d.joinToString(), QtType.QString),
+    )
+  }
+
+  companion object {
+    fun fromVariantMap(properties: QVariantMap) = ChannelModes(
+      a = properties["A"].into<QVariantMap>()?.map { (key, value) ->
+        key.first() to value.into<QStringList>()
+          ?.filterNotNull()
+          ?.toSet()
+          .orEmpty()
+      }?.toMap().orEmpty(),
+      b = properties["B"].into<QVariantMap>()?.map { (key, value) ->
+        key.first() to value.into<String>().orEmpty()
+      }?.toMap().orEmpty(),
+      c = properties["C"].into<QVariantMap>()?.map { (key, value) ->
+        key.first() to value.into<String>().orEmpty()
+      }?.toMap().orEmpty(),
+      d = properties["D"].into<String>()?.toSet().orEmpty()
+    )
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectionState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectionState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a0bfc5801d775b851f8f55a07945dd6a2fa147e3
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectionState.kt
@@ -0,0 +1,35 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.models
+
+enum class ConnectionState(
+  /**
+   * Underlying representation
+   */
+  val value: Int,
+) {
+  Disconnected(0),
+  Connecting(1),
+  Initializing(2),
+  Initialized(3),
+  Reconnecting(4),
+  Disconnecting(5);
+
+  companion object {
+    private val values = enumValues<ConnectionState>()
+      .associateBy(ConnectionState::value)
+
+    /**
+     * Obtain from underlying representation
+     */
+    fun of(value: Int): ConnectionState? = values[value]
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
index 44a06f74ad9d7ec67ef9b25a115ca7a6aaa32ea1..b078a7ce65d637fb560a592a5b9c8f257ed0b7b1 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
@@ -15,10 +15,14 @@ import de.justjanne.libquassel.protocol.models.BufferInfo
 import de.justjanne.libquassel.protocol.models.Command
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.state.AliasManagerState
 import de.justjanne.libquassel.protocol.syncables.stubs.AliasManagerStub
+import de.justjanne.libquassel.protocol.util.expansion.Expansion
+import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
 
 class AliasManager constructor(
   session: Session
@@ -32,8 +36,8 @@ class AliasManager constructor(
   }
 
   private fun initAliases(): QVariantMap = mapOf(
-    "names" to qVariant(aliases.map(Alias::name), QtType.QStringList),
-    "expansions" to qVariant(aliases.map(Alias::expansion), QtType.QStringList)
+    "names" to qVariant(aliases().map(Alias::name), QtType.QStringList),
+    "expansions" to qVariant(aliases().map(Alias::expansion), QtType.QStringList)
   )
 
   private fun initSetAliases(aliases: QVariantMap) {
@@ -44,7 +48,9 @@ class AliasManager constructor(
       "Sizes do not match: names=${names.size}, expansions=${expansions.size}"
     }
 
-    this.aliases = names.zip(expansions, ::Alias)
+    state.update {
+      copy(aliases = names.zip(expansions, ::Alias))
+    }
   }
 
   override fun addAlias(name: String, expansion: String) {
@@ -52,13 +58,17 @@ class AliasManager constructor(
       return
     }
 
-    aliases += Alias(name, expansion)
+    state.update {
+      copy(aliases = aliases + Alias(name, expansion))
+    }
     super.addAlias(name, expansion)
   }
 
-  fun indexOf(name: String?) = aliases.map(Alias::name).indexOf(name)
+  fun aliases() = state.value.aliases
 
-  fun contains(name: String?) = aliases.map(Alias::name).contains(name)
+  fun indexOf(name: String?) = aliases().map(Alias::name).indexOf(name)
+
+  fun contains(name: String?) = aliases().map(Alias::name).contains(name)
 
   fun processInput(
     info: BufferInfo,
@@ -78,7 +88,7 @@ class AliasManager constructor(
       // pure text. To ensure this won’t be unescaped twice it’s sent with /SAY.
       previousCommands.add(Command(info, "/SAY $arguments"))
     } else {
-      val found = aliases.firstOrNull { it.name.equals(command, true) }
+      val found = aliases().firstOrNull { it.name.equals(command, true) }
       if (found != null) {
         expand(found.expansion ?: "", info, arguments, previousCommands)
       } else {
@@ -93,32 +103,57 @@ class AliasManager constructor(
     arguments: String,
     previousCommands: MutableList<Command>
   ) {
-    /*
+    val network = session.network(bufferInfo.networkId)
     val params = arguments.split(' ')
-    expansion.split(';')
-      .map(String::trimStart)
-      .map(Expansion.Companion::parse)
-      .map {
-        it.map {
-          when (it) {
-            is Expansion.Constant -> TODO()
-            is Expansion.Parameter -> TODO()
-            is Expansion.ParameterRange ->
-              params.subList(it.from, it.to ?: params.size)
-                .joinToString(" ")
-            is Expansion.Text ->
-              it.value
-          }
-        }
-      }
-    */
-  }
-
-  fun copy() = AliasManager(session).also {
-    it.fromVariantMap(toVariantMap())
+    previousCommands.add(
+      Command(
+        bufferInfo,
+        expansion.split(';')
+          .map(String::trimStart)
+          .map(Expansion.Companion::parse)
+          .map {
+            it.map {
+              when (it) {
+                is Expansion.Constant -> when (it.field) {
+                  Expansion.ConstantField.CHANNEL ->
+                    bufferInfo.bufferName
+                  Expansion.ConstantField.NICK ->
+                    network?.myNick()
+                  Expansion.ConstantField.NETWORK ->
+                    network?.networkName()
+                }
+                is Expansion.Parameter -> when (it.field) {
+                  Expansion.ParameterField.HOSTNAME ->
+                    network?.ircUser(params[it.index])?.host() ?: "*"
+                  Expansion.ParameterField.VERIFIED_IDENT ->
+                    params.getOrNull(it.index)?.let { param ->
+                      network?.ircUser(param)?.verifiedUser() ?: "*"
+                    }
+                  Expansion.ParameterField.IDENT ->
+                    params.getOrNull(it.index)?.let { param ->
+                      network?.ircUser(param)?.user() ?: "*"
+                    }
+                  Expansion.ParameterField.ACCOUNT ->
+                    params.getOrNull(it.index)?.let { param ->
+                      network?.ircUser(param)?.account() ?: "*"
+                    }
+                  null -> params.getOrNull(it.index) ?: it.source
+                }
+                is Expansion.ParameterRange ->
+                  params.subList(it.from, it.to ?: params.size)
+                    .joinToString(" ")
+                is Expansion.Text ->
+                  it.source
+              } ?: it.source
+            }
+          }.joinToString(";")
+      )
+    )
   }
 
-  var aliases = listOf<Alias>()
+  private val state = MutableStateFlow(
+    AliasManagerState()
+  )
 
   companion object {
     private fun determineMessageCommand(message: String) = when {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5acdd7e097bbb37d7f7c9b7c9197a6e45622d014
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
@@ -0,0 +1,298 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.ChannelModeType
+import de.justjanne.libquassel.protocol.models.ChannelModes
+import de.justjanne.libquassel.protocol.models.QStringList
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
+import de.justjanne.libquassel.protocol.syncables.stubs.IrcChannelStub
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.indexed
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class IrcChannel(
+  name: String,
+  network: NetworkId,
+  session: Session
+) : SyncableObject(session, "IrcChannel"), IrcChannelStub {
+  override fun init() {
+    require(name().isNotEmpty()) {
+      "IrcChannel: channelName is empty"
+    }
+    renameObject("${network().id}/${name()}")
+  }
+
+  override fun fromVariantMap(properties: QVariantMap) =
+    fromVariantMap(properties, null)
+
+  fun fromVariantMap(properties: QVariantMap, index: Int?) {
+    state.update {
+      copy(
+        name = properties["name"].indexed(index).into(name),
+        topic = properties["topic"].indexed(index).into(topic),
+        password = properties["password"].indexed(index).into(password),
+        encrypted = properties["encrypted"].indexed(index).into(encrypted),
+        channelModes = properties["ChanModes"].indexed(index).into<QVariantMap>()
+          ?.let(ChannelModes.Companion::fromVariantMap) ?: channelModes,
+        userModes = properties["UserModes"].into<QVariantMap>()
+          ?.mapValues { (_, value) -> value.into<String>()?.toSet().orEmpty() }
+          .orEmpty()
+      )
+    }
+  }
+
+  override fun toVariantMap(): QVariantMap {
+    return mapOf(
+      "name" to qVariant(name(), QtType.QString),
+      "topic" to qVariant(topic(), QtType.QString),
+      "password" to qVariant(password(), QtType.QString),
+      "encrypted" to qVariant(isEncrypted(), QtType.Bool),
+      "ChanModes" to qVariant(state.value.channelModes.toVariantMap(), QtType.QVariantMap),
+      "UserModes" to qVariant(
+        state.value.userModes.mapValues { (_, value) ->
+          qVariant(value.joinToString(), QtType.QString)
+        },
+        QtType.QVariantMap
+      )
+    )
+  }
+
+  fun network() = state.value.network
+  fun name() = state.value.name
+  fun topic() = state.value.topic
+  fun password() = state.value.password
+  fun isEncrypted() = state.value.encrypted
+  fun ircUsers() = session.network(network())?.let { network ->
+    state.value.userModes.keys.mapNotNull(network::ircUser)
+  }.orEmpty()
+
+  fun userCount() = state.value.userModes.size
+  fun userModes(nick: String) = state.value.userModes[nick]
+  fun hasMode(mode: Char) = when (session.network(network())?.channelModeType(mode)) {
+    ChannelModeType.A_CHANMODE ->
+      state.value.channelModes.a.contains(mode)
+    ChannelModeType.B_CHANMODE ->
+      state.value.channelModes.b.contains(mode)
+    ChannelModeType.C_CHANMODE ->
+      state.value.channelModes.c.contains(mode)
+    ChannelModeType.D_CHANMODE ->
+      state.value.channelModes.d.contains(mode)
+    else ->
+      false
+  }
+
+  fun modeValue(mode: Char) = when (session.network(network())?.channelModeType(mode)) {
+    ChannelModeType.B_CHANMODE ->
+      state.value.channelModes.b[mode] ?: ""
+    ChannelModeType.C_CHANMODE ->
+      state.value.channelModes.c[mode] ?: ""
+    else ->
+      ""
+  }
+
+  fun modeValues(mode: Char) = when (session.network(network())?.channelModeType(mode)) {
+    ChannelModeType.A_CHANMODE ->
+      state.value.channelModes.a[mode].orEmpty()
+    else ->
+      emptySet()
+  }
+
+  fun channelModeString() = state.value.channelModes.modeString()
+
+  override fun setTopic(topic: String) {
+    state.update {
+      copy(topic = topic)
+    }
+    super.setTopic(topic)
+  }
+
+  override fun setPassword(password: String) {
+    state.update {
+      copy(password = password)
+    }
+    super.setPassword(password)
+  }
+
+  override fun setEncrypted(encrypted: Boolean) {
+    state.update {
+      copy(encrypted = encrypted)
+    }
+    super.setEncrypted(encrypted)
+  }
+
+  override fun joinIrcUsers(nicks: QStringList, modes: QStringList) {
+    joinIrcUsers(
+      nicks.filterNotNull().zip(modes).map { (nick, mode) ->
+        Pair(nick, mode?.toSet().orEmpty())
+      }.toMap()
+    )
+    super.joinIrcUsers(nicks, modes)
+  }
+
+  private fun joinIrcUsers(map: Map<String, Set<Char>>) {
+    val network = session.network(network())
+
+    val newNicks = map.keys - state.value.userModes.keys
+    state.update {
+      copy(
+        userModes = userModes + map.mapValues { (key, value) ->
+          value + userModes[key].orEmpty()
+        }
+      )
+    }
+    for (newNick in newNicks) {
+      network
+        ?.ircUser(newNick)
+        ?.joinChannel(this, skipChannelJoin = true)
+    }
+  }
+
+  fun joinIrcUser(user: IrcUser) = joinIrcUsers(
+    mapOf(
+      user.nick() to emptySet()
+    )
+  )
+
+  override fun part(nick: String) {
+    val network = session.network(network())
+    val partingUser = network?.ircUser(nick)
+
+    if (partingUser != null) {
+      partingUser.partChannel(name())
+      if (network.isMe(partingUser) || state.value.userModes.isEmpty()) {
+        for (nickname in state.value.userModes.keys.toList()) {
+          network.ircUser(nickname)?.partChannel(this)
+        }
+        state.update {
+          copy(channelModes = ChannelModes())
+        }
+        network.removeIrcChannel(this)
+        session.stopSynchronize(this)
+      }
+    }
+    super.part(nick)
+  }
+
+  override fun setUserModes(nick: String, modes: String?) {
+    state.update {
+      copy(
+        userModes = userModes + Pair(
+          nick,
+          modes?.toSet().orEmpty()
+        )
+      )
+    }
+    super.setUserModes(nick, modes)
+  }
+
+  override fun addUserMode(nick: String, mode: String?) {
+    state.update {
+      copy(
+        userModes = userModes + Pair(
+          nick,
+          userModes[nick].orEmpty() + mode?.toSet().orEmpty()
+        )
+      )
+    }
+    super.addUserMode(nick, mode)
+  }
+
+  override fun removeUserMode(nick: String, mode: String?) {
+    state.update {
+      copy(
+        userModes = userModes + Pair(
+          nick,
+          userModes[nick].orEmpty() - mode?.toSet().orEmpty()
+        )
+      )
+    }
+    super.addUserMode(nick, mode)
+  }
+
+  override fun addChannelMode(mode: Char, value: String?) {
+    val network = session.network(network())
+    state.update {
+      copy(
+        channelModes = channelModes.run {
+          when (network?.channelModeType(mode)) {
+            ChannelModeType.A_CHANMODE -> {
+              requireNotNull(value) {
+                "Mode $mode of ChannelModeType A must have a value"
+              }
+
+              copy(a = a + Pair(mode, a[mode].orEmpty() + value))
+            }
+            ChannelModeType.B_CHANMODE -> {
+              requireNotNull(value) {
+                "Mode $mode of ChannelModeType B must have a value"
+              }
+
+              copy(b = b + Pair(mode, value))
+            }
+            ChannelModeType.C_CHANMODE -> {
+              requireNotNull(value) {
+                "Mode $mode of ChannelModeType C must have a value"
+              }
+
+              copy(c = c + Pair(mode, value))
+            }
+            ChannelModeType.D_CHANMODE ->
+              copy(d = d + mode)
+            else -> channelModes
+          }
+        }
+      )
+    }
+    super.addChannelMode(mode, value)
+  }
+
+  override fun removeChannelMode(mode: Char, value: String?) {
+    val network = session.network(network())
+    state.update {
+      copy(
+        channelModes = channelModes.run {
+          when (network?.channelModeType(mode)) {
+            ChannelModeType.A_CHANMODE -> {
+              requireNotNull(value) {
+                "Mode $mode of ChannelModeType A must have a value"
+              }
+
+              copy(a = a + Pair(mode, a[mode].orEmpty() - value))
+            }
+            ChannelModeType.B_CHANMODE -> {
+              copy(b = b - mode)
+            }
+            ChannelModeType.C_CHANMODE -> {
+              copy(b = c - mode)
+            }
+            ChannelModeType.D_CHANMODE ->
+              copy(d = d - mode)
+            else -> channelModes
+          }
+        }
+      )
+    }
+    super.removeChannelMode(mode, value)
+  }
+
+  private val state = MutableStateFlow(
+    IrcChannelState(
+      network = network,
+      name = name
+    )
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
new file mode 100644
index 0000000000000000000000000000000000000000..091bc9591ef40622dbe9e7124dbcd070104c870c
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
@@ -0,0 +1,316 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
+import de.justjanne.libquassel.protocol.syncables.stubs.IrcUserStub
+import de.justjanne.libquassel.protocol.util.irc.HostmaskHelper
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.indexed
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.threeten.bp.Instant
+import org.threeten.bp.temporal.Temporal
+
+class IrcUser(
+  hostmask: String,
+  network: NetworkId,
+  session: Session
+) : SyncableObject(session, "IrcUser"), IrcUserStub {
+  override fun init() {
+    updateObjectName()
+  }
+
+  private fun updateObjectName() {
+    renameObject("${network().id}/${nick()}")
+  }
+
+  override fun fromVariantMap(properties: QVariantMap) =
+    fromVariantMap(properties, null)
+
+  fun fromVariantMap(properties: QVariantMap, index: Int?) {
+    state.update {
+      copy(
+        nick = properties["nick"].indexed(index).into(nick),
+        user = properties["user"].indexed(index).into(user),
+        host = properties["host"].indexed(index).into(host),
+        realName = properties["realName"].indexed(index).into(realName),
+        account = properties["account"].indexed(index).into(account),
+        away = properties["away"].indexed(index).into(away),
+        awayMessage = properties["user"].indexed(index).into(awayMessage),
+        idleTime = properties["idleTime"].indexed(index).into(idleTime),
+        loginTime = properties["loginTime"].indexed(index).into(loginTime),
+        server = properties["server"].indexed(index).into(server),
+        ircOperator = properties["ircOperator"].indexed(index).into(ircOperator),
+        lastAwayMessageTime = properties["lastAwayMessageTime"].indexed(index).into()
+          ?: properties["lastAwayMessage"].indexed(index).into<Int>()?.toLong()
+            ?.let(Instant::ofEpochSecond)
+          ?: lastAwayMessageTime,
+        whoisServiceReply = properties["whoisServiceReply"].indexed(index).into(whoisServiceReply),
+        suserHost = properties["suserHost"].indexed(index).into(suserHost),
+        encrypted = properties["encrypted"].indexed(index).into(encrypted),
+        channels = properties["channels"].indexed(index).into(channels),
+        userModes = properties["userModes"].indexed(index).into(userModes),
+      )
+    }
+  }
+
+  override fun toVariantMap() = mapOf(
+    "nick" to qVariant(nick(), QtType.QString),
+    "user" to qVariant(user(), QtType.QString),
+    "host" to qVariant(host(), QtType.QString),
+    "realName" to qVariant(realName(), QtType.QString),
+    "account" to qVariant(account(), QtType.QString),
+    "away" to qVariant(isAway(), QtType.Bool),
+    "awayMessage" to qVariant(awayMessage(), QtType.QString),
+    "idleTime" to qVariant(idleTime(), QtType.QDateTime),
+    "loginTime" to qVariant(loginTime(), QtType.QDateTime),
+    "server" to qVariant(server(), QtType.QString),
+    "ircOperator" to qVariant(ircOperator(), QtType.QString),
+    "lastAwayMessage" to qVariant(lastAwayMessageTime().epochSecond.toInt(), QtType.Int),
+    "lastAwayMessageTime" to qVariant(lastAwayMessageTime(), QtType.QDateTime),
+    "whoisServiceReply" to qVariant(whoisServiceReply(), QtType.QString),
+    "suserHost" to qVariant(suserHost(), QtType.QString),
+    "encrypted" to qVariant(encrypted(), QtType.Bool),
+
+    "channels" to qVariant(channels(), QtType.QStringList),
+    "userModes" to qVariant(userModes(), QtType.QString)
+  )
+
+  override fun updateHostmask(mask: String) {
+    state.update {
+      val (_, user, host) = HostmaskHelper.split(mask)
+      copy(user = user, host = host)
+    }
+    super.updateHostmask(mask)
+  }
+
+  override fun addUserModes(modes: String) {
+    state.update {
+      copy(userModes = userModes + modes.toSet())
+    }
+    super.addUserModes(modes)
+  }
+
+  override fun removeUserModes(modes: String) {
+    state.update {
+      copy(userModes = userModes - modes.toSet())
+    }
+    super.removeUserModes(modes)
+  }
+
+  override fun setUser(user: String) {
+    state.update {
+      copy(user = user)
+    }
+    super.setUser(user)
+  }
+
+  override fun setHost(host: String) {
+    state.update {
+      copy(host = host)
+    }
+    super.setHost(host)
+  }
+
+  override fun setNick(nick: String) {
+    val network = session.network(network())
+    network?.ircUserNickChanged(nick(), nick)
+    state.update {
+      copy(nick = nick)
+    }
+    updateObjectName()
+    super.setNick(nick)
+  }
+
+  override fun setRealName(realName: String) {
+    state.update {
+      copy(realName = realName)
+    }
+    super.setRealName(realName)
+  }
+
+  override fun setAccount(account: String) {
+    state.update {
+      copy(account = account)
+    }
+    super.setAccount(account)
+  }
+
+  override fun setAway(away: Boolean) {
+    state.update {
+      copy(away = away)
+    }
+    super.setAway(away)
+  }
+
+  override fun setAwayMessage(awayMessage: String) {
+    state.update {
+      copy(awayMessage = awayMessage)
+    }
+    super.setAwayMessage(awayMessage)
+  }
+
+  override fun setIdleTime(idleTime: Temporal) {
+    state.update {
+      copy(idleTime = Instant.from(idleTime))
+    }
+    super.setIdleTime(idleTime)
+  }
+
+  override fun setLoginTime(loginTime: Temporal) {
+    state.update {
+      copy(loginTime = Instant.from(loginTime))
+    }
+    super.setLoginTime(loginTime)
+  }
+
+  override fun setIrcOperator(ircOperator: String) {
+    state.update {
+      copy(ircOperator = ircOperator)
+    }
+    super.setIrcOperator(ircOperator)
+  }
+
+  override fun setLastAwayMessage(lastAwayMessage: Int) {
+    state.update {
+      copy(lastAwayMessageTime = Instant.ofEpochSecond(lastAwayMessage.toLong()))
+    }
+    super.setLastAwayMessage(lastAwayMessage)
+  }
+
+  override fun setLastAwayMessageTime(lastAwayMessageTime: Temporal) {
+    state.update {
+      copy(lastAwayMessageTime = Instant.from(lastAwayMessageTime))
+    }
+    super.setLastAwayMessageTime(lastAwayMessageTime)
+  }
+
+  override fun setWhoisServiceReply(whoisServiceReply: String) {
+    state.update {
+      copy(whoisServiceReply = whoisServiceReply)
+    }
+    super.setWhoisServiceReply(whoisServiceReply)
+  }
+
+  override fun setSuserHost(suserHost: String) {
+    state.update {
+      copy(suserHost = suserHost)
+    }
+    super.setSuserHost(suserHost)
+  }
+
+  override fun setEncrypted(encrypted: Boolean) {
+    state.update {
+      copy(encrypted = encrypted)
+    }
+    super.setEncrypted(encrypted)
+  }
+
+  override fun setServer(server: String) {
+    state.update {
+      copy(server = server)
+    }
+    super.setServer(server)
+  }
+
+  override fun setUserModes(modes: String) {
+    state.update {
+      copy(userModes = userModes.toSet())
+    }
+    super.setUserModes(modes)
+  }
+
+  fun joinChannel(channel: IrcChannel, skipChannelJoin: Boolean = false) {
+    if (state.value.channels.contains(channel.name())) {
+      return
+    }
+
+    state.update {
+      copy(channels = channels + channel.name())
+    }
+    if (!skipChannelJoin) {
+      channel.joinIrcUser(this)
+    }
+    super.joinChannel(channel.name())
+  }
+
+  override fun joinChannel(channelname: String) {
+    val network = session.network(network()) ?: return
+    val channel = network.newIrcChannel(channelname)
+    joinChannel(channel)
+  }
+
+  fun partChannel(channel: IrcChannel) {
+    val network = session.network(network())
+
+    state.update {
+      copy(channels = channels - channel.name())
+    }
+    channel.part(nick())
+    super.partChannel(channel.name())
+    if (channels().isEmpty() && network?.isMe(this) != true) {
+      quit()
+    }
+  }
+
+  override fun quit() {
+    val network = session.network(network())
+    for (channel in channels()) {
+      network?.ircChannel(channel)
+        ?.part(nick())
+    }
+    state.update {
+      copy(channels = emptySet())
+    }
+    network?.removeIrcUser(this)
+    session.stopSynchronize(this)
+    super.quit()
+  }
+
+  fun network() = state.value.network
+  fun nick() = state.value.nick
+  fun user() = state.value.user
+  fun verifiedUser() = user().let {
+    if (it.startsWith("~")) null
+    else it
+  }
+  fun host() = state.value.host
+  fun realName() = state.value.realName
+  fun account() = state.value.account
+  fun hostMask() = "${nick()}!${user()}@${host()}"
+  fun isAway() = state.value.away
+  fun awayMessage() = state.value.awayMessage
+  fun server() = state.value.server
+  fun idleTime() = state.value.idleTime
+
+  fun loginTime() = state.value.loginTime
+  fun ircOperator() = state.value.ircOperator
+  fun lastAwayMessageTime() = state.value.lastAwayMessageTime
+  fun whoisServiceReply() = state.value.whoisServiceReply
+  fun suserHost() = state.value.suserHost
+  fun encrypted() = state.value.encrypted
+  fun userModes() = state.value.userModes
+  fun channels() = state.value.channels
+
+  private val state = MutableStateFlow(
+    IrcUserState(
+      network = network,
+      nick = HostmaskHelper.nick(hostmask),
+      user = HostmaskHelper.user(hostmask),
+      host = HostmaskHelper.host(hostmask)
+    )
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a6c3eca5178c9ad72486bcb9cdb6be7276919993
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
@@ -0,0 +1,751 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.ChannelModeType
+import de.justjanne.libquassel.protocol.models.ConnectionState
+import de.justjanne.libquassel.protocol.models.NetworkInfo
+import de.justjanne.libquassel.protocol.models.NetworkServer
+import de.justjanne.libquassel.protocol.models.QStringList
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.syncables.state.NetworkState
+import de.justjanne.libquassel.protocol.syncables.stubs.NetworkStub
+import de.justjanne.libquassel.protocol.util.indices
+import de.justjanne.libquassel.protocol.util.irc.HostmaskHelper
+import de.justjanne.libquassel.protocol.util.irc.IrcCapability
+import de.justjanne.libquassel.protocol.util.irc.IrcCaseMapper
+import de.justjanne.libquassel.protocol.util.transpose
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+import java.nio.ByteBuffer
+import java.util.Locale
+
+class Network constructor(
+  networkId: NetworkId,
+  session: Session
+) : SyncableObject(session, "Network"), NetworkStub {
+  override fun init() {
+    renameObject("${networkId().id}")
+    super.init()
+  }
+
+  override fun fromVariantMap(properties: QVariantMap) {
+    state.update {
+      copy(
+        networkName = properties["networkName"].into(networkName),
+        currentServer = properties["currentServer"].into(currentServer),
+        myNick = properties["myNick"].into(),
+        latency = properties["latency"].into(latency),
+        codecForServer = StringSerializerUtf8.deserializeRaw(
+          properties["codecForServer"].into<ByteBuffer>()
+        ),
+        codecForEncoding = StringSerializerUtf8.deserializeRaw(
+          properties["codecForEncoding"].into<ByteBuffer>()
+        ),
+        codecForDecoding = StringSerializerUtf8.deserializeRaw(
+          properties["codecForDecoding"].into<ByteBuffer>()
+        ),
+        identity = properties["identityId"]
+          .into(identity),
+        connected = properties["isConnected"]
+          .into(connected),
+        connectionState = ConnectionState.of(
+          properties["connectionState"].into(connectionState.value)
+        ) ?: ConnectionState.Disconnected,
+        useRandomServer = properties["useRandomServer"]
+          .into(useRandomServer),
+        perform = properties["perform"]
+          .into(perform),
+        useAutoIdentify = properties["useAutoIdentify"]
+          .into(useAutoIdentify),
+        autoIdentifyService = properties["autoIdentifyService"]
+          .into(autoIdentifyService),
+        autoIdentifyPassword = properties["autoIdentifyPassword"]
+          .into(autoIdentifyPassword),
+        useSasl = properties["useSasl"]
+          .into(useSasl),
+        saslAccount = properties["saslAccount"]
+          .into(saslAccount),
+        saslPassword = properties["saslPassword"]
+          .into(saslPassword),
+        useAutoReconnect = properties["useAutoReconnect"]
+          .into(useAutoReconnect),
+        autoReconnectInterval = properties["autoReconnectInterval"]
+          .into(autoReconnectInterval),
+        autoReconnectRetries = properties["autoReconnectRetries"]
+          .into(autoReconnectRetries),
+        unlimitedReconnectRetries = properties["unlimitedReconnectRetries"]
+          .into(unlimitedReconnectRetries),
+        rejoinChannels = properties["rejoinChannels"]
+          .into(rejoinChannels),
+        useCustomMessageRate = properties["useCustomMessageRate"]
+          .into(useCustomMessageRate),
+        messageRateBurstSize = properties["msgRateBurstSize"]
+          .into(messageRateBurstSize),
+        messageRateDelay = properties["messageRateDelay"]
+          .into(messageRateDelay),
+        unlimitedMessageRate = properties["unlimitedMessageRate"]
+          .into(unlimitedMessageRate),
+        skipCaps = properties["skipCaps"]
+          .into<QStringList>()
+          ?.filterNotNull()
+          ?.toSet()
+          .orEmpty(),
+        serverList = properties["ServerList"].into<QVariantList>()
+          ?.mapNotNull { it.into<NetworkServer>() }
+          .orEmpty(),
+        supports = properties["Supports"].into<QVariantMap>()
+          ?.mapValues { (_, value) -> value.into<String>() }
+          .orEmpty(),
+        caps = properties["Caps"].into<QVariantMap>()?.mapValues { (_, value) ->
+          value.into<String>()
+        }.orEmpty(),
+        capsEnabled = properties["CapsEnabled"].into<QVariantList>()
+          ?.mapNotNull { it.into<String>() }
+          ?.toSet()
+          .orEmpty(),
+        ircUsers = properties["IrcUsersAndChannels"].into<QVariantMap>()
+          ?.get("Users")?.into<QVariantMap>()
+          ?.let {
+            it.indices.map { index ->
+              newIrcUser(
+                properties["nick"]
+                  .into<QVariantList>()
+                  ?.getOrNull(index)
+                  .into(""),
+                properties,
+                index
+              )
+            }
+          }
+          ?.associateBy { caseMapper().toLowerCase(it.nick()) }
+          .orEmpty(),
+        ircChannels = properties["IrcUsersAndChannels"].into<QVariantMap>()
+          ?.get("Channels")?.into<QVariantMap>()
+          ?.let {
+            it.indices.map { index ->
+              newIrcChannel(
+                properties["name"]
+                  .into<QVariantList>()
+                  ?.getOrNull(index)
+                  .into(""),
+                properties,
+                index
+              )
+            }
+          }
+          ?.associateBy { caseMapper().toLowerCase(it.name()) }
+          .orEmpty()
+      )
+    }
+    state.update {
+      val (prefixes, prefixModes) = determinePrefixes()
+      val channelModeTypes = determineChannelModeTypes()
+      copy(
+        prefixes = prefixes,
+        prefixModes = prefixModes,
+        channelModes = channelModeTypes
+      )
+    }
+  }
+
+  override fun toVariantMap() = mapOf(
+    "networkName" to qVariant(networkName(), QtType.QString),
+    "currentServer" to qVariant(currentServer(), QtType.QString),
+    "myNick" to qVariant(myNick(), QtType.QString),
+    "latency" to qVariant(latency(), QtType.Int),
+    "codecForServer" to qVariant(
+      StringSerializerUtf8.serializeRaw(codecForServer()),
+      QtType.QByteArray
+    ),
+    "codecForEncoding" to qVariant(
+      StringSerializerUtf8.serializeRaw(codecForEncoding()),
+      QtType.QByteArray
+    ),
+    "codecForDecoding" to qVariant(
+      StringSerializerUtf8.serializeRaw(codecForDecoding()),
+      QtType.QByteArray
+    ),
+    "identityId" to qVariant(identity(), QuasselType.IdentityId),
+    "isConnected" to qVariant(isConnected(), QtType.Bool),
+    "connectionState" to qVariant(connectionState().value, QtType.Int),
+    "useRandomServer" to qVariant(useRandomServer(), QtType.Bool),
+    "perform" to qVariant(perform(), QtType.QStringList),
+    "useAutoIdentify" to qVariant(useAutoIdentify(), QtType.Bool),
+    "autoIdentifyService" to qVariant(autoIdentifyService(), QtType.QString),
+    "autoIdentifyPassword" to qVariant(autoIdentifyPassword(), QtType.QString),
+    "useSasl" to qVariant(useSasl(), QtType.Bool),
+    "saslAccount" to qVariant(saslAccount(), QtType.QString),
+    "saslPassword" to qVariant(saslPassword(), QtType.QString),
+    "useAutoReconnect" to qVariant(useAutoReconnect(), QtType.Bool),
+    "autoReconnectInterval" to qVariant(autoReconnectInterval(), QtType.UInt),
+    "autoReconnectRetries" to qVariant(autoReconnectRetries(), QtType.UShort),
+    "unlimitedReconnectRetries" to qVariant(unlimitedReconnectRetries(), QtType.Bool),
+    "rejoinChannels" to qVariant(rejoinChannels(), QtType.Bool),
+    "useCustomMessageRate" to qVariant(useCustomMessageRate(), QtType.Bool),
+    "msgRateBurstSize" to qVariant(messageRateBurstSize(), QtType.UInt),
+    "msgRateMessageDelay" to qVariant(messageRateDelay(), QtType.UInt),
+    "unlimitedMessageRate" to qVariant(unlimitedMessageRate(), QtType.Bool),
+    "Supports" to qVariant(
+      supports().mapValues { (_, value) -> qVariant(value, QtType.QString) },
+      QtType.QVariantMap
+    ),
+    "ServerList" to qVariant(
+      serverList().map { qVariant(it, QuasselType.NetworkServer) },
+      QtType.QVariantList
+    ),
+    "Caps" to qVariant(
+      caps().mapValues { (_, value) -> qVariant(value, QtType.QString) },
+      QtType.QVariantMap
+    ),
+    "CapsEnabled" to qVariant(
+      capsEnabled().map { qVariant(it, QtType.QString) },
+      QtType.QVariantList
+    ),
+    "IrcUsersAndChannels" to qVariant(
+      mapOf(
+        "Users" to qVariant(
+          ircUsers().map { it.toVariantMap() }.transpose(),
+          QtType.QVariantMap
+        ),
+        "Channels" to qVariant(
+          ircChannels().map { it.toVariantMap() }.transpose(),
+          QtType.QVariantMap
+        ),
+      ),
+      QtType.QVariantMap
+    )
+  )
+
+  fun me() = ircUser(myNick() ?: "")
+
+  fun networkId() = state.value.id
+  fun networkName() = state.value.networkName
+  fun isConnected() = state.value.connected
+  fun connectionState() = state.value.connectionState
+  fun currentServer() = state.value.currentServer
+  fun myNick() = state.value.myNick
+  fun latency() = state.value.latency
+  fun identity() = state.value.identity
+  fun nicks() = state.value.ircUsers.keys
+  fun channels() = state.value.ircChannels.keys
+  fun caps() = state.value.caps
+  fun capsEnabled() = state.value.capsEnabled
+  fun serverList() = state.value.serverList
+  fun useRandomServer() = state.value.useRandomServer
+  fun perform() = state.value.perform
+  fun useAutoIdentify() = state.value.useAutoIdentify
+  fun autoIdentifyService() = state.value.autoIdentifyService
+  fun autoIdentifyPassword() = state.value.autoIdentifyPassword
+  fun useSasl() = state.value.useSasl
+  fun saslAccount() = state.value.saslAccount
+  fun saslPassword() = state.value.saslPassword
+  fun useAutoReconnect() = state.value.useAutoReconnect
+  fun autoReconnectInterval() = state.value.autoReconnectInterval
+  fun autoReconnectRetries() = state.value.autoReconnectRetries
+  fun unlimitedReconnectRetries() = state.value.unlimitedReconnectRetries
+  fun rejoinChannels() = state.value.rejoinChannels
+  fun useCustomMessageRate() = state.value.useCustomMessageRate
+  fun messageRateBurstSize() = state.value.messageRateBurstSize
+  fun messageRateDelay() = state.value.messageRateDelay
+  fun unlimitedMessageRate() = state.value.unlimitedMessageRate
+  fun prefixes() = state.value.prefixes
+  fun prefixModes() = state.value.prefixModes
+  fun channelModes() = state.value.channelModes
+  fun supports() = state.value.supports
+  fun supports(key: String) =
+    state.value.supports.containsKey(key.toUpperCase(Locale.ROOT))
+
+  fun supportValue(key: String) =
+    state.value.supports[key.toUpperCase(Locale.ROOT)]
+
+  fun capAvailable(capability: String) =
+    state.value.caps.containsKey(capability.toLowerCase(Locale.ROOT))
+
+  fun capEnabled(capability: String) =
+    state.value.capsEnabled.contains(capability.toLowerCase(Locale.ROOT))
+
+  fun capValue(capability: String) =
+    state.value.caps[capability.toLowerCase(Locale.ROOT)] ?: ""
+
+  fun skipCaps() = state.value.skipCaps
+
+  fun isSaslSupportLikely(mechanism: String): Boolean {
+    if (!capAvailable(IrcCapability.SASL)) {
+      return false
+    }
+    val capValue = capValue(IrcCapability.SASL)
+    return (capValue.isBlank() || capValue.contains(mechanism, ignoreCase = true))
+  }
+
+  fun ircUser(nickName: String) =
+    state.value.ircUsers[caseMapper().toLowerCase(nickName)]
+
+  fun ircUsers() = state.value.ircUsers.values
+  fun ircUserCount() = state.value.ircUsers.size
+  fun ircChannel(name: String) =
+    state.value.ircChannels[caseMapper().toLowerCase(name)]
+
+  fun ircChannels() = state.value.ircChannels.values
+  fun ircChannelCount() = state.value.ircChannels.size
+
+  fun codecForServer() = state.value.codecForServer
+  fun codecForEncoding() = state.value.codecForEncoding
+  fun codecForDecoding() = state.value.codecForDecoding
+
+  fun caseMapper() = IrcCaseMapper[supportValue("CASEMAPPING")]
+
+  fun networkInfo() = NetworkInfo(
+    networkName = networkName(),
+    networkId = networkId(),
+    identity = identity(),
+    codecForServer = codecForServer(),
+    codecForEncoding = codecForEncoding(),
+    codecForDecoding = codecForDecoding(),
+    serverList = serverList(),
+    useRandomServer = useRandomServer(),
+    perform = perform(),
+    useAutoIdentify = useAutoIdentify(),
+    autoIdentifyService = autoIdentifyService(),
+    autoIdentifyPassword = autoIdentifyPassword(),
+    useSasl = useSasl(),
+    saslAccount = saslAccount(),
+    saslPassword = saslPassword(),
+    useAutoReconnect = useAutoReconnect(),
+    autoReconnectInterval = autoReconnectInterval(),
+    autoReconnectRetries = autoReconnectRetries(),
+    unlimitedReconnectRetries = unlimitedReconnectRetries(),
+    rejoinChannels = rejoinChannels(),
+    useCustomMessageRate = useCustomMessageRate(),
+    messageRateBurstSize = messageRateBurstSize(),
+    messageRateDelay = messageRateDelay(),
+    unlimitedMessageRate = unlimitedMessageRate()
+  )
+
+  override fun addIrcUser(hostmask: String) {
+    newIrcUser(hostmask)
+  }
+
+  override fun addIrcChannel(channel: String) {
+    newIrcChannel(channel)
+  }
+
+  fun newIrcUser(
+    hostMask: String,
+    properties: QVariantMap = emptyMap(),
+    index: Int? = null
+  ): IrcUser {
+    val nick = caseMapper().toLowerCase(HostmaskHelper.nick(hostMask))
+    val ircUser = ircUser(nick)
+    if (ircUser != null) {
+      return ircUser
+    }
+
+    val user = IrcUser(hostMask, networkId(), session)
+    user.init()
+    if (properties.isNotEmpty()) {
+      user.fromVariantMap(properties, index)
+      user.initialized = true
+    }
+    session.synchronize(user)
+    state.update {
+      copy(ircUsers = ircUsers + Pair(nick, user))
+    }
+    return user
+  }
+
+  fun newIrcChannel(
+    name: String,
+    properties: QVariantMap = emptyMap(),
+    index: Int? = null
+  ): IrcChannel {
+    val ircChannel = ircChannel(name)
+    if (ircChannel != null) {
+      return ircChannel
+    }
+
+    val channel = IrcChannel(name, networkId(), session)
+    channel.init()
+    if (properties.isNotEmpty()) {
+      channel.fromVariantMap(properties, index)
+      channel.initialized = true
+      session.synchronize(channel)
+      state.update {
+        copy(ircChannels = ircChannels + Pair(caseMapper().toLowerCase(name), channel))
+      }
+    }
+    return channel
+  }
+
+  fun removeIrcUser(user: IrcUser) {
+    state.update {
+      copy(ircUsers = ircUsers - caseMapper().toLowerCase(user.nick()))
+    }
+  }
+
+  fun removeIrcChannel(channel: IrcChannel) {
+    state.update {
+      copy(ircChannels = ircChannels - caseMapper().toLowerCase(channel.name()))
+    }
+  }
+
+  fun isMe(user: IrcUser): Boolean {
+    return caseMapper().equalsIgnoreCase(user.nick(), myNick())
+  }
+
+  fun channelModeType(mode: Char): ChannelModeType? {
+    return channelModes().entries.find {
+      it.value.contains(mode)
+    }?.key
+  }
+
+  override fun addSupport(param: String, value: String) {
+    state.update {
+      copy(
+        supports = supports + Pair(
+          caseMapper().toUpperCase(param),
+          value
+        )
+      )
+    }
+    super.addSupport(param, value)
+  }
+
+  override fun removeSupport(param: String) {
+    state.update {
+      copy(supports = supports - caseMapper().toUpperCase(param))
+    }
+    super.removeSupport(param)
+  }
+
+  override fun addCap(capability: String, value: String) {
+    state.update {
+      copy(
+        caps = caps + Pair(
+          caseMapper().toLowerCase(capability),
+          value
+        )
+      )
+    }
+    super.addCap(capability, value)
+  }
+
+  override fun acknowledgeCap(capability: String) {
+    state.update {
+      copy(capsEnabled = capsEnabled + caseMapper().toLowerCase(capability))
+    }
+    super.acknowledgeCap(capability)
+  }
+
+  override fun removeCap(capability: String) {
+    state.update {
+      copy(capsEnabled = capsEnabled - caseMapper().toLowerCase(capability))
+    }
+    super.removeCap(capability)
+  }
+
+  override fun clearCaps() {
+    state.update {
+      copy(caps = emptyMap(), capsEnabled = emptySet())
+    }
+    super.clearCaps()
+  }
+
+  override fun setSkipCaps(skipCaps: QStringList) {
+    state.update {
+      copy(skipCaps = skipCaps.filterNotNull().toSet())
+    }
+    super.setSkipCaps(skipCaps)
+  }
+
+  fun ircUserNickChanged(old: String, new: String) {
+    val oldNick = caseMapper().toLowerCase(old)
+    val newNick = caseMapper().toLowerCase(new)
+    val user = state.value.ircUsers[oldNick]
+    if (user != null) {
+      state.update {
+        copy(ircUsers = ircUsers - oldNick + Pair(newNick, user))
+      }
+    }
+  }
+
+  private fun determineChannelModeTypes(): Map<ChannelModeType, Set<Char>> {
+    return ChannelModeType.values()
+      .zip(
+        supportValue("CHANMODES")
+          ?.split(',', limit = ChannelModeType.values().size)
+          ?.map(String::toSet)
+          .orEmpty()
+      )
+      .toMap()
+  }
+
+  private fun determinePrefixes(): Pair<List<Char>, List<Char>> {
+    val defaultPrefixes = listOf('~', '&', '@', '%', '+')
+    val defaultPrefixModes = listOf('q', 'a', 'o', 'h', 'v')
+
+    val prefix = supportValue("PREFIX")
+      ?: return Pair(defaultPrefixes, defaultPrefixModes)
+
+    if (prefix.startsWith("(") && prefix.contains(")")) {
+      val (prefixModes, prefixes) = prefix.substringAfter('(')
+        .split(')', limit = 2)
+        .map(String::toList)
+
+      return Pair(prefixModes, prefixes)
+    } else if (prefix.isBlank()) {
+      return Pair(defaultPrefixes, defaultPrefixModes)
+    } else if ((prefix.toSet() intersect defaultPrefixes.toSet()).isNotEmpty()) {
+      val (prefixes, prefixModes) = defaultPrefixes.zip(defaultPrefixModes)
+        .filter { prefix.contains(it.second) }
+        .unzip()
+
+      return Pair(prefixModes, prefixes)
+    } else if ((prefix.toSet() intersect defaultPrefixModes.toSet()).isNotEmpty()) {
+      val (prefixes, prefixModes) = defaultPrefixes.zip(defaultPrefixModes)
+        .filter { prefix.contains(it.first) }
+        .unzip()
+
+      return Pair(prefixModes, prefixes)
+    }
+
+    return Pair(defaultPrefixes, defaultPrefixModes)
+  }
+
+  override fun setIdentity(identityId: IdentityId) {
+    state.update {
+      copy(identity = identity)
+    }
+    super.setIdentity(identityId)
+  }
+
+  override fun setMyNick(myNick: String) {
+    state.update {
+      copy(myNick = myNick)
+    }
+    super.setMyNick(myNick)
+  }
+
+  override fun setLatency(latency: Int) {
+    state.update {
+      copy(latency = latency)
+    }
+    super.setLatency(latency)
+  }
+
+  override fun setNetworkName(networkName: String) {
+    state.update {
+      copy(networkName = networkName)
+    }
+    super.setNetworkName(networkName)
+  }
+
+  override fun setCurrentServer(currentServer: String) {
+    state.update {
+      copy(currentServer = currentServer)
+    }
+    super.setCurrentServer(currentServer)
+  }
+
+  override fun setConnected(isConnected: Boolean) {
+    state.update {
+      if (isConnected) {
+        copy(connected = true)
+      } else {
+        ircChannels.values.forEach(session::stopSynchronize)
+        ircUsers.values.forEach(session::stopSynchronize)
+        copy(
+          connected = isConnected,
+          myNick = "",
+          currentServer = "",
+          ircChannels = emptyMap(),
+          ircUsers = emptyMap()
+        )
+      }
+    }
+    super.setConnected(isConnected)
+  }
+
+  override fun setConnectionState(connectionState: Int) {
+    state.update {
+      copy(
+        connectionState = ConnectionState.of(connectionState)
+          ?: ConnectionState.Disconnected
+      )
+    }
+    super.setConnectionState(connectionState)
+  }
+
+  override fun setServerList(serverList: QVariantList) {
+    state.update {
+      copy(
+        serverList = serverList.mapNotNull {
+          it.into<NetworkServer>()
+        }
+      )
+    }
+    super.setServerList(serverList)
+  }
+
+  override fun setUseRandomServer(useRandomServer: Boolean) {
+    state.update {
+      copy(useRandomServer = useRandomServer)
+    }
+    super.setUseRandomServer(useRandomServer)
+  }
+
+  override fun setPerform(perform: QStringList) {
+    state.update {
+      copy(perform = perform.map { it ?: "" })
+    }
+    super.setPerform(perform)
+  }
+
+  override fun setUseAutoIdentify(useAutoIdentify: Boolean) {
+    state.update {
+      copy(useAutoIdentify = useAutoIdentify)
+    }
+    super.setUseAutoIdentify(useAutoIdentify)
+  }
+
+  override fun setAutoIdentifyPassword(autoIdentifyPassword: String) {
+    state.update {
+      copy(autoIdentifyPassword = autoIdentifyPassword)
+    }
+    super.setAutoIdentifyPassword(autoIdentifyPassword)
+  }
+
+  override fun setAutoIdentifyService(autoIdentifyService: String) {
+    state.update {
+      copy(autoIdentifyService = autoIdentifyService)
+    }
+    super.setAutoIdentifyService(autoIdentifyService)
+  }
+
+  override fun setUseSasl(useSasl: Boolean) {
+    state.update {
+      copy(useSasl = useSasl)
+    }
+    super.setUseSasl(useSasl)
+  }
+
+  override fun setSaslAccount(saslAccount: String) {
+    state.update {
+      copy(saslAccount = saslAccount)
+    }
+    super.setSaslAccount(saslAccount)
+  }
+
+  override fun setSaslPassword(saslPassword: String) {
+    state.update {
+      copy(saslPassword = saslPassword)
+    }
+    super.setSaslPassword(saslPassword)
+  }
+
+  override fun setUseAutoReconnect(useAutoReconnect: Boolean) {
+    state.update {
+      copy(useAutoReconnect = useAutoReconnect)
+    }
+    super.setUseAutoReconnect(useAutoReconnect)
+  }
+
+  override fun setAutoReconnectInterval(autoReconnectInterval: UInt) {
+    state.update {
+      copy(autoReconnectInterval = autoReconnectInterval)
+    }
+    super.setAutoReconnectInterval(autoReconnectInterval)
+  }
+
+  override fun setAutoReconnectRetries(autoReconnectRetries: UShort) {
+    state.update {
+      copy(autoReconnectRetries = autoReconnectRetries)
+    }
+    super.setAutoReconnectRetries(autoReconnectRetries)
+  }
+
+  override fun setUnlimitedReconnectRetries(unlimitedReconnectRetries: Boolean) {
+    state.update {
+      copy(unlimitedReconnectRetries = unlimitedReconnectRetries)
+    }
+    super.setUnlimitedReconnectRetries(unlimitedReconnectRetries)
+  }
+
+  override fun setRejoinChannels(rejoinChannels: Boolean) {
+    state.update {
+      copy(rejoinChannels = rejoinChannels)
+    }
+    super.setRejoinChannels(rejoinChannels)
+  }
+
+  override fun setUseCustomMessageRate(useCustomMessageRate: Boolean) {
+    state.update {
+      copy(useCustomMessageRate = useCustomMessageRate)
+    }
+    super.setUseCustomMessageRate(useCustomMessageRate)
+  }
+
+  override fun setMessageRateBurstSize(messageRateBurstSize: UInt) {
+    state.update {
+      copy(messageRateBurstSize = messageRateBurstSize)
+    }
+    super.setMessageRateBurstSize(messageRateBurstSize)
+  }
+
+  override fun setMessageRateDelay(messageRateDelay: UInt) {
+    state.update {
+      copy(messageRateDelay = messageRateDelay)
+    }
+    super.setMessageRateDelay(messageRateDelay)
+  }
+
+  override fun setUnlimitedMessageRate(unlimitedMessageRate: Boolean) {
+    state.update {
+      copy(unlimitedMessageRate = unlimitedMessageRate)
+    }
+    super.setUnlimitedMessageRate(unlimitedMessageRate)
+  }
+
+  override fun setCodecForServer(codecForServer: ByteBuffer) {
+    state.update {
+      copy(codecForServer = StringSerializerUtf8.deserializeRaw(codecForServer))
+    }
+    super.setCodecForServer(codecForServer)
+  }
+
+  override fun setCodecForEncoding(codecForEncoding: ByteBuffer) {
+    state.update {
+      copy(codecForEncoding = StringSerializerUtf8.deserializeRaw(codecForEncoding))
+    }
+    super.setCodecForEncoding(codecForEncoding)
+  }
+
+  override fun setCodecForDecoding(codecForDecoding: ByteBuffer) {
+    state.update {
+      copy(codecForDecoding = StringSerializerUtf8.deserializeRaw(codecForDecoding))
+    }
+    super.setCodecForDecoding(codecForDecoding)
+  }
+
+  private val state = MutableStateFlow(
+    NetworkState(
+      id = networkId
+    )
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
index 6797cdfc39f5163c6c39eb3fb8514ebc4874828b..75fd70a06f9a62ed99a562b940b763248c8ae459 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
@@ -15,7 +15,6 @@ import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.syncables.stubs.IdentityStub
-import de.justjanne.libquassel.protocol.syncables.stubs.NetworkStub
 import de.justjanne.libquassel.protocol.syncables.stubs.RpcHandlerStub
 import de.justjanne.libquassel.protocol.variant.QVariantList
 
@@ -24,9 +23,12 @@ interface Session : RpcHandlerStub {
 
   val objectRepository: ObjectRepository
 
-  fun network(id: NetworkId): NetworkStub
+  fun network(id: NetworkId): Network?
   fun identity(id: IdentityId): IdentityStub
 
+  fun synchronize(it: SyncableObject)
+  fun stopSynchronize(it: SyncableObject)
+
   fun sync(
     target: ProtocolSide,
     className: String,
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt
index 8747579c8e45f48750de4dcb82ef8392c514aec4..f3e994ad3f70ed400c327963df52a6452be5b315 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt
@@ -38,11 +38,6 @@ abstract class SyncableObject(
     }
   }
 
-  override fun init() {
-    initialized = true
-  }
-
-  override fun deinit() {
-    initialized = false
-  }
+  override fun init() = Unit
+  override fun deinit() = Unit
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt
index f3e4632c17bfb7906275ece2f0c0c23a76bdb37d..1352dcc677570795d700bef93583a90f41feee2e 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt
@@ -45,9 +45,6 @@ interface SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "update",
-      /**
-       * Construct a QVariant from a QVariantMap
-       */
       qVariant(properties, QtType.QVariantMap)
     )
   }
@@ -56,9 +53,6 @@ interface SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestUpdate",
-      /**
-       * Construct a QVariant from a QVariantMap
-       */
       qVariant(properties, QtType.QVariantMap)
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..89a1d7c8c57e6f5b9168283079f6688eb485bca8
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.models.Alias
+
+data class AliasManagerState(
+  val aliases: List<Alias> = emptyList()
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db675e335cfbd6e6895eb398785e7c8d97df163b
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt
@@ -0,0 +1,24 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.models.ChannelModes
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+
+data class IrcChannelState(
+  val network: NetworkId,
+  val name: String,
+  val topic: String = "",
+  val password: String = "",
+  val encrypted: Boolean = false,
+  val channelModes: ChannelModes = ChannelModes(),
+  val userModes: Map<String, Set<Char>> = emptyMap()
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcUserState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcUserState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5e6e2ddee24767fb9ec494e43d50d2f3b93a7d6b
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcUserState.kt
@@ -0,0 +1,35 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import org.threeten.bp.Instant
+
+data class IrcUserState(
+  val network: NetworkId,
+  val nick: String,
+  val user: String,
+  val host: String,
+  val realName: String = "",
+  val account: String = "",
+  val away: Boolean = false,
+  val awayMessage: String = "",
+  val idleTime: Instant = Instant.EPOCH,
+  val loginTime: Instant = Instant.EPOCH,
+  val server: String = "",
+  val ircOperator: String = "",
+  val lastAwayMessageTime: Instant = Instant.EPOCH,
+  val whoisServiceReply: String = "",
+  val suserHost: String = "",
+  val encrypted: Boolean = false,
+  val channels: Set<String> = emptySet(),
+  val userModes: Set<Char> = emptySet()
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..48311799b6602f730b4beaf31ee033d60f00b362
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
@@ -0,0 +1,60 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.models.ChannelModeType
+import de.justjanne.libquassel.protocol.models.ConnectionState
+import de.justjanne.libquassel.protocol.models.NetworkServer
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.IrcUser
+
+data class NetworkState(
+  val id: NetworkId,
+  val identity: IdentityId = IdentityId(-1),
+  val myNick: String? = "",
+  val latency: Int = 0,
+  val networkName: String = "<not initialized>",
+  val currentServer: String = "",
+  val connected: Boolean = false,
+  val connectionState: ConnectionState = ConnectionState.Disconnected,
+  val prefixes: List<Char> = emptyList(),
+  val prefixModes: List<Char> = emptyList(),
+  val channelModes: Map<ChannelModeType, Set<Char>> = emptyMap(),
+  val ircUsers: Map<String, IrcUser> = emptyMap(),
+  val ircChannels: Map<String, IrcChannel> = emptyMap(),
+  val supports: Map<String, String?> = emptyMap(),
+  val caps: Map<String, String?> = emptyMap(),
+  val capsEnabled: Set<String> = emptySet(),
+  val skipCaps: Set<String> = emptySet(),
+  val serverList: List<NetworkServer> = emptyList(),
+  val useRandomServer: Boolean = false,
+  val perform: List<String> = emptyList(),
+  val useAutoIdentify: Boolean = false,
+  val autoIdentifyService: String = "",
+  val autoIdentifyPassword: String = "",
+  val useSasl: Boolean = false,
+  val saslAccount: String = "",
+  val saslPassword: String = "",
+  val useAutoReconnect: Boolean = false,
+  val autoReconnectInterval: UInt = 60u,
+  val autoReconnectRetries: UShort = 10u,
+  val unlimitedReconnectRetries: Boolean = false,
+  val rejoinChannels: Boolean = false,
+  val useCustomMessageRate: Boolean = false,
+  val messageRateBurstSize: UInt = 5u,
+  val messageRateDelay: UInt = 2200u,
+  val unlimitedMessageRate: Boolean = false,
+  val codecForServer: String = "UTF_8",
+  val codecForEncoding: String = "UTF_8",
+  val codecForDecoding: String = "UTF_8"
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt
index 5fc623d98e69525a997fb986c7c13435d9c63f1b..d4b4899151d84268e2761e381c8e338c16ba73c4 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt
@@ -25,13 +25,7 @@ interface AliasManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "addAlias",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(name, QtType.QString),
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(expansion, QtType.QString)
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt
index 4045d4322db7f7628c22dba4291192eb6742fe61..77656fb2fd8c2d17a966a0d0da57aa5cf0eae3b6 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt
@@ -33,25 +33,10 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestBacklog",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(bufferId, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
     )
   }
@@ -69,33 +54,12 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestBacklogFiltered",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(bufferId, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(type, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(flags, QtType.Int),
     )
   }
@@ -110,21 +74,9 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestBacklogAll",
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
     )
   }
@@ -141,29 +93,11 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestBacklogAll",
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(type, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(flags, QtType.Int),
     )
   }
@@ -179,25 +113,10 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "receiveBacklog",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(bufferId, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
     )
   }
@@ -215,33 +134,12 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "receiveBacklogFiltered",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(bufferId, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(type, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(flags, QtType.Int),
     )
   }
@@ -256,21 +154,9 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "receiveBacklogAll",
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
     )
   }
@@ -287,29 +173,11 @@ interface BacklogManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "receiveBacklogAllFiltered",
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(first, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(last, QuasselType.MsgId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(additional, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(type, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(flags, QtType.Int),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt
index b3b32e6efc31e33f2cbf973ba27a5a70cd68a057..d61c3fa7848cb68c1e00547c9b5b6e028f6865cb 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt
@@ -28,9 +28,6 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "markBufferAsRead",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -40,9 +37,6 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestMarkBufferAsRead",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -52,13 +46,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "mergeBuffersPermanently",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer2, QuasselType.BufferId),
     )
   }
@@ -68,13 +56,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestMergeBuffersPermanently",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer2, QuasselType.BufferId),
     )
   }
@@ -84,9 +66,6 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -96,9 +75,6 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRemoveBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -108,13 +84,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "renameBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(newName, QtType.QString),
     )
   }
@@ -124,13 +94,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRenameBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(newName, QtType.QString),
     )
   }
@@ -140,13 +104,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMarkerLine",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(msgId, QuasselType.MsgId),
     )
   }
@@ -156,13 +114,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetLastSeenMsg",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(msgId, QuasselType.MsgId),
     )
   }
@@ -172,13 +124,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setLastSeenMsg",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(msgId, QuasselType.MsgId),
     )
   }
@@ -188,13 +134,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetMarkerLine",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a MsgId
-       */
       qVariant(msgId, QuasselType.MsgId),
     )
   }
@@ -204,13 +144,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setBufferActivity",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(count, QtType.Int),
     )
   }
@@ -220,13 +154,7 @@ interface BufferSyncerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setHighlightCount",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(count, QtType.Int),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt
index c568aaf7fbdf6ba1eba398c28df4bef37f8e2f36..e2c9a18520698786a7a39fe7c939c888917f6052 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt
@@ -28,13 +28,7 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(pos, QtType.Int),
     )
   }
@@ -44,13 +38,7 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestAddBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(pos, QtType.Int),
     )
   }
@@ -60,13 +48,7 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "moveBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(pos, QtType.Int),
     )
   }
@@ -76,13 +58,7 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestMoveBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(pos, QtType.Int),
     )
   }
@@ -92,9 +68,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -104,9 +77,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRemoveBuffer",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -116,9 +86,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeBufferPermanently",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -128,9 +95,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRemoveBufferPermanently",
-      /**
-       * Construct a QVariant from a BufferId
-       */
       qVariant(buffer, QuasselType.BufferId),
     )
   }
@@ -140,9 +104,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setBufferViewName",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(value, QtType.QString),
     )
   }
@@ -152,9 +113,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetBufferViewName",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(value, QtType.QString),
     )
   }
@@ -164,9 +122,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAddNewBuffersAutomatically",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(value, QtType.Bool),
     )
   }
@@ -176,9 +131,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAllowedBufferTypes",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(value, QtType.Int),
     )
   }
@@ -188,9 +140,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setDisableDecoration",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(value, QtType.Bool),
     )
   }
@@ -200,9 +149,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setHideInactiveBuffers",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(value, QtType.Bool),
     )
   }
@@ -212,9 +158,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setHideInactiveNetworks",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(value, QtType.Bool),
     )
   }
@@ -224,9 +167,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMinimumActivity",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(value, QtType.Int),
     )
   }
@@ -236,9 +176,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setNetworkId",
-      /**
-       * Construct a QVariant from a Message
-       */
       qVariant(value, QuasselType.NetworkId),
     )
   }
@@ -248,9 +185,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setShowSearch",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(value, QtType.Bool),
     )
   }
@@ -260,9 +194,6 @@ interface BufferViewConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSortAlphabetically",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(value, QtType.Bool),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt
index efe87dccfe7ab9349766a2fecbba4c33bde978d2..68bc372605a6cb0656e05c3217576232c701d0f2 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt
@@ -26,9 +26,6 @@ interface BufferViewManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addBufferViewConfig",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(bufferViewConfigId, QtType.Int),
     )
   }
@@ -43,9 +40,6 @@ interface BufferViewManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestCreateBufferView",
-      /**
-       * Construct a QVariant from a QVariantMap
-       */
       qVariant(properties, QtType.QVariantMap),
     )
   }
@@ -55,9 +49,6 @@ interface BufferViewManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestCreateBufferViews",
-      /**
-       * Construct a QVariant from a QVariantList
-       */
       qVariant(properties, QtType.QVariantList),
     )
   }
@@ -67,9 +58,6 @@ interface BufferViewManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "deleteBufferViewConfig",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(bufferViewConfigId, QtType.Int),
     )
   }
@@ -79,9 +67,6 @@ interface BufferViewManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestDeleteBufferView",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(bufferViewConfigId, QtType.Int),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt
index b7f06126b38db7ccde68565b325b4f3c9072a822..a3d98cd0c9d8bf48074c7789a99576c64e0de1e7 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt
@@ -26,9 +26,6 @@ interface CertManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSslCert",
-      /**
-       * Construct a QVariant from a ByteBuffer
-       */
       qVariant(encoded, QtType.QByteArray),
     )
   }
@@ -38,9 +35,6 @@ interface CertManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSslKey",
-      /**
-       * Construct a QVariant from a ByteBuffer
-       */
       qVariant(encoded, QtType.QByteArray),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt
index bc4f3ca1a62dfe54e7c5ae28afd07cdb18ab56fc..3400dce3e54f9cde38443d1f8b185dac55ea4e06 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt
@@ -25,9 +25,6 @@ interface CoreInfoStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setCoreData",
-      /**
-       * Construct a QVariant from a QVariantMap
-       */
       qVariant(data, QtType.QVariantMap),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt
index 6a1a5e4d0474ac041998577109505a4c346c8290..f9bb09040bf5ebcfa45fcaea9bdf837d456b890f 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt
@@ -29,9 +29,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setDccEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -41,9 +38,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setOutgoingIp",
-      /**
-       * Construct a QVariant from a InetAddress
-       */
       qVariant(outgoingIp, QuasselType.QHostAddress),
     )
   }
@@ -53,9 +47,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setIpDetectionMode",
-      /**
-       * Construct a QVariant from a DccIpDetectionMode
-       */
       qVariant(ipDetectionMode, QuasselType.DccConfigIpDetectionMode),
     )
   }
@@ -65,9 +56,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setPortSelectionMode",
-      /**
-       * Construct a QVariant from a DccPortSelectionMode
-       */
       qVariant(portSelectionMode, QuasselType.DccConfigPortSelectionMode),
     )
   }
@@ -77,9 +65,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMinPort",
-      /**
-       * Construct a QVariant from a UShort
-       */
       qVariant(port, QtType.UShort),
     )
   }
@@ -89,9 +74,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMaxPort",
-      /**
-       * Construct a QVariant from a UShort
-       */
       qVariant(port, QtType.UShort),
     )
   }
@@ -101,9 +83,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setChunkSize",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(chunkSize, QtType.Int),
     )
   }
@@ -113,9 +92,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSendTimeout",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(timeout, QtType.Int),
     )
   }
@@ -125,9 +101,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUsePassiveDcc",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(use, QtType.Bool),
     )
   }
@@ -137,9 +110,6 @@ interface DccConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUseFastSend",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(use, QtType.Bool),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt
index 865a8cffa27a1fc7bcd24f0eb0d812e8981c93b4..c81cc4eae63164a6e17bef06160da04be407d9fd 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt
@@ -25,9 +25,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRemoveHighlightRule",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(highlightRule, QtType.Int),
     )
   }
@@ -37,9 +34,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeHighlightRule",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(highlightRule, QtType.Int),
     )
   }
@@ -49,9 +43,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestToggleHighlightRule",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(highlightRule, QtType.Int),
     )
   }
@@ -61,9 +52,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "toggleHighlightRule",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(highlightRule, QtType.Int),
     )
   }
@@ -82,37 +70,13 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestToggleHighlightRule",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(id, QtType.Int),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(name, QtType.QString),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isRegEx, QtType.Bool),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isCaseSensitive, QtType.Bool),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isEnabled, QtType.Bool),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isInverse, QtType.Bool),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(sender, QtType.QString),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(chanName, QtType.QString),
     )
   }
@@ -131,37 +95,13 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addHighlightRule",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(id, QtType.Int),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(name, QtType.QString),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isRegEx, QtType.Bool),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isCaseSensitive, QtType.Bool),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isEnabled, QtType.Bool),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isInverse, QtType.Bool),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(sender, QtType.QString),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(chanName, QtType.QString),
     )
   }
@@ -171,9 +111,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetHighlightNick",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(highlightNick, QtType.Int),
     )
   }
@@ -183,9 +120,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setHighlightNick",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(highlightNick, QtType.Int),
     )
   }
@@ -195,9 +129,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetNicksCaseSensitive",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(nicksCaseSensitive, QtType.Bool),
     )
   }
@@ -207,9 +138,6 @@ interface HighlightRuleManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setNicksCaseSensitive",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(nicksCaseSensitive, QtType.Bool),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt
index 70fcc96d60456c63891388e36598ae49daf1b586..8e96c27119d891090b9cd879a4bbf4657011f4e4 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt
@@ -28,9 +28,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoAwayEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -40,9 +37,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoAwayReason",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(reason, QtType.QString),
     )
   }
@@ -52,9 +46,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoAwayReasonEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -64,9 +55,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoAwayTime",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(time, QtType.Int),
     )
   }
@@ -76,9 +64,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAwayNick",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(awayNick, QtType.QString),
     )
   }
@@ -88,9 +73,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAwayNickEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -100,9 +82,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAwayReason",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(awayReason, QtType.QString),
     )
   }
@@ -112,9 +91,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAwayReasonEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -124,9 +100,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setDetachAwayEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -136,9 +109,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setDetachAwayReason",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(reason, QtType.QString),
     )
   }
@@ -148,9 +118,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setDetachAwayReasonEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool),
     )
   }
@@ -160,9 +127,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setId",
-      /**
-       * Construct a QVariant from a IdentityId
-       */
       qVariant(id, QuasselType.IdentityId),
     )
   }
@@ -172,9 +136,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setIdent",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ident, QtType.QString),
     )
   }
@@ -184,9 +145,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setIdentityName",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(name, QtType.QString),
     )
   }
@@ -196,9 +154,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setKickReason",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(reason, QtType.QString),
     )
   }
@@ -208,9 +163,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setNicks",
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(nicks, QtType.QStringList),
     )
   }
@@ -220,9 +172,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setPartReason",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(reason, QtType.QString),
     )
   }
@@ -232,9 +181,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setQuitReason",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(reason, QtType.QString),
     )
   }
@@ -244,9 +190,6 @@ interface IdentityStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setRealName",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(realName, QtType.QString),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt
index bb5dd39bb1fa8ca9110645b8450ae1d4b47c0995..4aa64d0df04da810a13ffd38a8b1e28413621734 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt
@@ -33,33 +33,12 @@ interface IgnoreListManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addIgnoreListItem",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(type, QtType.Int),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ignoreRule, QtType.QString),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isRegEx, QtType.Bool),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(strictness, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(scope, QtType.Int),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(scopeRule, QtType.QString),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isActive, QtType.Bool),
     )
   }
@@ -69,9 +48,6 @@ interface IgnoreListManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeIgnoreListItem",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ignoreRule, QtType.QString),
     )
   }
@@ -89,33 +65,12 @@ interface IgnoreListManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "requestAddIgnoreListItem",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(type, QtType.Int),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ignoreRule, QtType.QString),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isRegEx, QtType.Bool),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(strictness, QtType.Int),
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(scope, QtType.Int),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(scopeRule, QtType.QString),
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isActive, QtType.Bool),
     )
   }
@@ -125,9 +80,6 @@ interface IgnoreListManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRemoveIgnoreListItem",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ignoreRule, QtType.QString),
     )
   }
@@ -137,9 +89,6 @@ interface IgnoreListManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestToggleIgnoreRule",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ignoreRule, QtType.QString),
     )
   }
@@ -149,9 +98,6 @@ interface IgnoreListManagerStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "requestToggleIgnoreRule",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ignoreRule, QtType.QString),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt
index cdd56fbad429b93fe3384221628750745d2f6ea4..7dce7c7bbe190ca99aef1b438883e3dc15aa813e 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt
@@ -26,29 +26,17 @@ interface IrcChannelStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addChannelMode",
-      /**
-       * Construct a QVariant from a Char
-       */
       qVariant(mode, QtType.QChar),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(value, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun addUserMode(nick: String?, mode: String? = null) {
+  fun addUserMode(nick: String, mode: String? = null) {
     sync(
       target = ProtocolSide.CLIENT,
       "addUserMode",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(nick, QtType.QString),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(mode, QtType.QString),
     )
   }
@@ -58,25 +46,16 @@ interface IrcChannelStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "joinIrcUsers",
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(nicks, QtType.QStringList),
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(modes, QtType.QStringList),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun part(nick: String?) {
+  fun part(nick: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "part",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(nick, QtType.QString),
     )
   }
@@ -86,29 +65,17 @@ interface IrcChannelStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeChannelMode",
-      /**
-       * Construct a QVariant from a Char
-       */
       qVariant(mode, QtType.QChar),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(value, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun removeUserMode(nick: String?, mode: String? = null) {
+  fun removeUserMode(nick: String, mode: String? = null) {
     sync(
       target = ProtocolSide.CLIENT,
       "removeUserMode",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(nick, QtType.QString),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(mode, QtType.QString),
     )
   }
@@ -118,49 +85,34 @@ interface IrcChannelStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setEncrypted",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(encrypted, QtType.Bool),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setPassword(password: String?) {
+  fun setPassword(password: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setPassword",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(password, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setTopic(topic: String?) {
+  fun setTopic(topic: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setTopic",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(topic, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setUserModes(nick: String?, modes: String? = null) {
+  fun setUserModes(nick: String, modes: String? = null) {
     sync(
       target = ProtocolSide.CLIENT,
       "setUserModes",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(nick, QtType.QString),
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(modes, QtType.QString),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcListHelperStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcListHelperStub.kt
index 37aeaab186af1d045fbaf797ed6ad6a09b02acce..4d8d4aa8f5f9baec23367b5229889fcf1f05d367 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcListHelperStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcListHelperStub.kt
@@ -28,13 +28,7 @@ interface IrcListHelperStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestChannelList",
-      /**
-       * Construct a QVariant from a Message
-       */
       qVariant(netId, QuasselType.NetworkId),
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(channelFilters, QtType.QStringList),
     )
     return emptyList()
@@ -45,17 +39,8 @@ interface IrcListHelperStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "receiveChannelList",
-      /**
-       * Construct a QVariant from a Message
-       */
       qVariant(netId, QuasselType.NetworkId),
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(channelFilters, QtType.QStringList),
-      /**
-       * Construct a QVariant from a QVariantList
-       */
       qVariant(channels, QtType.QVariantList),
     )
   }
@@ -65,9 +50,6 @@ interface IrcListHelperStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "reportError",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(error, QtType.QString),
     )
   }
@@ -77,9 +59,6 @@ interface IrcListHelperStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "reportFinishedList",
-      /**
-       * Construct a QVariant from a Message
-       */
       qVariant(netId, QuasselType.NetworkId),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt
index 1781bed48665e506f38f0499e68043d86cdb473f..20a4c05ef396f88e32e3f563e843760fc0e5ff23 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt
@@ -23,37 +23,28 @@ import org.threeten.bp.temporal.Temporal
 interface IrcUserStub : SyncableStub {
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun addUserModes(modes: String?) {
+  fun addUserModes(modes: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "addUserModes",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(modes, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun joinChannel(channelname: String?) {
+  fun joinChannel(channelname: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "joinChannel",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(channelname, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun partChannel(channelname: String?) {
+  fun partChannel(channelname: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "partChannel",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(channelname, QtType.QString),
     )
   }
@@ -67,25 +58,19 @@ interface IrcUserStub : SyncableStub {
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun removeUserModes(modes: String?) {
+  fun removeUserModes(modes: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "removeUserModes",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(modes, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setAccount(account: String?) {
+  fun setAccount(account: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setAccount",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(account, QtType.QString),
     )
   }
@@ -95,21 +80,15 @@ interface IrcUserStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAway",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(away, QtType.Bool),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setAwayMessage(awayMessage: String?) {
+  fun setAwayMessage(awayMessage: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setAwayMessage",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(awayMessage, QtType.QString),
     )
   }
@@ -119,21 +98,15 @@ interface IrcUserStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setEncrypted",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(encrypted, QtType.Bool),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setHost(host: String?) {
+  fun setHost(host: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setHost",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(host, QtType.QString),
     )
   }
@@ -143,21 +116,15 @@ interface IrcUserStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setIdleTime",
-      /**
-       * Construct a QVariant from a Temporal object
-       */
       qVariant(idleTime, QtType.QDateTime),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setIrcOperator(ircOperator: String?) {
+  fun setIrcOperator(ircOperator: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setIrcOperator",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(ircOperator, QtType.QString),
     )
   }
@@ -167,9 +134,6 @@ interface IrcUserStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setLastAwayMessage",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(lastAwayMessage, QtType.Int),
     )
   }
@@ -178,10 +142,7 @@ interface IrcUserStub : SyncableStub {
   fun setLastAwayMessageTime(lastAwayMessageTime: Temporal) {
     sync(
       target = ProtocolSide.CLIENT,
-      "setLastAwayMessageTIme",
-      /**
-       * Construct a QVariant from a Temporal object
-       */
+      "setLastAwayMessageTime",
       qVariant(lastAwayMessageTime, QtType.QDateTime),
     )
   }
@@ -191,105 +152,78 @@ interface IrcUserStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setLoginTime",
-      /**
-       * Construct a QVariant from a Temporal object
-       */
       qVariant(loginTime, QtType.QDateTime),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setNick(nick: String?) {
+  fun setNick(nick: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setNick",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(nick, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setRealName(realName: String?) {
+  fun setRealName(realName: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setRealName",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(realName, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setServer(server: String?) {
+  fun setServer(server: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setServer",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(server, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setSuserHost(suserHost: String?) {
+  fun setSuserHost(suserHost: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setSuserHost",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(suserHost, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setUser(user: String?) {
+  fun setUser(user: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setUser",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(user, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setUserModes(modes: String?) {
+  fun setUserModes(modes: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setUserModes",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(modes, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun setWhoisServiceReply(whoisServiceReply: String?) {
+  fun setWhoisServiceReply(whoisServiceReply: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "setWhoisServiceReply",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(whoisServiceReply, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun updateHostmask(mask: String?) {
+  fun updateHostmask(mask: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "updateHostmask",
-      /**
-       * Construct a QVariant from a String?
-       */
       qVariant(mask, QtType.QString),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt
index d54c8da438bdc0719ade6644d65eeac1be91f03a..2c3ba3d65293785f55dcdbeec4b13e2535a0b122 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt
@@ -25,9 +25,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetAutoWhoDelay",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(delay, QtType.Int)
     )
   }
@@ -37,9 +34,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoWhoDelay",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(delay, QtType.Int)
     )
   }
@@ -49,9 +43,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetAutoWhoEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool)
     )
   }
@@ -61,9 +52,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoWhoEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool)
     )
   }
@@ -73,9 +61,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetAutoWhoInterval",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(interval, QtType.Int)
     )
   }
@@ -85,9 +70,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoWhoInterval",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(interval, QtType.Int)
     )
   }
@@ -97,9 +79,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetAutoWhoNickLimit",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int)
     )
   }
@@ -109,9 +88,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "setAutoWhoNickLimit",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(limit, QtType.Int)
     )
   }
@@ -121,9 +97,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetMaxPingCount",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(count, QtType.Int)
     )
   }
@@ -133,9 +106,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "setMaxPingCount",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(count, QtType.Int)
     )
   }
@@ -145,9 +115,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetPingInterval",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(interval, QtType.Int)
     )
   }
@@ -157,9 +124,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "setPingInterval",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(interval, QtType.Int)
     )
   }
@@ -169,9 +133,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetPingTimeoutEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool)
     )
   }
@@ -181,9 +142,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "setPingTimeoutEnabled",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool)
     )
   }
@@ -193,9 +151,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestSetStandardCtcp",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool)
     )
   }
@@ -205,9 +160,6 @@ interface NetworkConfigStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "setStandardCtcp",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(enabled, QtType.Bool)
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt
index b32bce88eac48a979d967cfbbc706a931d9b8998..e1e01161663a6216bb038ed2e52dde7f1d8b7935 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt
@@ -31,9 +31,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setNetworkName",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(networkName, QtType.QString),
     )
   }
@@ -43,9 +40,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setCurrentServer",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(currentServer, QtType.QString),
     )
   }
@@ -55,9 +49,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMyNick",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(myNick, QtType.QString),
     )
   }
@@ -67,9 +58,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setLatency",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(latency, QtType.Int),
     )
   }
@@ -79,9 +67,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setCodecForServer",
-      /**
-       * Construct a QVariant from a ByteBuffer
-       */
       qVariant(codecForServer, QtType.QByteArray),
     )
   }
@@ -91,9 +76,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setCodecForEncoding",
-      /**
-       * Construct a QVariant from a ByteBuffer
-       */
       qVariant(codecForEncoding, QtType.QByteArray),
     )
   }
@@ -103,9 +85,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setCodecForDecoding",
-      /**
-       * Construct a QVariant from a ByteBuffer
-       */
       qVariant(codecForDecoding, QtType.QByteArray),
     )
   }
@@ -115,9 +94,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setIdentity",
-      /**
-       * Construct a QVariant from a IdentityId
-       */
       qVariant(identityId, QuasselType.IdentityId),
     )
   }
@@ -127,9 +103,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setConnected",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(isConnected, QtType.Bool),
     )
   }
@@ -139,9 +112,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setConnectionState",
-      /**
-       * Construct a QVariant from a Int
-       */
       qVariant(connectionState, QtType.Int),
     )
   }
@@ -151,9 +121,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUseRandomServer",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(useRandomServer, QtType.Bool),
     )
   }
@@ -163,9 +130,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setPerform",
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(perform, QtType.QStringList),
     )
   }
@@ -175,9 +139,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSkipCaps",
-      /**
-       * Construct a QVariant from a QStringList
-       */
       qVariant(skipCaps, QtType.QStringList),
     )
   }
@@ -187,9 +148,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUseAutoIdentify",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(useAutoIdentify, QtType.Bool),
     )
   }
@@ -199,9 +157,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoIdentifyService",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(autoIdentifyService, QtType.QString),
     )
   }
@@ -211,9 +166,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoIdentifyPassword",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(autoIdentifyPassword, QtType.QString),
     )
   }
@@ -223,9 +175,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUseSasl",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(useSasl, QtType.Bool),
     )
   }
@@ -235,9 +184,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSaslAccount",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(saslAccount, QtType.QString),
     )
   }
@@ -247,9 +193,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setSaslPassword",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(saslPassword, QtType.QString),
     )
   }
@@ -259,9 +202,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUseAutoReconnect",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(useAutoReconnect, QtType.Bool),
     )
   }
@@ -271,9 +211,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoReconnectInterval",
-      /**
-       * Construct a QVariant from a UInt
-       */
       qVariant(autoReconnectInterval, QtType.UInt),
     )
   }
@@ -283,9 +220,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAutoReconnectRetries",
-      /**
-       * Construct a QVariant from a UShort
-       */
       qVariant(autoReconnectRetries, QtType.UShort),
     )
   }
@@ -295,9 +229,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUnlimitedReconnectRetries",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(unlimitedReconnectRetries, QtType.Bool),
     )
   }
@@ -307,9 +238,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setRejoinChannels",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(rejoinChannels, QtType.Bool),
     )
   }
@@ -319,9 +247,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUseCustomMessageRate",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(useCustomMessageRate, QtType.Bool),
     )
   }
@@ -331,9 +256,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMessageRateBurstSize",
-      /**
-       * Construct a QVariant from a UInt
-       */
       qVariant(messageRateBurstSize, QtType.UInt),
     )
   }
@@ -343,9 +265,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setMessageRateDelay",
-      /**
-       * Construct a QVariant from a UInt
-       */
       qVariant(messageRateDelay, QtType.UInt),
     )
   }
@@ -355,9 +274,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setUnlimitedMessageRate",
-      /**
-       * Construct a QVariant from a Boolean
-       */
       qVariant(unlimitedMessageRate, QtType.Bool),
     )
   }
@@ -367,9 +283,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setServerList",
-      /**
-       * Construct a QVariant from a QVariantList
-       */
       qVariant(serverList, QtType.QVariantList),
     )
   }
@@ -379,13 +292,7 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addSupport",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(param, QtType.QString),
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(value, QtType.QString),
     )
   }
@@ -395,9 +302,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "removeSupport",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(param, QtType.QString)
     )
   }
@@ -407,38 +311,26 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addCap",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(capability, QtType.QString),
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(value, QtType.QString),
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun acknowledgeCap(param: String) {
+  fun acknowledgeCap(capability: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "acknowledgeCap",
-      /**
-       * Construct a QVariant from a String
-       */
-      qVariant(param, QtType.QString)
+      qVariant(capability, QtType.QString)
     )
   }
 
   @SyncedCall(target = ProtocolSide.CLIENT)
-  fun removeCap(param: String) {
+  fun removeCap(capability: String) {
     sync(
       target = ProtocolSide.CLIENT,
       "removeCap",
-      /**
-       * Construct a QVariant from a String
-       */
-      qVariant(param, QtType.QString)
+      qVariant(capability, QtType.QString)
     )
   }
 
@@ -455,9 +347,6 @@ interface NetworkStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "addIrcUser",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(hostmask, QtType.QString),
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt
index 74fe856ef91d6cd0493c011ad37da78af1453f39..4ea18317b972c1fb53ae8a45e3e4ee82fb9ade6a 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt
@@ -30,9 +30,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "accept",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(savePath, QtType.QString)
     )
   }
@@ -42,9 +39,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestAccepted",
-      /**
-       * Construct a QVariant from a ULong
-       */
       qVariant(peer, QtType.ULong)
     )
   }
@@ -62,9 +56,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CORE,
       "requestRejected",
-      /**
-       * Construct a QVariant from a ULong
-       */
       qVariant(peer, QtType.ULong)
     )
   }
@@ -92,9 +83,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setAddress",
-      /**
-       * Construct a QVariant from a InetAddress
-       */
       qVariant(address, QuasselType.QHostAddress)
     )
   }
@@ -104,9 +92,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setPort",
-      /**
-       * Construct a QVariant from a UShort
-       */
       qVariant(port, QtType.UShort)
     )
   }
@@ -116,9 +101,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setFileName",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(fileName, QtType.QString)
     )
   }
@@ -128,9 +110,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setFileSize",
-      /**
-       * Construct a QVariant from a ULong
-       */
       qVariant(fileSize, QtType.ULong)
     )
   }
@@ -140,9 +119,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setNick",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(nick, QtType.QString)
     )
   }
@@ -152,9 +128,6 @@ interface TransferStub : SyncableStub {
     sync(
       target = ProtocolSide.CLIENT,
       "setError",
-      /**
-       * Construct a QVariant from a String
-       */
       qVariant(errorString, QtType.QString)
     )
   }
@@ -165,9 +138,6 @@ interface TransferStub : SyncableStub {
       target = ProtocolSide.CLIENT,
       "dataReceived",
       qVariant(peer, QuasselType.PeerPtr),
-      /**
-       * Construct a QVariant from a ByteBuffer
-       */
       qVariant(data, QtType.QByteArray)
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt
index 9531f839ded60be5c494b379c39abe357703c1a4..ab36264d1b638614a0cfe7763e97ab918d940d0d 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt
@@ -38,12 +38,12 @@ internal abstract class ParsingContext<T>(
   @Generated
   protected inline fun match(
     vararg patterns: String,
-    crossinline function: () -> Expansion
+    crossinline function: (String) -> Expansion
   ) = Supplier {
     for (pattern in patterns) {
       if (text.startsWith(pattern, startIndex = position)) {
         position += pattern.length
-        return@Supplier function()
+        return@Supplier function(pattern)
       }
     }
     return@Supplier null
@@ -52,13 +52,13 @@ internal abstract class ParsingContext<T>(
   @Generated
   protected inline fun match(
     vararg patterns: Regex,
-    crossinline function: (List<String>) -> Expansion
+    crossinline function: (String, List<String>) -> Expansion
   ) = Supplier {
     for (pattern in patterns) {
       val match = pattern.find(text, startIndex = position)
       if (match != null && match.range.first == position) {
         position = match.range.last + 1
-        return@Supplier function(match.groupValues)
+        return@Supplier function(match.value, match.groupValues)
       }
     }
     return@Supplier null
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt
index a694729e42f83d5f338ef1f9f29b29aed72a77fc..a26378315e91db3b06245058d199c16495559fd7 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt
@@ -14,6 +14,8 @@ package de.justjanne.libquassel.protocol.util.expansion
  * Model for a command expansion
  */
 sealed class Expansion {
+  abstract val source: String
+
   /**
    * Model for raw text
    */
@@ -21,7 +23,7 @@ sealed class Expansion {
     /**
      * Text to insert
      */
-    val value: String
+    override val source: String
   ) : Expansion()
 
   /**
@@ -36,7 +38,11 @@ sealed class Expansion {
      * Index of the last included parameter.
      * If null, this means all parameters after the start should be included
      */
-    val to: Int?
+    val to: Int?,
+    /**
+     * Original value that was parsed and replaced
+     */
+    override val source: String
   ) : Expansion()
 
   /**
@@ -57,7 +63,11 @@ sealed class Expansion {
      * [ParameterField.VERIFIED_IDENT], it’ll look up the ident of justjanne
      * in the current context, try to verify it, and set it to * otherwise.
      */
-    val field: ParameterField?
+    val field: ParameterField?,
+    /**
+     * Original value that was parsed and replaced
+     */
+    override val source: String
   ) : Expansion()
 
   /**
@@ -67,7 +77,11 @@ sealed class Expansion {
     /**
      * Type of constant to be inserted
      */
-    val field: ConstantField
+    val field: ConstantField,
+    /**
+     * Original value that was parsed and replaced
+     */
+    override val source: String
   ) : Expansion()
 
   /**
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt
index 566e2df1362548c2bd5f08d4432456b13d73bc87..6adccc7a34a3d46fe405aa9c7af54ea171c1b14f 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt
@@ -17,38 +17,38 @@ internal class ExpansionParsingContext(
   text: String
 ) : ParsingContext<Expansion>(text) {
   override val matchers: List<Supplier<Expansion?>> = listOf(
-    match("\$channelname", "\$channel") {
-      Expansion.Constant(Expansion.ConstantField.CHANNEL)
+    match("\$channelname", "\$channel") { source ->
+      Expansion.Constant(Expansion.ConstantField.CHANNEL, source)
     },
-    match("\$currentnick", "\$nick") {
-      Expansion.Constant(Expansion.ConstantField.NICK)
+    match("\$currentnick", "\$nick") { source ->
+      Expansion.Constant(Expansion.ConstantField.NICK, source)
     },
-    match("\$network") {
-      Expansion.Constant(Expansion.ConstantField.NETWORK)
+    match("\$network") { source ->
+      Expansion.Constant(Expansion.ConstantField.NETWORK, source)
     },
-    match("\$0") {
-      Expansion.Parameter(0, null)
+    match("\$0") { source ->
+      Expansion.Parameter(0, null, source)
     },
-    match("""\$(\d+)\.\.(\d+)""".toRegex()) { (_, from, to) ->
-      Expansion.ParameterRange(from.toInt(), to.toInt())
+    match("""\$(\d+)\.\.(\d+)""".toRegex()) { source, (_, from, to) ->
+      Expansion.ParameterRange(from.toInt(), to.toInt(), source)
     },
-    match("""\$(\d+)\.\.""".toRegex()) { (_, from) ->
-      Expansion.ParameterRange(from.toInt(), null)
+    match("""\$(\d+)\.\.""".toRegex()) { source, (_, from) ->
+      Expansion.ParameterRange(from.toInt(), null, source)
     },
-    match("""\$(\d+):hostname""".toRegex()) { (_, value) ->
-      Expansion.Parameter(value.toInt(), Expansion.ParameterField.HOSTNAME)
+    match("""\$(\d+):hostname""".toRegex()) { source, (_, value) ->
+      Expansion.Parameter(value.toInt(), Expansion.ParameterField.HOSTNAME, source)
     },
-    match("""\$(\d+):identd""".toRegex()) { (_, value) ->
-      Expansion.Parameter(value.toInt(), Expansion.ParameterField.VERIFIED_IDENT)
+    match("""\$(\d+):identd""".toRegex()) { source, (_, value) ->
+      Expansion.Parameter(value.toInt(), Expansion.ParameterField.VERIFIED_IDENT, source)
     },
-    match("""\$(\d+):ident""".toRegex()) { (_, value) ->
-      Expansion.Parameter(value.toInt(), Expansion.ParameterField.IDENT)
+    match("""\$(\d+):ident""".toRegex()) { source, (_, value) ->
+      Expansion.Parameter(value.toInt(), Expansion.ParameterField.IDENT, source)
     },
-    match("""\$(\d+):account""".toRegex()) { (_, value) ->
-      Expansion.Parameter(value.toInt(), Expansion.ParameterField.ACCOUNT)
+    match("""\$(\d+):account""".toRegex()) { source, (_, value) ->
+      Expansion.Parameter(value.toInt(), Expansion.ParameterField.ACCOUNT, source)
     },
-    match("""\$(\d+)""".toRegex()) { (_, value) ->
-      Expansion.Parameter(value.toInt(), null)
+    match("""\$(\d+)""".toRegex()) { source, (_, value) ->
+      Expansion.Parameter(value.toInt(), null, source)
     },
     Supplier {
       val end = text.indexOf('$', startIndex = position).let {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/indices.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/indices.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ca42d8ba0141135937e9d522d69f6a959c4da53a
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/indices.kt
@@ -0,0 +1,13 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+inline val <K, V> Map<K, V>.indices get() = 0 until size
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/HostmaskHelper.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/HostmaskHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7f0032b996bea852b3e0ded5b6ff319270a541b9
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/HostmaskHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util.irc
+
+object HostmaskHelper {
+  fun nick(mask: String) = mask
+    .substringBeforeLast('@')
+    .substringBefore('!')
+
+  fun user(mask: String) = mask
+    .substringBeforeLast('@')
+    .substringAfter('!', missingDelimiterValue = "")
+
+  fun host(mask: String) = mask
+    .substringAfterLast('@', missingDelimiterValue = "")
+
+  fun split(mask: String): Triple<String, String, String> {
+    val userPart = mask.substringBeforeLast('@')
+    val host = mask.substringAfterLast('@', missingDelimiterValue = "")
+
+    val user = userPart.substringAfter('!', missingDelimiterValue = "")
+    val nick = userPart.substringBefore('!')
+
+    return Triple(nick, user, host)
+  }
+
+  fun build(nick: String, user: String?, host: String?) = buildString {
+    append(nick)
+    if (!user.isNullOrEmpty()) {
+      append("!$user")
+    }
+    if (!host.isNullOrEmpty()) {
+      append("@$host")
+    }
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/IrcCapability.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/IrcCapability.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c8c156f956910cb11f081bccd6bdc7d23be8d8e0
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/IrcCapability.kt
@@ -0,0 +1,32 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util.irc
+
+object IrcCapability {
+  const val ACCOUNT_NOTIFY = "account-notify"
+  const val ACCOUNT_NOTIFY_WHOX_NUMERIC = 369
+  const val AWAY_NOTIFY = "away-notify"
+  const val CAP_NOTIFY = "cap-notify"
+  const val CHGHOST = "chghost"
+  const val EXTENDED_JOIN = "extended-join"
+  const val MULTI_PREFIX = "multi-prefix"
+  const val SASL = "sasl"
+  const val USERHOST_IN_NAMES = "userhost-in-names"
+
+  object Vendor {
+    const val ZNC_SELF_MESSAGE = "znc.in/self-message"
+  }
+
+  object SaslMechanism {
+    const val PLAIN = "PLAIN"
+    const val EXTERNAL = "EXTERNAL"
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/IrcCaseMapper.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/IrcCaseMapper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d079b0da5c9b72278793988e4636c780d788f18e
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/irc/IrcCaseMapper.kt
@@ -0,0 +1,67 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util.irc
+
+import java.util.Locale
+
+abstract class IrcCaseMapper {
+  @JvmName("equalsIgnoreCaseNonNull")
+  fun equalsIgnoreCase(a: String?, b: String?) =
+    a == null && b == null || (a != null && b != null && equalsIgnoreCase(a, b))
+
+  abstract fun equalsIgnoreCase(a: String, b: String): Boolean
+
+  @JvmName("toLowerCaseNonNull")
+  fun toLowerCase(value: String?): String? = value
+    ?.let(this@IrcCaseMapper::toLowerCase)
+
+  abstract fun toLowerCase(value: String): String
+
+  @JvmName("toUpperCaseNonNull")
+  fun toUpperCase(value: String?): String? = value
+    ?.let(this@IrcCaseMapper::toUpperCase)
+
+  abstract fun toUpperCase(value: String): String
+
+  object Unicode : IrcCaseMapper() {
+    override fun equalsIgnoreCase(a: String, b: String): Boolean =
+      a.equals(b, ignoreCase = true)
+
+    override fun toLowerCase(value: String): String =
+      value.toLowerCase(Locale.ENGLISH)
+
+    override fun toUpperCase(value: String): String =
+      value.toUpperCase(Locale.ENGLISH)
+  }
+
+  object Rfc1459 : IrcCaseMapper() {
+    override fun equalsIgnoreCase(a: String, b: String): Boolean =
+      toLowerCase(a) == toLowerCase(b) || toUpperCase(a) == toUpperCase(b)
+
+    override fun toLowerCase(value: String): String =
+      value.toLowerCase(Locale.ROOT)
+        .replace('[', '{')
+        .replace(']', '}')
+        .replace('\\', '|')
+
+    override fun toUpperCase(value: String): String =
+      value.toUpperCase(Locale.ROOT)
+        .replace('{', '[')
+        .replace('}', ']')
+        .replace('|', '\\')
+  }
+
+  companion object {
+    operator fun get(caseMapping: String?) =
+      if (caseMapping.equals("rfc1459", ignoreCase = true)) Rfc1459
+      else Unicode
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/transpose.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/transpose.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c9658896cc69536a735cd9c9b59bc40b92407bea
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/transpose.kt
@@ -0,0 +1,20 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.QVariant_
+
+fun List<QVariantMap>.transpose(): Map<String, QVariantList> =
+  flatMap { it.keys }.toSet().map { key ->
+    Pair(key, map { it[key] as QVariant_ })
+  }.toMap()
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/updateStateFlow.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/updateStateFlow.kt
new file mode 100644
index 0000000000000000000000000000000000000000..282ddf089f5141178a6a915494d74efc8c1a1487
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/updateStateFlow.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+inline fun <T> MutableStateFlow<T>.update(function: T.() -> T) {
+  value = function(value)
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/indexed.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/indexed.kt
new file mode 100644
index 0000000000000000000000000000000000000000..346708e39aac83fec2d817bd5e7c7b3b923c213d
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/indexed.kt
@@ -0,0 +1,18 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.variant
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun QVariant_?.indexed(index: Int?) = this?.let {
+  index?.let { i ->
+    it.into<QVariantList>()?.get(i)
+  } ?: it
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt
index 02290613c6e94d40d9ec754934e4b81e4363504d..1e640e5f7711ddb20df1177542c7507732d81b70 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt
@@ -19,7 +19,7 @@ class ExpansionTest {
     assertEquals(
       listOf(
         Expansion.Text("/join "),
-        Expansion.Parameter(0, null)
+        Expansion.Parameter(0, null, "$0")
       ),
       Expansion.parse("/join $0")
     )
@@ -27,9 +27,9 @@ class ExpansionTest {
     assertEquals(
       listOf(
         Expansion.Text("/whois "),
-        Expansion.Parameter(0, null),
+        Expansion.Parameter(0, null, "$0"),
         Expansion.Text(" "),
-        Expansion.Parameter(0, null)
+        Expansion.Parameter(0, null, "$0")
       ),
       Expansion.parse("/whois $0 $0")
     )
@@ -40,21 +40,21 @@ class ExpansionTest {
     assertEquals(
       listOf(
         Expansion.Text("/say Welcome to the support channel for the IRC client Quassel, "),
-        Expansion.Parameter(1, null)
+        Expansion.Parameter(1, null, "\$1")
       ),
       Expansion.parse("/say Welcome to the support channel for the IRC client Quassel, \$1")
     )
     assertEquals(
       listOf(
-        Expansion.Parameter(1, null),
+        Expansion.Parameter(1, null, "\$1"),
         Expansion.Text(" "),
-        Expansion.Parameter(1, Expansion.ParameterField.ACCOUNT),
+        Expansion.Parameter(1, Expansion.ParameterField.ACCOUNT, "\$1:account"),
         Expansion.Text(" "),
-        Expansion.Parameter(1, Expansion.ParameterField.HOSTNAME),
+        Expansion.Parameter(1, Expansion.ParameterField.HOSTNAME, "\$1:hostname"),
         Expansion.Text(" "),
-        Expansion.Parameter(1, Expansion.ParameterField.VERIFIED_IDENT),
+        Expansion.Parameter(1, Expansion.ParameterField.VERIFIED_IDENT, "\$1:identd"),
         Expansion.Text(" "),
-        Expansion.Parameter(1, Expansion.ParameterField.IDENT),
+        Expansion.Parameter(1, Expansion.ParameterField.IDENT, "\$1:ident"),
       ),
       Expansion.parse("\$1 \$1:account \$1:hostname \$1:identd \$1:ident")
     )
@@ -65,11 +65,11 @@ class ExpansionTest {
     assertEquals(
       listOf(
         Expansion.Text("/say I am "),
-        Expansion.Constant(Expansion.ConstantField.NICK),
+        Expansion.Constant(Expansion.ConstantField.NICK, "\$nick"),
         Expansion.Text(", welcoming you to our channel "),
-        Expansion.Constant(Expansion.ConstantField.CHANNEL),
+        Expansion.Constant(Expansion.ConstantField.CHANNEL, "\$channelname"),
         Expansion.Text(" on "),
-        Expansion.Constant(Expansion.ConstantField.NETWORK),
+        Expansion.Constant(Expansion.ConstantField.NETWORK, "\$network"),
         Expansion.Text(".")
       ),
       Expansion.parse("/say I am \$nick, welcoming you to our channel \$channelname on \$network.")
@@ -77,9 +77,9 @@ class ExpansionTest {
     assertEquals(
       listOf(
         Expansion.Text("/say That’s right, I’m /the/ "),
-        Expansion.Constant(Expansion.ConstantField.NICK),
+        Expansion.Constant(Expansion.ConstantField.NICK, "\$nick"),
         Expansion.Text(" from "),
-        Expansion.Constant(Expansion.ConstantField.CHANNEL),
+        Expansion.Constant(Expansion.ConstantField.CHANNEL, "\$channel"),
         Expansion.Text(".")
       ),
       Expansion.parse("/say That’s right, I’m /the/ \$nick from \$channel.")
@@ -91,13 +91,13 @@ class ExpansionTest {
     assertEquals(
       listOf(
         Expansion.Text("1 \""),
-        Expansion.Parameter(1, null),
+        Expansion.Parameter(1, null, "\$1"),
         Expansion.Text("\" 2 \""),
-        Expansion.Parameter(2, null),
+        Expansion.Parameter(2, null, "\$2"),
         Expansion.Text("\" 3..4 \""),
-        Expansion.ParameterRange(3, 4),
+        Expansion.ParameterRange(3, 4, "\$3..4"),
         Expansion.Text("\" 3.. \""),
-        Expansion.ParameterRange(3, null),
+        Expansion.ParameterRange(3, null, "\$3.."),
         Expansion.Text("\""),
       ),
       Expansion.parse("1 \"\$1\" 2 \"\$2\" 3..4 \"\$3..4\" 3.. \"\$3..\"")