Browse Source

Add behaviour config, and option to send modes on join

tags/v0.9.0
Chris Smith 5 years ago
parent
commit
daee215fc4

+ 2
- 0
CHANGELOG View File

@@ -2,6 +2,8 @@ vNEXT (in development)
2 2
 
3 3
  * Improve DSL for creating an IrcClient to allow parameters to be passed to server and profile
4 4
    e.g. IrcClient { server("irc.example.com", 6667) }
5
+ * Add behaviour options
6
+   * requestModesOnJoin - automatically sends a MODE request when joining a channel
5 7
 
6 8
 v0.8.0
7 9
 

+ 32
- 1
src/main/kotlin/com/dmdirc/ktirc/Dsl.kt View File

@@ -6,7 +6,11 @@ package com.dmdirc.ktirc
6 6
 @DslMarker
7 7
 annotation class IrcClientDsl
8 8
 
9
-internal data class IrcClientConfig(val server: ServerConfig, val profile: ProfileConfig, val sasl: SaslConfig?)
9
+internal data class IrcClientConfig(
10
+        val server: ServerConfig,
11
+        val profile: ProfileConfig,
12
+        val behaviour: ClientBehaviour,
13
+        val sasl: SaslConfig?)
10 14
 
11 15
 /**
12 16
  * Dsl for configuring an IRC Client.
@@ -27,6 +31,10 @@ internal data class IrcClientConfig(val server: ServerConfig, val profile: Profi
27 31
  *     realName = "Botomatic v1.2"
28 32
  * }
29 33
  *
34
+ * behaviour {
35
+ *     requestModesOnJoin = true
36
+ * }
37
+ *
30 38
  * sasl {
31 39
  *     mechanisms += "PLAIN" // or to set the list from scratch:
32 40
  *     mechanisms("PLAIN")
@@ -53,6 +61,12 @@ class IrcClientConfigBuilder {
53 61
             field = value
54 62
         }
55 63
 
64
+    private var behaviour: BehaviourConfig? = null
65
+        set(value) {
66
+            check(field == null) { "behaviour may only be specified once" }
67
+            field = value
68
+        }
69
+
56 70
     private var sasl: SaslConfig? = null
57 71
         set(value) {
58 72
             check(field == null) { "sasl may only be specified once" }
@@ -94,6 +108,14 @@ class IrcClientConfigBuilder {
94 108
         }
95 109
     }
96 110
 
111
+    /**
112
+     * Configures the behaviour of the client (optional).
113
+     */
114
+    @IrcClientDsl
115
+    fun behaviour(block: BehaviourConfig.() -> Unit) {
116
+        behaviour = BehaviourConfig().apply(block)
117
+    }
118
+
97 119
     /**
98 120
      * Configures SASL authentication (optional).
99 121
      */
@@ -106,6 +128,7 @@ class IrcClientConfigBuilder {
106 128
             IrcClientConfig(
107 129
                     checkNotNull(server) { "Server must be specified " },
108 130
                     checkNotNull(profile) { "Profile must be specified" },
131
+                    behaviour ?: BehaviourConfig(),
109 132
                     sasl)
110 133
 
111 134
 }
@@ -177,3 +200,11 @@ class SaslConfig {
177 200
         }
178 201
     }
179 202
 }
203
+
204
+/**
205
+ * Dsl for configuring the behaviour of an [IrcClient].
206
+ */
207
+@IrcClientDsl
208
+class BehaviourConfig : ClientBehaviour {
209
+    override var requestModesOnJoin = false
210
+}

+ 15
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

