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
 
2
 
3
  * Improve DSL for creating an IrcClient to allow parameters to be passed to server and profile
3
  * Improve DSL for creating an IrcClient to allow parameters to be passed to server and profile
4
    e.g. IrcClient { server("irc.example.com", 6667) }
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
 v0.8.0
8
 v0.8.0
7
 
9
 

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

6
 @DslMarker
6
 @DslMarker
7
 annotation class IrcClientDsl
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
  * Dsl for configuring an IRC Client.
16
  * Dsl for configuring an IRC Client.
27
  *     realName = "Botomatic v1.2"
31
  *     realName = "Botomatic v1.2"
28
  * }
32
  * }
29
  *
33
  *
34
+ * behaviour {
35
+ *     requestModesOnJoin = true
36
+ * }
37
+ *
30
  * sasl {
38
  * sasl {
31
  *     mechanisms += "PLAIN" // or to set the list from scratch:
39
  *     mechanisms += "PLAIN" // or to set the list from scratch:
32
  *     mechanisms("PLAIN")
40
  *     mechanisms("PLAIN")
53
             field = value
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
     private var sasl: SaslConfig? = null
70
     private var sasl: SaslConfig? = null
57
         set(value) {
71
         set(value) {
58
             check(field == null) { "sasl may only be specified once" }
72
             check(field == null) { "sasl may only be specified once" }
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
      * Configures SASL authentication (optional).
120
      * Configures SASL authentication (optional).
99
      */
121
      */
106
             IrcClientConfig(
128
             IrcClientConfig(
107
                     checkNotNull(server) { "Server must be specified " },
129
                     checkNotNull(server) { "Server must be specified " },
108
                     checkNotNull(profile) { "Profile must be specified" },
130
                     checkNotNull(profile) { "Profile must be specified" },
131
+                    behaviour ?: BehaviourConfig(),
109
                     sasl)
132
                     sasl)
110
 
133
 
111
 }
134
 }
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
      */
25
      */
26
     val userState: UserState
26
     val userState: UserState
27
 
27
 
28
+    /**
29
+     * The configured behaviour of the client.
30
+     */
31
+    val behaviour: ClientBehaviour
32
+
28
     val caseMapping: CaseMapping
33
     val caseMapping: CaseMapping
29
         get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
34
         get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
30
 
35
 
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
  * Constructs a new [IrcClient] using a configuration DSL.
109
  * Constructs a new [IrcClient] using a configuration DSL.
95
  *
110
  *

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

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

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

2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.events.*
4
 import com.dmdirc.ktirc.events.*
5
+import com.dmdirc.ktirc.messages.sendModeRequest
5
 import com.dmdirc.ktirc.model.ChannelState
6
 import com.dmdirc.ktirc.model.ChannelState
6
 import com.dmdirc.ktirc.model.ChannelTopic
7
 import com.dmdirc.ktirc.model.ChannelTopic
7
 import com.dmdirc.ktirc.model.ChannelUser
8
 import com.dmdirc.ktirc.model.ChannelUser
77
         client.channelState[event.channel]?.let {
78
         client.channelState[event.channel]?.let {
78
             it.receivingUserList = false
79
             it.receivingUserList = false
79
             log.finest { "Finished receiving names in ${event.channel}. Users: ${it.users.toList()}" }
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
 /** Sends a request to join the given channel. */
15
 /** Sends a request to join the given channel. */
16
 fun IrcClient.sendJoin(channel: String) = send("JOIN :$channel")
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
 /** Sends a request to change to the given nickname. */
21
 /** Sends a request to change to the given nickname. */
19
 fun IrcClient.sendNickChange(nick: String) = send("NICK :$nick")
22
 fun IrcClient.sendNickChange(nick: String) = send("NICK :$nick")
20
 
23
 

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

1
 package com.dmdirc.ktirc
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
 import org.junit.jupiter.api.Test
4
 import org.junit.jupiter.api.Test
6
 import org.junit.jupiter.api.assertThrows
5
 import org.junit.jupiter.api.assertThrows
7
 
6
 
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
     @Test
75
     @Test
67
     fun `throws if sasl is defined twice`() {
76
     fun `throws if sasl is defined twice`() {
68
         assertThrows<IllegalStateException> {
77
         assertThrows<IllegalStateException> {
180
         assertEquals("Kate", config.profile.realName)
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
     @Test
215
     @Test
184
     fun `applies sasl settings`() {
216
     fun `applies sasl settings`() {
185
         val config = IrcClientConfigBuilder().apply {
217
         val config = IrcClientConfigBuilder().apply {

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

59
     private val normalConfig = IrcClientConfig(ServerConfig().apply {
59
     private val normalConfig = IrcClientConfig(ServerConfig().apply {
60
         host = HOST
60
         host = HOST
61
         port = PORT
61
         port = PORT
62
-    }, profileConfig, null)
62
+    }, profileConfig, BehaviourConfig(), null)
63
 
63
 
64
     @BeforeEach
64
     @BeforeEach
65
     fun setUp() {
65
     fun setUp() {
81
             host = HOST
81
             host = HOST
82
             port = PORT
82
             port = PORT
83
             useTls = true
83
             useTls = true
84
-        }, profileConfig, null))
84
+        }, profileConfig, BehaviourConfig(), null))
85
         client.socketFactory = mockSocketFactory
85
         client.socketFactory = mockSocketFactory
86
         client.connect()
86
         client.connect()
87
 
87
 
134
             host = HOST
134
             host = HOST
135
             port = PORT
135
             port = PORT
136
             password = PASSWORD
136
             password = PASSWORD
137
-        }, profileConfig, null))
137
+        }, profileConfig, BehaviourConfig(), null))
138
         client.socketFactory = mockSocketFactory
138
         client.socketFactory = mockSocketFactory
139
         client.connect()
139
         client.connect()
140
 
140
 
222
     }
222
     }
