Browse Source

Add support for channel modes

Closes #5
tags/v0.5.0
Chris Smith 5 years ago
parent
commit
2ceecf54a8

+ 1
- 0
CHANGELOG View File

@@ -8,6 +8,7 @@ vNEXT (in development)
8 8
     * CHANMODES feature is now stored as an array, not a single comma-separated string
9 9
     * Added ChanModeType enum, and method in ServerState to get the type of a mode
10 10
     * Added ModeChanged event, for user and channel mode changes and discovery
11
+    * Added modes and modesDiscovered to ChannelState
11 12
  * Other new events:
12 13
     * Added MotdFinished event
13 14
     * Added UserAccountChanged event

+ 31
- 0
src/main/kotlin/com/dmdirc/ktirc/events/ChannelStateHandler.kt View File

@@ -15,6 +15,7 @@ internal class ChannelStateHandler : EventHandler {
15 15
             is ChannelParted -> handlePart(client, event)
16 16
             is ChannelNamesReceived -> handleNamesReceived(client, event)
17 17
             is ChannelNamesFinished -> handleNamesFinished(client, event)
18
+            is ModeChanged -> handleModeChanged(client, event)
18 19
             is UserQuit -> return handleQuit(client, event)
19 20
         }
20 21
         return emptyList()
@@ -61,6 +62,36 @@ internal class ChannelStateHandler : EventHandler {
61 62
         }
62 63
     }
63 64
 