@@ -25,6 +25,11 @@ interface IrcClient {
25 25
      */
26 26
     val userState: UserState
27 27
 
28
+    /**
29
+     * The configured behaviour of the client.
30
+     */
31
+    val behaviour: ClientBehaviour
32
+
28 33
     val caseMapping: CaseMapping
29 34
         get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
30 35
 
@@ -90,6 +95,16 @@ interface IrcClient {
90 95
 
91 96
 }
92 97
 
98
+/**
99
+ * Defines the behaviour of an [IrcClient].
100
+ */
101
+interface ClientBehaviour {
102
+
103
+    /** Whether or not to request channel modes when we join a channel. */
104
+    val requestModesOnJoin: Boolean
105
+
106
+}
107
+
93 108
 /**
94 109
  * Constructs a new [IrcClient] using a configuration DSL.
95 110
  *

+ 2
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClientImpl.kt View File

@@ -36,6 +36,8 @@ internal class IrcClientImpl(private val config: IrcClientConfig) : IrcClient, C
36 36
     @KtorExperimentalAPI
37 37
     internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
38 38
 
39
+    override var behaviour = config.behaviour
40
+
39 41
     override val serverState = ServerState(config.profile.nickname, config.server.host, config.sasl)
40 42
     override val channelState = ChannelStateMap { caseMapping }
41 43
     override val userState = UserState { caseMapping }

+ 4
- 0
src/main/kotlin/com/dmdirc/ktirc/handlers/ChannelStateHandler.kt View File

@@ -2,6 +2,7 @@ package com.dmdirc.ktirc.handlers
2 2
 
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.events.*
5
+import com.dmdirc.ktirc.messages.sendModeRequest
5 6
 import com.dmdirc.ktirc.model.ChannelState
6 7
 import com.dmdirc.ktirc.model.ChannelTopic
7 8
 import com.dmdirc.ktirc.model.ChannelUser
@@ -77,6 +78,9 @@ internal class ChannelStateHandler : EventHandler {
77 78
         client.channelState[event.channel]?.let {
78 79
             it.receivingUserList = false
79 80
             log.finest { "Finished receiving names in ${event.channel}. Users: ${it.users.toList()}" }
81
+            if (client.behaviour.requestModesOnJoin && !it.modesDiscovered) {
82
+                client.sendModeRequest(it.name)
83
+            }
80 84
         }
81 85
     }
82 86
 

+ 3
- 0
src/main/kotlin/com/dmdirc/ktirc/messages/MessageBuilders.kt View File

@@ -15,6 +15,9 @@ internal fun IrcClient.sendCapabilityRequest(capabilities: List<String>) = send(
15 15
 /** Sends a request to join the given channel. */
16 16
 fun IrcClient.sendJoin(channel: String) = send("JOIN :$channel")
17 17
 
18
+/** Sends a request to see the modes of a given target. */
19
+fun IrcClient.sendModeRequest(target: String) = send("MODE :$target")
20
+
18 21
 /** Sends a request to change to the given nickname. */
19 22
 fun IrcClient.sendNickChange(nick: String) = send("NICK :$nick")
20 23
 

+ 34
- 2
src/test/kotlin/com/dmdirc/ktirc/DslTest.kt View File

@@ -1,7 +1,6 @@
1 1
 package com.dmdirc.ktirc
2 2
 
3
-import org.junit.jupiter.api.Assertions.assertEquals
4
-import org.junit.jupiter.api.Assertions.assertTrue
3
+import org.junit.jupiter.api.Assertions.*
5 4
 import org.junit.jupiter.api.Test
6 5
 import org.junit.jupiter.api.assertThrows
7 6
 
@@ -63,6 +62,16 @@ internal class IrcClientConfigBuilderTest {
63 62
         }
64 63
     }
65 64
 
65
+    @Test
66
+    fun `throws if behaviour is defined twice`() {
67
+        assertThrows<IllegalStateException> {
68
+            IrcClientConfigBuilder().apply {
69
+                behaviour {}
70
+                behaviour {}
71
+            }
72
+        }
73
+    }
74
+
66 75
     @Test
