Pārlūkot izejas kodu

Fix handling of channel user modes

Closes #8
tags/v0.6.0
Chris Smith 5 gadus atpakaļ
vecāks
revīzija
1d119f3166

+ 1
- 0
CHANGELOG Parādīt failu

3
  * Changed USER command to not send the server name, per modern standards
3
  * Changed USER command to not send the server name, per modern standards
4
  * Added support for SASL authentication (with PLAIN mechanism)
4
  * Added support for SASL authentication (with PLAIN mechanism)
5
  * Removed some unused test code
5
  * Removed some unused test code
6
+ * Fixed handling of user mode changes on channels (op/deop/etc)
6
  * Message extensions:
7
  * Message extensions:
7
     * Added support for IRCv3 message tags v3.3
8
     * Added support for IRCv3 message tags v3.3
8
     * Exposed message IDs in MessageReceived and ActionReceived events
9
     * Exposed message IDs in MessageReceived and ActionReceived events

+ 26
- 8
src/main/kotlin/com/dmdirc/ktirc/events/ChannelStateHandler.kt Parādīt failu

75
     }
75
     }
76
 
76
 
77
     private fun handleModeChanged(client: IrcClient, event: ModeChanged) {
77
     private fun handleModeChanged(client: IrcClient, event: ModeChanged) {
78
-        val chan =  client.channelState[event.target] ?: return
78
+        val chan = client.channelState[event.target] ?: return
79
         if (event.discovered) {
79
         if (event.discovered) {
80
             chan.modesDiscovered = true
80
             chan.modesDiscovered = true
81
             chan.modes.clear()
81
             chan.modes.clear()
93
     }
93
     }
94
 
94
 
95
     private fun adjustMode(client: IrcClient, chan: ChannelState, mode: Char, arguments: Array<String>, argumentOffset: Int, adding: Boolean): Int {
95
     private fun adjustMode(client: IrcClient, chan: ChannelState, mode: Char, arguments: Array<String>, argumentOffset: Int, adding: Boolean): Int {
96
-        val type = client.serverState.channelModeType(mode)
97
-        val takesParam = if (adding) type.needsParameterToSet else type.needsParameterToUnset
98
-        val param = if (takesParam) arguments[argumentOffset] else ""
99
-        if (adding) {
100
-            chan.modes[mode] = param
96
+        return if (client.serverState.isChannelUserMode(mode)) {
97
+            adjustUserMode(client, chan, mode, adding, arguments[argumentOffset])
98
+            1
101
         } else {
99
         } else {
102
-            chan.modes.remove(mode)
100
+            val type = client.serverState.channelModeType(mode)
101
+            val takesParam = if (adding) type.needsParameterToSet else type.needsParameterToUnset
102
+            val param = if (takesParam) arguments[argumentOffset] else ""
103
+            if (adding) {
104
+                chan.modes[mode] = param
105
+            } else {
106
+                chan.modes.remove(mode)
107
+            }
108
+            if (takesParam) 1 else 0
109
+        }
110
+    }
111
+
112
+    private fun adjustUserMode(client: IrcClient, chan: ChannelState, mode: Char, adding: Boolean, user: String) {
113
+        chan.users[user]?.let { channelUser ->
114
+            // Filter from the master list of mode prefixes so that ordering is consistent
115
+            channelUser.modes = client.serverState.channelModePrefixes.modes.filter {
116
+                if (adding) {
117
+                    it == mode || it in channelUser.modes
118
+                } else {
119
+                    it != mode && it in channelUser.modes
120
+                }
121
+            }
103
         }
122
         }
104
-        return if (takesParam) 1 else 0
105
     }
123
     }
106
 
124
 
107
     private fun handleQuit(client: IrcClient, event: UserQuit) = sequence {
125
     private fun handleQuit(client: IrcClient, event: UserQuit) = sequence {

+ 13
- 0
src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt Parādīt failu

52
     /** The current state of SASL authentication. */
52
     /** The current state of SASL authentication. */
53
     internal val sasl = SaslState(saslMechanisms)
53
     internal val sasl = SaslState(saslMechanisms)
54
 
54
 
55
+    /**
56
+     * Convenience accessor for the [ServerFeature.ModePrefixes] feature, which will always have a value.
57
+     */
58
+    val channelModePrefixes
59
+        get() = features[ServerFeature.ModePrefixes] ?: throw IllegalStateException("lost mode prefixes")
60
+
61
+    /**
62
+     * Determines if the given mode is one applied to a user of a channel, such as 'o' for operator.
63
+     */
64
+    fun isChannelUserMode(mode: Char) = channelModePrefixes.isMode(mode)
65
+
55
     /**
66
     /**
56
      * Determines what type of channel mode the given character is, based on the server features.
67
      * Determines what type of channel mode the given character is, based on the server features.
57
      *
68
      *
103
 
114
 
104
     /** Determines whether the given character is a mode prefix (e.g. "@", "+"). */
115
     /** Determines whether the given character is a mode prefix (e.g. "@", "+"). */
105
     fun isPrefix(char: Char) = prefixes.contains(char)
116
     fun isPrefix(char: Char) = prefixes.contains(char)
117
+    /** Determines whether the given character is a channel user mode (e.g. "o", "v"). */
118
+    fun isMode(char: Char) = modes.contains(char)
106
     /** Gets the mode corresponding to the given prefix (e.g. "@" -> "o"). */
119
     /** Gets the mode corresponding to the given prefix (e.g. "@" -> "o"). */
107
     fun getMode(prefix: Char) = modes[prefixes.indexOf(prefix)]
120
     fun getMode(prefix: Char) = modes[prefixes.indexOf(prefix)]
108
     /** Gets the modes corresponding to the given prefixes (e.g. "@+" -> "ov"). */
121
     /** Gets the modes corresponding to the given prefixes (e.g. "@+" -> "ov"). */

+ 75
- 1
src/test/kotlin/com/dmdirc/ktirc/events/ChannelStateHandlerTest.kt Parādīt failu

260
         assertNull(channel.modes['h'])
260
         assertNull(channel.modes['h'])
261
     }
261
     }
262
 
262
 
263
+    @Test
264
+    fun `handles unprivileged user gaining new mode`() {
265
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
266
+            users += ChannelUser("ZeroCool")
267
+            channelStateMap += this
268
+        }
269
+
270
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "+o", arrayOf("zeroCool")))
271
+
272
+        assertEquals("o", channelStateMap["#thegibson"]?.users?.get("zeroCool")?.modes)
273
+    }
274
+
275
+    @Test
276
+    fun `handles privileged user gaining lesser mode`() {
277
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
278
+            users += ChannelUser("ZeroCool", "o")
279
+            channelStateMap += this
280
+        }
281
+
282
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "+v", arrayOf("zeroCool")))
283
+
284
+        assertEquals("ov", channelStateMap["#thegibson"]?.users?.get("zeroCool")?.modes)
285
+    }
286
+
287
+    @Test
288
+    fun `handles privileged user gaining greater mode`() {
289
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
290
+            users += ChannelUser("ZeroCool", "v")
291
+            channelStateMap += this
292
+        }
293
+
294
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "+o", arrayOf("zeroCool")))
295
+
296
+        assertEquals("ov", channelStateMap["#thegibson"]?.users?.get("zeroCool")?.modes)
297
+    }
298
+
299
+    @Test
300
+    fun `handles user gaining multiple modes`() {
301
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
302
+            users += ChannelUser("ZeroCool")
303
+            channelStateMap += this
304
+        }
305
+
306
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "+vo", arrayOf("zeroCool", "zeroCool")))
307
+
308
+        assertEquals("ov", channelStateMap["#thegibson"]?.users?.get("zeroCool")?.modes)
309
+    }
310
+
311
+    @Test
312
+    fun `handles user losing multiple modes`() {
313
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
314
+            users += ChannelUser("ZeroCool", "ov")
315
+            channelStateMap += this
316
+        }
317
+
318
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "-vo", arrayOf("zeroCool", "zeroCool")))
319
+
320
+        assertEquals("", channelStateMap["#thegibson"]?.users?.get("zeroCool")?.modes)
321
+    }
322
+
323
+    @Test
324
+    fun `handles mixture of user modes and normal modes`() {
325
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
326
+            users += ChannelUser("ZeroCool", "v")
327
+            channelStateMap += this
328
+        }
329
+        serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
330
+
331
+        handler.processEvent(ircClient, ModeChanged(TestConstants.time, "#thegibson", "oa-v+b", arrayOf("zeroCool", "aaa", "zeroCool", "bbb")))
332
+
333
+        assertEquals("o", channelStateMap["#thegibson"]?.users?.get("zeroCool")?.modes)
334
+        assertEquals("aaa", channelStateMap["#thegibson"]?.modes?.get('a'))
335
+        assertEquals("bbb", channelStateMap["#thegibson"]?.modes?.get('b'))
336
+    }
263
 