223
 
223
 
224
     @Test
224
     @Test
225
+    @ObsoleteCoroutinesApi
225
     fun `sends messages in order`() = runBlocking {
226
     fun `sends messages in order`() = runBlocking {
226
         val client = IrcClientImpl(normalConfig)
227
         val client = IrcClientImpl(normalConfig)
227
         client.socketFactory = mockSocketFactory
228
         client.socketFactory = mockSocketFactory
242
     }
243
     }
243
 
244
 
244
     @Test
245
     @Test
245
-    fun `defaults local nickname to profile`() = runBlocking {
246
+    fun `defaults local nickname to profile`() {
246
         val client = IrcClientImpl(normalConfig)
247
         val client = IrcClientImpl(normalConfig)
247
         assertEquals(NICK, client.serverState.localNickname)
248
         assertEquals(NICK, client.serverState.localNickname)
248
     }
249
     }
249
 
250
 
250
     @Test
251
     @Test
251
-    fun `defaults server name to host name`() = runBlocking {
252
+    fun `defaults server name to host name`() {
252
         val client = IrcClientImpl(normalConfig)
253
         val client = IrcClientImpl(normalConfig)
253
         assertEquals(HOST, client.serverState.serverName)
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
     @Test
268
     @Test
257
     fun `reset clears all state`() {
269
     fun `reset clears all state`() {
258
         with(IrcClientImpl(normalConfig)) {
270
         with(IrcClientImpl(normalConfig)) {

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

1
 package com.dmdirc.ktirc.handlers
1
 package com.dmdirc.ktirc.handlers
2
 
2
 
3
+import com.dmdirc.ktirc.BehaviourConfig
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.TestConstants
5
 import com.dmdirc.ktirc.TestConstants
5
 import com.dmdirc.ktirc.events.*
6
 import com.dmdirc.ktirc.events.*
7
 import com.dmdirc.ktirc.model.*
8
 import com.dmdirc.ktirc.model.*
8
 import com.nhaarman.mockitokotlin2.doReturn
9
 import com.nhaarman.mockitokotlin2.doReturn
9
 import com.nhaarman.mockitokotlin2.mock
10
 import com.nhaarman.mockitokotlin2.mock
11
+import com.nhaarman.mockitokotlin2.never
12
+import com.nhaarman.mockitokotlin2.verify
10
 import org.junit.jupiter.api.Assertions.*
13
 import org.junit.jupiter.api.Assertions.*
11
 import org.junit.jupiter.api.Test
14
 import org.junit.jupiter.api.Test
12
 
15
 
15
     private val handler = ChannelStateHandler()
18
     private val handler = ChannelStateHandler()
16
     private val channelStateMap = ChannelStateMap { CaseMapping.Rfc }
19
     private val channelStateMap = ChannelStateMap { CaseMapping.Rfc }
17
     private val serverState = ServerState("", "")
20
     private val serverState = ServerState("", "")
21
+    private val behaviour = BehaviourConfig()
18
     private val ircClient = mock<IrcClient> {
22
     private val ircClient = mock<IrcClient> {
19
         on { serverState } doReturn serverState
23
         on { serverState } doReturn serverState
20
         on { channelState } doReturn channelStateMap
24
         on { channelState } doReturn channelStateMap
25
+        on { behaviour } doReturn behaviour
21
         on { isLocalUser(User("acidburn", "libby", "root.localhost")) } doReturn true
26
         on { isLocalUser(User("acidburn", "libby", "root.localhost")) } doReturn true
22
         on { isLocalUser("acidburn") } doReturn  true
27
         on { isLocalUser("acidburn") } doReturn  true
23
     }
28
     }
116
         assertEquals("v", channel.users["acidBurn"]?.modes)
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
     @Test
176
     @Test
120
     fun `removes state object for local parts`() {
177
     fun `removes state object for local parts`() {
121
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
178
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }

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

29
         verify(mockClient).send("JOIN :#TheGibson")
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
     @Test
38
     @Test
33
     fun `sendNickChange sends correct NICK message`() {
39
     fun `sendNickChange sends correct NICK message`() {
34
         mockClient.sendNickChange("AcidBurn")
40
         mockClient.sendNickChange("AcidBurn")

Loading…
Cancel
Save