67 76
     fun `throws if sasl is defined twice`() {
68 77
         assertThrows<IllegalStateException> {
@@ -180,6 +189,29 @@ internal class IrcClientConfigBuilderTest {
180 189
         assertEquals("Kate", config.profile.realName)
181 190
     }
182 191
 
192
+    @Test
193
+    fun `applies behaviour settings`() {
194
+        val config = IrcClientConfigBuilder().apply {
195
+            profile { nickname = "acidBurn" }
196
+            server { host = "thegibson.com" }
197
+            behaviour {
198
+                requestModesOnJoin = true
199
+            }
200
+        }.build()
201
+
202
+        assertTrue(config.behaviour.requestModesOnJoin)
203
+    }
204
+
205
+    @Test
206
+    fun `falls back to default behaviour settings`() {
207
+        val config = IrcClientConfigBuilder().apply {
208
+            profile { nickname = "acidBurn" }
209
+            server { host = "thegibson.com" }
210
+        }.build()
211
+
212
+        assertFalse(config.behaviour.requestModesOnJoin)
213
+    }
214
+
183 215
     @Test
184 216
     fun `applies sasl settings`() {
185 217
         val config = IrcClientConfigBuilder().apply {

+ 17
- 5
src/test/kotlin/com/dmdirc/ktirc/IrcClientImplTest.kt View File

@@ -59,7 +59,7 @@ internal class IrcClientImplTest {
59 59
     private val normalConfig = IrcClientConfig(ServerConfig().apply {
60 60
         host = HOST
61 61
         port = PORT
62
-    }, profileConfig, null)
62
+    }, profileConfig, BehaviourConfig(), null)
63 63
 
64 64
     @BeforeEach
65 65
     fun setUp() {
@@ -81,7 +81,7 @@ internal class IrcClientImplTest {
81 81
             host = HOST
82 82
             port = PORT
83 83
             useTls = true
84
-        }, profileConfig, null))
84
+        }, profileConfig, BehaviourConfig(), null))
85 85
         client.socketFactory = mockSocketFactory
86 86
         client.connect()
87 87
 
@@ -134,7 +134,7 @@ internal class IrcClientImplTest {
134 134
             host = HOST
135 135
             port = PORT
136 136
             password = PASSWORD
137
-        }, profileConfig, null))
137
+        }, profileConfig, BehaviourConfig(), null))
138 138
         client.socketFactory = mockSocketFactory
139 139
         client.connect()
140 140
 
@@ -222,6 +222,7 @@ internal class IrcClientImplTest {
222 222
     }
223 223
 
224 224
     @Test