65
+    private fun handleModeChanged(client: IrcClient, event: ModeChanged) {
66
+        val chan =  client.channelState[event.target] ?: return
67
+        if (event.discovered) {
68
+            chan.modesDiscovered = true
69
+            chan.modes.clear()
70
+        }
71
+
72
+        var adding = true
73
+        var argumentOffset = 0
74
+        for (char in event.modes) {
75
+            when (char) {
76
+                '+' -> adding = true
77
+                '-' -> adding = false
78
+                else -> argumentOffset += adjustMode(client, chan, char, event.arguments, argumentOffset, adding)
79
+            }
80
+        }
81
+    }
82
+
83
+    private fun adjustMode(client: IrcClient, chan: ChannelState, mode: Char, arguments: Array<String>, argumentOffset: Int, adding: Boolean): Int {
84
+        val type = client.serverState.channelModeType(mode)
85
+        val takesParam = if (adding) type.needsParameterToSet else type.needsParameterToUnset
86
+        val param = if (takesParam) arguments[argumentOffset] else ""
87
+        if (adding) {
88
+            chan.modes[mode] = param
89
+        } else {
90
+            chan.modes.remove(mode)
91
+        }
92
+        return if (takesParam) 1 else 0
93
+    }
94
+
64 95
     private fun handleQuit(client: IrcClient, event: UserQuit) = sequence {
65 96
         client.channelState.forEach {
66 97
             if (it.users.contains(event.user.nickname)) {

+ 12
- 0
src/main/kotlin/com/dmdirc/ktirc/model/ChannelState.kt View File

@@ -13,11 +13,23 @@ class ChannelState(val name: String, caseMappingProvider: () -> CaseMapping) {
13 13
     var receivingUserList = false
14 14
         internal set
15 15
 
16
+    /**
17
+     * Whether or not we have discovered the full set of modes for the channel.
18
+     */
19
+    var modesDiscovered = false
20
+        internal set
21
+
16 22
     /**
17 23
      * A map of all users in the channel to their current modes.
18 24
      */
19 25
     val users = ChannelUserMap(caseMappingProvider)
20 26
 
27
+    /**
28
+     * A map of modes set on the channel, and their values (if any).
29
+     *
30
+     * If [modesDiscovered] is false, this map may be missing modes that the server hasn't told us about.
31
+     */
32
+    var modes = HashMap<Char, String>()
21 33
 }
22 34
 
23 35
 /**

+ 56
- 13
src/test/kotlin/com/dmdirc/ktirc/events/ChannelStateHandlerTest.kt View File

@@ -6,7 +6,6 @@ import com.dmdirc.ktirc.io.CaseMapping
6 6
 import com.dmdirc.ktirc.model.*
7 7
 import com.nhaarman.mockitokotlin2.doReturn
8 8
 import com.nhaarman.mockitokotlin2.mock
9
-import kotlinx.coroutines.runBlocking
10 9
 import org.junit.jupiter.api.Assertions.*
11 10
 import org.junit.jupiter.api.Test
12 11
 
@@ -22,19 +21,19 @@ internal class ChannelStateHandlerTest {
22 21
     }
23 22
 
24 23
     @Test
25
-    fun `ChannelStateHandler creates new state object for local joins`() = runBlocking {
24
+    fun `ChannelStateHandler creates new state object for local joins`() {
26 25
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("acidburn", "libby", "root.localhost"), "#thegibson"))
27 26
         assertTrue("#thegibson" in channelStateMap)
28 27
     }
29 28
 
30 29
     @Test
31
-    fun `ChannelStateHandler does not create new state object for remote joins`() = runBlocking {
30
+    fun `ChannelStateHandler does not create new state object for remote joins`() {
32 31
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("zerocool", "dade", "root.localhost"), "#thegibson"))
33 32
         assertFalse("#thegibson" in channelStateMap)
34 33
     }
35 34
 
36 35
     @Test
37
-    fun `ChannelStateHandler adds joiners to channel state`() = runBlocking {
36
+    fun `ChannelStateHandler adds joiners to channel state`() {
38 37
         channelStateMap += ChannelState("#thegibson") { CaseMapping.Rfc }
39 38
 
40 39
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("zerocool", "dade", "root.localhost"), "#thegibson"))
@@ -43,7 +42,7 @@ internal class ChannelStateHandlerTest {
43 42
     }
44 43
 
45 44
     @Test
46
-    fun `ChannelStateHandler clears existing users when getting a new list`() = runBlocking {
45
+    fun `ChannelStateHandler clears existing users when getting a new list`() {
47 46
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
48 47
         channel.users += ChannelUser("acidBurn")
49 48
         channel.users += ChannelUser("thePlague")
@@ -56,7 +55,7 @@ internal class ChannelStateHandlerTest {
56 55
     }
57 56
 
58 57
     @Test
59
-    fun `ChannelStateHandler adds users from multiple name received events`() = runBlocking {
58
+    fun `ChannelStateHandler adds users from multiple name received events`() {
60 59
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
61 60
         channelStateMap += channel
62 61
 
@@ -71,7 +70,7 @@ internal class ChannelStateHandlerTest {
71 70
     }
72 71
 
73 72
     @Test
74
-    fun `ChannelStateHandler clears and readds users on additional names received`() = runBlocking {
73
+    fun `ChannelStateHandler clears and readds users on additional names received`() {
75 74
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
76 75
         channelStateMap += channel
77 76
 
@@ -86,7 +85,7 @@ internal class ChannelStateHandlerTest {
86 85
     }
87 86
 
88 87
     @Test
89
-    fun `ChannelStateHandler adds users with mode prefixes`() = runBlocking {
88
+    fun `ChannelStateHandler adds users with mode prefixes`() {
90 89
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
91 90
         channelStateMap += channel
92 91
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
@@ -102,7 +101,7 @@ internal class ChannelStateHandlerTest {
102 101
     }
103 102
 
104 103
     @Test
105
-    fun `ChannelStateHandler adds users with full hosts`() = runBlocking {
104
+    fun `ChannelStateHandler adds users with full hosts`() {
106 105
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
107 106
         channelStateMap += channel
108 107
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
@@ -116,7 +115,7 @@ internal class ChannelStateHandlerTest {
116 115
     }
117 116
 
118 117
     @Test
119
-    fun `ChannelStateHandler removes state object for local parts`() = runBlocking {
118
+    fun `ChannelStateHandler removes state object for local parts`() {
120 119
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
121 120
         channelStateMap += channel
122 121
 
@@ -126,7 +125,7 @@ internal class ChannelStateHandlerTest {
126 125
     }
127 126
 
128 127
     @Test
129
-    fun `ChannelStateHandler removes user from channel member list for remote parts`() = runBlocking {
128
+    fun `ChannelStateHandler removes user from channel member list for remote parts`() {
130 129
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
131 130
         channel.users += ChannelUser("ZeroCool")
132 131
         channelStateMap += channel
@@ -137,7 +136,7 @@ internal class ChannelStateHandlerTest {
137 136
     }
138 137
 
139 138
     @Test
140
-    fun `ChannelStateHandler removes user from all channel member lists for quits`() = runBlocking {
139
+    fun `ChannelStateHandler removes user from all channel member lists for quits`() {
141 140
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
142 141
             users += ChannelUser("ZeroCool")
143 142
             channelStateMap += this
@@ -163,7 +162,7 @@ internal class ChannelStateHandlerTest {
163 162
 
164 163
 
165 164
     @Test
166
-    fun `ChannelStateHandler raises ChannelQuit event for each channel a user quits from`() = runBlocking {
165
+    fun `ChannelStateHandler raises ChannelQuit event for each channel a user quits from`() {
167 166
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
168 167
             users += ChannelUser("ZeroCool")
169 168
             channelStateMap += this
@@ -196,4 +195,48 @@ internal class ChannelStateHandlerTest {
196 195
         assertTrue("#dumpsterdiving" in names)
197 196
     }
198 197
 
198
+    @Test
199
+    fun `sets mode discovered flag when discovered mode event received`() {
200
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
201
+        channelStateMap += channel
202
+        serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
203
+
204
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "+", emptyArray(), true))
205
+
206
+        assertTrue(channel.modesDiscovered)
207
+    }
208
+
209
+    @Test
210
+    fun `adds modes when discovered mode event received`() {
211
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
212
+        channelStateMap += channel
213
+        serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
214
+
215
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "+ceg", arrayOf("CCC", "EEE"), true))
216
+
217
+        assertEquals("CCC", channel.modes['c'])
218
+        assertEquals("EEE", channel.modes['e'])
219
+        assertEquals("", channel.modes['g'])
220
+    }
221
+
222
+    @Test
223
+    fun `adjusts complex modes when mode change event received`() {
224
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
225
+        channel.modes['c'] = "CCC"
226
+        channel.modes['e'] = "EEE"
227
+        channel.modes['h'] = ""
228
+        channelStateMap += channel
229
+        serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
230
+
231
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "-c+d-eh+fg", arrayOf("CCC", "DDD", "FFF"), true))
232
+
233
+        assertNull(channel.modes['c'])
234
+        assertEquals("DDD", channel.modes['d'])
235
+        assertNull(channel.modes['e'])
236
+        assertEquals("FFF", channel.modes['f'])
237
+        assertEquals("", channel.modes['g'])
238
+        assertNull(channel.modes['h'])
239
+    }
240
+
241
+
199 242
 }

Loading…
Cancel
Save