Browse Source

Add support for receiving actions and CTCPs

tags/v0.4.0
Chris Smith 5 years ago
parent
commit
b82e16edb1

+ 2
- 0
CHANGELOG View File

@@ -1,5 +1,7 @@
1 1
 vNEXT (in development)
2 2
 
3
+ * Added CtcpReceived and ActionReceived events
4
+
3 5
 v0.3.1
4 6
 
5 7
   * Added more documentation to public methods/classes

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

@@ -128,6 +128,9 @@ class IrcClientImpl(private val server: Server, private val profile: Profile) :
128 128
         socket?.disconnect()
129 129
     }
130 130
 
131
+    /**
132
+     * Joins the coroutine running the message loop, and blocks until it is completed.
133
+     */
131 134
     suspend fun join() {
132 135
         connectionJob?.join()
133 136
     }

+ 7
- 0
src/main/kotlin/com/dmdirc/ktirc/events/EventUtils.kt View File

@@ -14,6 +14,13 @@ internal fun ChannelNamesReceived.toModesAndUsers(client: IrcClient) = sequence
14 14
     }
15 15
 }.toList()
16 16
 
17
+/**
18
+ * Replies in the appropriate place to a message received.
19
+ *
20
+ * Messages sent direct to the client will be responded to in direct message back; messages sent to a channel
21
+ * will be replied to in the channel. If [prefixWithNickname] is `true`, channel messages will be prefixed
22
+ * with the other user's nickname (separated from the message by a colon and space).
23
+ */
17 24
 fun IrcClient.reply(message: MessageReceived, response: String, prefixWithNickname: Boolean = false) {
18 25
     if (caseMapping.areEquivalent(message.target, serverState.localNickname)) {
19 26
         sendMessage(message.user.nickname, response)

+ 6
- 0
src/main/kotlin/com/dmdirc/ktirc/events/Events.kt View File

@@ -41,6 +41,12 @@ class ChannelNamesFinished(time: LocalDateTime, val channel: String) : IrcEvent(
41 41
 /** Raised when a message is received. */
42 42
 class MessageReceived(time: LocalDateTime, val user: User, val target: String, val message: String) : IrcEvent(time)
43 43
 
44
+/** Raised when an action is received. */
45
+class ActionReceived(time: LocalDateTime, val user: User, val target: String, val action: String) : IrcEvent(time)
46
+
47
+/** Raised when a CTCP is received. */
48
+class CtcpReceived(time: LocalDateTime, val user: User, val target: String, val type: String, val content: String) : IrcEvent(time)
49
+
44 50
 /** Raised when a user quits. */
45 51
 class UserQuit(time: LocalDateTime, val user: User, val reason: String = "") : IrcEvent(time)
46 52
 

+ 22
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/PrivmsgProcessor.kt View File

@@ -1,14 +1,35 @@
1 1
 package com.dmdirc.ktirc.messages
2 2
 
3
+import com.dmdirc.ktirc.events.ActionReceived
4
+import com.dmdirc.ktirc.events.CtcpReceived
5
+import com.dmdirc.ktirc.events.IrcEvent
3 6
 import com.dmdirc.ktirc.events.MessageReceived
4 7
 import com.dmdirc.ktirc.model.IrcMessage
8
+import com.dmdirc.ktirc.model.User
9
+
10
+private const val CTCP_BYTE : Byte = 1
5 11
 
6 12
 internal class PrivmsgProcessor : MessageProcessor {
7 13
 
8 14
     override val commands = arrayOf("PRIVMSG")
9 15
 
10 16
     override fun process(message: IrcMessage) = message.sourceUser?.let { user ->
11
-        listOf(MessageReceived(message.time, user, String(message.params[0]), String(message.params[1])))
17
+        listOf(when {
18
+            message.isCtcp() -> handleCtcp(message, user)
19
+            else -> MessageReceived(message.time, user, String(message.params[0]), String(message.params[1]))
20
+        })
12 21
     } ?: emptyList()
13 22
 
23
+    private fun handleCtcp(message: IrcMessage, user: User): IrcEvent {
24
+        val content = String(message.params[1]).substring(1 until message.params[1].size - 1)
25
+        val parts = content.split(' ', limit=2)
26
+        val body = if (parts.size == 2) parts[1] else ""
27
+        return when (parts[0].toUpperCase()) {
28
+            "ACTION" -> ActionReceived(message.time, user, String(message.params[0]), body)
29
+            else -> CtcpReceived(message.time, user, String(message.params[0]), parts[0], body)
30
+        }
31
+    }
32
+
33
+    private fun IrcMessage.isCtcp() = params[1].size > 2 && params[1][0] == CTCP_BYTE && params[1][params[1].size - 1] == CTCP_BYTE
34
+
14 35
 }

+ 1
- 1
src/main/kotlin/com/dmdirc/ktirc/model/IrcMessage.kt View File

@@ -27,7 +27,7 @@ class IrcMessage(val tags: Map<MessageTag, String>, val prefix: ByteArray?, val
27 27
 }
28 28
 
29 29
 /**
30
- * Supported tags that may be applied to messages
30
+ * Supported tags that may be applied to messages.
31 31
  */
32 32
 sealed class MessageTag(val name: String) {
33 33
     /** Specifies the account name of the user, if the `account-tag` capability is negotiated. */

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

@@ -9,7 +9,9 @@ class UserState(private val caseMappingProvider: () -> CaseMapping): Iterable<Kn
9 9
 
10 10
     private val users = UserMap(caseMappingProvider)
11 11
 
12
+    /** Gets the [KnownUser] with the given nickname. */
12 13
     operator fun get(nickname: String) = users[nickname]
14
+    /** Gets the [KnownUser] for a given user. */
13 15
     operator fun get(user: User) = users[user.nickname]
14 16
 
15 17
     internal operator fun plusAssign(details: User) { users += KnownUser(caseMappingProvider, details) }

+ 1
- 1
src/main/kotlin/com/dmdirc/ktirc/util/Time.kt View File

@@ -4,4 +4,4 @@ import java.time.LocalDateTime
4 4
 import java.time.ZoneId
5 5
 
6 6
 internal var currentTimeZoneProvider = { ZoneId.systemDefault() }
7
-internal var currentTimeProvider = { LocalDateTime.now(currentTimeZoneProvider()) }
7
+internal var currentTimeProvider = { LocalDateTime.now(currentTimeZoneProvider()) }

+ 75
- 4
src/test/kotlin/com/dmdirc/ktirc/messages/PrivmsgProcessorTest.kt View File

@@ -1,6 +1,9 @@
1 1
 package com.dmdirc.ktirc.messages
2 2
 
3 3
 import com.dmdirc.ktirc.TestConstants
4
+import com.dmdirc.ktirc.events.ActionReceived
5
+import com.dmdirc.ktirc.events.CtcpReceived
6
+import com.dmdirc.ktirc.events.MessageReceived
4 7
 import com.dmdirc.ktirc.model.IrcMessage
5 8
 import com.dmdirc.ktirc.model.User
6 9
 import com.dmdirc.ktirc.util.currentTimeProvider
@@ -21,10 +24,78 @@ internal class PrivmsgProcessorTest {
21 24
                 IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "hack the planet!".toByteArray())))
22 25
         assertEquals(1, events.size)
23 26
 
24
-        assertEquals(TestConstants.time, events[0].time)
25
-        assertEquals(User("acidburn", "libby", "root.localhost"), events[0].user)
26
-        assertEquals("#crashandburn", events[0].target)
27
-        assertEquals("hack the planet!", events[0].message)
27
+        val event = events[0] as MessageReceived
28
+        assertEquals(TestConstants.time, event.time)
29
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
30
+        assertEquals("#crashandburn", event.target)
31
+        assertEquals("hack the planet!", event.message)
32
+    }
33
+
34
+    @Test
35
+    fun `PrivsgProcessor raises action received event with content`() {
36
+        val events = PrivmsgProcessor().process(
37
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "\u0001ACTION hacks the planet\u0001".toByteArray())))
38
+        assertEquals(1, events.size)
39
+
40
+        val event = events[0] as ActionReceived
41
+        assertEquals(TestConstants.time, event.time)
42
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
43
+        assertEquals("#crashandburn", event.target)
44
+        assertEquals("hacks the planet", event.action)
45
+    }
46
+
47
+    @Test
48
+    fun `PrivsgProcessor raises action received event without content`() {
49
+        val events = PrivmsgProcessor().process(
50
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "\u0001ACTION\u0001".toByteArray())))
51
+        assertEquals(1, events.size)
52
+
53
+        val event = events[0] as ActionReceived
54
+        assertEquals(TestConstants.time, event.time)
55
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
56
+        assertEquals("#crashandburn", event.target)
57
+        assertEquals("", event.action)
58
+    }
59
+
60
+    @Test
61
+    fun `PrivsgProcessor raises action received event with lowercase type`() {
62
+        val events = PrivmsgProcessor().process(
63
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "\u0001action hacks the planet\u0001".toByteArray())))
64
+        assertEquals(1, events.size)
65
+
66
+        val event = events[0] as ActionReceived
67
+        assertEquals(TestConstants.time, event.time)
68
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
69
+        assertEquals("#crashandburn", event.target)
70
+        assertEquals("hacks the planet", event.action)
71
+    }
72
+
73
+    @Test
74
+    fun `PrivsgProcessor raises CTCP received event with content`() {
75
+        val events = PrivmsgProcessor().process(
76
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "\u0001PING 12345\u0001".toByteArray())))
77
+        assertEquals(1, events.size)
78
+
79
+        val event = events[0] as CtcpReceived
80
+        assertEquals(TestConstants.time, event.time)
81
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
82
+        assertEquals("#crashandburn", event.target)
83
+        assertEquals("PING", event.type)
84
+        assertEquals("12345", event.content)
85
+    }
86
+
87
+    @Test
88
+    fun `PrivsgProcessor raises CTCP received event without content`() {
89
+        val events = PrivmsgProcessor().process(
90
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "\u0001PING\u0001".toByteArray())))
91
+        assertEquals(1, events.size)
92
+
93
+        val event = events[0] as CtcpReceived
94
+        assertEquals(TestConstants.time, event.time)
95
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
96
+        assertEquals("#crashandburn", event.target)
97
+        assertEquals("PING", event.type)
98
+        assertEquals("", event.content)
28 99
     }
29 100
 
30 101
     @Test

Loading…
Cancel
Save