225
+    @ObsoleteCoroutinesApi
225 226
     fun `sends messages in order`() = runBlocking {
226 227
         val client = IrcClientImpl(normalConfig)
227 228
         client.socketFactory = mockSocketFactory
@@ -242,17 +243,28 @@ internal class IrcClientImplTest {
242 243
     }
243 244
 
244 245
     @Test
245
-    fun `defaults local nickname to profile`() = runBlocking {
246
+    fun `defaults local nickname to profile`() {
246 247
         val client = IrcClientImpl(normalConfig)
247 248
         assertEquals(NICK, client.serverState.localNickname)
248 249
     }
249 250
 
250 251
     @Test
251
-    fun `defaults server name to host name`() = runBlocking {
252
+    fun `defaults server name to host name`() {
252 253
         val client = IrcClientImpl(normalConfig)
253 254
         assertEquals(HOST, client.serverState.serverName)
254 255
     }
255 256
 
257
+    @Test
258
+    fun `exposes behaviour config`() {
259
+        val client = IrcClientImpl(IrcClientConfig(
260
+                ServerConfig().apply { host = HOST },
261
+                profileConfig,
262
+                BehaviourConfig().apply { requestModesOnJoin = true },
263
+                null))
264
+
265
+        assertTrue(client.behaviour.requestModesOnJoin)
266
+    }
267
+
256 268
     @Test
257 269
     fun `reset clears all state`() {
258 270
         with(IrcClientImpl(normalConfig)) {

+ 57
- 0
src/test/kotlin/com/dmdirc/ktirc/handlers/ChannelStateHandlerTest.kt View File

@@ -1,5 +1,6 @@
1 1
 package com.dmdirc.ktirc.handlers
2 2
 
3
+import com.dmdirc.ktirc.BehaviourConfig
3 4
 import com.dmdirc.ktirc.IrcClient
4 5
 import com.dmdirc.ktirc.TestConstants
5 6
 import com.dmdirc.ktirc.events.*
@@ -7,6 +8,8 @@ import com.dmdirc.ktirc.io.CaseMapping
7 8
 import com.dmdirc.ktirc.model.*
8 9
 import com.nhaarman.mockitokotlin2.doReturn
9 10
 import com.nhaarman.mockitokotlin2.mock
11
+import com.nhaarman.mockitokotlin2.never
12
+import com.nhaarman.mockitokotlin2.verify
10 13
 import org.junit.jupiter.api.Assertions.*
11 14
 import org.junit.jupiter.api.Test
12 15
 
@@ -15,9 +18,11 @@ internal class ChannelStateHandlerTest {
15 18
     private val handler = ChannelStateHandler()
16 19
     private val channelStateMap = ChannelStateMap { CaseMapping.Rfc }
17 20
     private val serverState = ServerState("", "")
21
+    private val behaviour = BehaviourConfig()
18 22
     private val ircClient = mock<IrcClient> {
19 23
         on { serverState } doReturn serverState
20 24
         on { channelState } doReturn channelStateMap
25
+        on { behaviour } doReturn behaviour
21 26
         on { isLocalUser(User("acidburn", "libby", "root.localhost")) } doReturn true
22 27
         on { isLocalUser("acidburn") } doReturn  true
23 28
     }
@@ -116,6 +121,58 @@ internal class ChannelStateHandlerTest {
116 121
         assertEquals("v", channel.users["acidBurn"]?.modes)
117 122
     }
118 123
 
124
+    @Test
125
+    fun `updates receiving user list state`() {
126
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
127
+        channelStateMap += channel
128
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
129
+
130
+        handler.processEvent(ircClient, ChannelNamesReceived(TestConstants.time, "#thegibson", listOf("@zeroCool!dade@root.localhost", "+acidBurn!libby@root.localhost")))
131
+
132
+        assertTrue(channel.receivingUserList)
133
+
134
+        handler.processEvent(ircClient, ChannelNamesFinished(TestConstants.time, "#thegibson"))
135
+
136
+        assertFalse(channel.receivingUserList)
137
+    }
138
+
139
+    @Test
140
+    fun `requests modes on end of names if configured and undiscovered`() {
141
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
142
+        channelStateMap += channel
143
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
144
+        behaviour.requestModesOnJoin = true
145
+
146
+        handler.processEvent(ircClient, ChannelNamesFinished(TestConstants.time, "#thegibson"))
147
+
148
+        verify(ircClient).send("MODE :#thegibson")
149
+    }
150
+
151
+    @Test
152
+    fun `does not request modes on end of names if already discovered`() {
153
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
154
+        channelStateMap += channel
155
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
156
+        behaviour.requestModesOnJoin = true
157
+        channel.modesDiscovered = true
158
+
159
+        handler.processEvent(ircClient, ChannelNamesFinished(TestConstants.time, "#thegibson"))
160
+
161
+        verify(ircClient, never()).send("MODE :#thegibson")
162
+    }
163
+
164
+    @Test
165
+    fun `does not request modes on end of names if not configured`() {
166
+        val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
167
+        channelStateMap += channel
168
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
169
+        behaviour.requestModesOnJoin = false
170
+
171
+        handler.processEvent(ircClient, ChannelNamesFinished(TestConstants.time, "#thegibson"))
172
+
173
+        verify(ircClient, never()).send("MODE :#thegibson")
174
+    }
175
+
119 176
     @Test
120 177
     fun `removes state object for local parts`() {
121 178
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }

+ 6
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/MessageBuildersTest.kt View File

@@ -29,6 +29,12 @@ internal class MessageBuildersTest {
29 29
         verify(mockClient).send("JOIN :#TheGibson")
30 30
     }
31 31
 
32
+    @Test
33
+    fun `sendModeRequest sends correct MODE message`() {
34
+        mockClient.sendModeRequest("#TheGibson")
35
+        verify(mockClient).send("MODE :#TheGibson")
36
+    }
37
+
32 38
     @Test
33 39
     fun `sendNickChange sends correct NICK message`() {
34 40
         mockClient.sendNickChange("AcidBurn")

Loading…
Cancel
Save