Explorar el Código

Add support for channel modes

Closes #5
tags/v0.5.0
Chris Smith hace 5 años
padre
commit
2ceecf54a8

+ 1
- 0
CHANGELOG Ver fichero

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

+ 31
- 0
src/main/kotlin/com/dmdirc/ktirc/events/ChannelStateHandler.kt Ver fichero

15
             is ChannelParted -> handlePart(client, event)
15
             is ChannelParted -> handlePart(client, event)
16
             is ChannelNamesReceived -> handleNamesReceived(client, event)
16
             is ChannelNamesReceived -> handleNamesReceived(client, event)
17
             is ChannelNamesFinished -> handleNamesFinished(client, event)
17
             is ChannelNamesFinished -> handleNamesFinished(client, event)
18
+            is ModeChanged -> handleModeChanged(client, event)
18
             is UserQuit -> return handleQuit(client, event)
19
             is UserQuit -> return handleQuit(client, event)
19
         }
20
         }
20
         return emptyList()
21
         return emptyList()
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
     private fun handleQuit(client: IrcClient, event: UserQuit) = sequence {
95
     private fun handleQuit(client: IrcClient, event: UserQuit) = sequence {
65
         client.channelState.forEach {
96
         client.channelState.forEach {
66
             if (it.users.contains(event.user.nickname)) {
97
             if (it.users.contains(event.user.nickname)) {

+ 12
- 0
src/main/kotlin/com/dmdirc/ktirc/model/ChannelState.kt Ver fichero

13
     var receivingUserList = false
13
     var receivingUserList = false
14
         internal set
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
      * A map of all users in the channel to their current modes.
23
      * A map of all users in the channel to their current modes.
18
      */
24
      */
19
     val users = ChannelUserMap(caseMappingProvider)
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 Ver fichero

6
 import com.dmdirc.ktirc.model.*
6
 import com.dmdirc.ktirc.model.*
7
 import com.nhaarman.mockitokotlin2.doReturn
7
 import com.nhaarman.mockitokotlin2.doReturn
8
 import com.nhaarman.mockitokotlin2.mock
8
 import com.nhaarman.mockitokotlin2.mock
9
-import kotlinx.coroutines.runBlocking
10
 import org.junit.jupiter.api.Assertions.*
9
 import org.junit.jupiter.api.Assertions.*
11
 import org.junit.jupiter.api.Test
10
 import org.junit.jupiter.api.Test
12
 
11
 
22
     }
21
     }
23
 
22
 
24
     @Test
23
     @Test
25
-    fun `ChannelStateHandler creates new state object for local joins`() = runBlocking {
24
+    fun `ChannelStateHandler creates new state object for local joins`() {
26
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("acidburn", "libby", "root.localhost"), "#thegibson"))
25
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("acidburn", "libby", "root.localhost"), "#thegibson"))
27
         assertTrue("#thegibson" in channelStateMap)
26
         assertTrue("#thegibson" in channelStateMap)
28
     }
27
     }
29
 
28
 
30
     @Test
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
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("zerocool", "dade", "root.localhost"), "#thegibson"))
31
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("zerocool", "dade", "root.localhost"), "#thegibson"))
33
         assertFalse("#thegibson" in channelStateMap)
32
         assertFalse("#thegibson" in channelStateMap)
34
     }
33
     }
35
 
34
 
36
     @Test
35
     @Test
37
-    fun `ChannelStateHandler adds joiners to channel state`() = runBlocking {
36
+    fun `ChannelStateHandler adds joiners to channel state`() {
38
         channelStateMap += ChannelState("#thegibson") { CaseMapping.Rfc }
37
         channelStateMap += ChannelState("#thegibson") { CaseMapping.Rfc }
39
 
38
 
40
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("zerocool", "dade", "root.localhost"), "#thegibson"))
39
         handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("zerocool", "dade", "root.localhost"), "#thegibson"))
43
     }
42
     }
44
 
43
 
45
     @Test
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
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
46
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
48
         channel.users += ChannelUser("acidBurn")
47
         channel.users += ChannelUser("acidBurn")
49
         channel.users += ChannelUser("thePlague")
48
         channel.users += ChannelUser("thePlague")
56
     }
55
     }
57
 
56
 
58
     @Test
57
     @Test
59
-    fun `ChannelStateHandler adds users from multiple name received events`() = runBlocking {
58
+    fun `ChannelStateHandler adds users from multiple name received events`() {
60
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
59
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
61
         channelStateMap += channel
60
         channelStateMap += channel
62
 
61
 
71
     }
70
     }
72
 
71
 
73
     @Test
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
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
74
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
76
         channelStateMap += channel
75
         channelStateMap += channel
77
 
76
 
86
     }
85
     }
87
 
86
 
88
     @Test
87
     @Test
89
-    fun `ChannelStateHandler adds users with mode prefixes`() = runBlocking {
88
+    fun `ChannelStateHandler adds users with mode prefixes`() {
90
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
89
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
91
         channelStateMap += channel
90
         channelStateMap += channel
92
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
91
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
102
     }
101
     }
103
 
102
 
104
     @Test
103
     @Test
105
-    fun `ChannelStateHandler adds users with full hosts`() = runBlocking {
104
+    fun `ChannelStateHandler adds users with full hosts`() {
106
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
105
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
107
         channelStateMap += channel
106
         channelStateMap += channel
108
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
107
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
116
     }
115
     }
117
 
116
 
118
     @Test
117
     @Test
119
-    fun `ChannelStateHandler removes state object for local parts`() = runBlocking {
118
+    fun `ChannelStateHandler removes state object for local parts`() {
120
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
119
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
121
         channelStateMap += channel
120
         channelStateMap += channel
122
 
121
 
126
     }
125
     }
127
 
126
 
128
     @Test
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
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
129
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
131
         channel.users += ChannelUser("ZeroCool")
130
         channel.users += ChannelUser("ZeroCool")
132
         channelStateMap += channel
131
         channelStateMap += channel
137
     }
136
     }
138
 
137
 
139
     @Test
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
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
140
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
142
             users += ChannelUser("ZeroCool")
141
             users += ChannelUser("ZeroCool")
143
             channelStateMap += this
142
             channelStateMap += this
163
 
162
 
164
 
163
 
165
     @Test
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
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
166
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
168
             users += ChannelUser("ZeroCool")
167
             users += ChannelUser("ZeroCool")
169
             channelStateMap += this
168
             channelStateMap += this
196
         assertTrue("#dumpsterdiving" in names)
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…
Cancelar
Guardar