337
 
264
-}
338
+}

+ 21
- 0
src/test/kotlin/com/dmdirc/ktirc/model/ServerStateTest.kt Parādīt failu

33
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('g'))
33
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('g'))
34
     }
34
     }
35
 
35
 
36
+    @Test
37
+    fun `returns whether a mode is a channel user mode or not`() {
38
+        val serverState = ServerState("acidBurn", "")
39
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("oqv", "@~+")
40
+        assertTrue(serverState.isChannelUserMode('o'))
41
+        assertTrue(serverState.isChannelUserMode('q'))
42
+        assertTrue(serverState.isChannelUserMode('v'))
43
+        assertFalse(serverState.isChannelUserMode('@'))
44
+        assertFalse(serverState.isChannelUserMode('!'))
45
+        assertFalse(serverState.isChannelUserMode('z'))
46
+    }
47
+
36
     @Test
48
     @Test
37
     fun `returns NoParameter for unknown channel mode`() {
49
     fun `returns NoParameter for unknown channel mode`() {
38
         val serverState = ServerState("acidBurn", "")
50
         val serverState = ServerState("acidBurn", "")
59
         assertFalse(mapping.isPrefix('o'))
71
         assertFalse(mapping.isPrefix('o'))
60
     }
72
     }
61
 
73
 
74
+    @Test
75
+    fun `ModePrefixMapping identifies which chars are modes`() {
76
+        val mapping = ModePrefixMapping("oav", "+@-")
77
+        assertFalse(mapping.isMode('+'))
78
+        assertFalse(mapping.isMode('@'))
79
+        assertFalse(mapping.isMode('!'))
80
+        assertTrue(mapping.isMode('o'))
81
+    }
82
+
62
     @Test
83
     @Test
63
     fun `ModePrefixMapping maps prefixes to modes`() {
84
     fun `ModePrefixMapping maps prefixes to modes`() {
64
         val mapping = ModePrefixMapping("oav", "+@-")
85
         val mapping = ModePrefixMapping("oav", "+@-")

Notiek ielāde…
Atcelt
Saglabāt