瀏覽代碼

Add support for receiving actions and CTCPs

tags/v0.4.0
Chris Smith 5 年之前
父節點
當前提交
b82e16edb1

+ 2
- 0
CHANGELOG 查看文件

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

+ 3
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt 查看文件

128
         socket?.disconnect()
128
         socket?.disconnect()
129
     }
129
     }
130
 
130
 
131
+    /**
132
+     * Joins the coroutine running the message loop, and blocks until it is completed.
133
+     */
131
     suspend fun join() {
134
     suspend fun join() {
132
         connectionJob?.join()
135
         connectionJob?.join()
133
     }
136
     }

+ 7
- 0
src/main/kotlin/com/dmdirc/ktirc/events/EventUtils.kt 查看文件

14
     }
14
     }
15
 }.toList()
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
 fun IrcClient.reply(message: MessageReceived, response: String, prefixWithNickname: Boolean = false) {
24
 fun IrcClient.reply(message: MessageReceived, response: String, prefixWithNickname: Boolean = false) {
18
     if (caseMapping.areEquivalent(message.target, serverState.localNickname)) {
25
     if (caseMapping.areEquivalent(message.target, serverState.localNickname)) {
19
         sendMessage(message.user.nickname, response)
26
         sendMessage(message.user.nickname, response)

+ 6
- 0
src/main/kotlin/com/dmdirc/ktirc/events/Events.kt 查看文件

41
 /** Raised when a message is received. */
41
 /** Raised when a message is received. */
42
 class MessageReceived(time: LocalDateTime, val user: User, val target: String, val message: String) : IrcEvent(time)
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
 /** Raised when a user quits. */
50
 /** Raised when a user quits. */
45
 class UserQuit(time: LocalDateTime, val user: User, val reason: String = "") : IrcEvent(time)
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 查看文件

1
 package com.dmdirc.ktirc.messages
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
 import com.dmdirc.ktirc.events.MessageReceived
6
 import com.dmdirc.ktirc.events.MessageReceived
4
 import com.dmdirc.ktirc.model.IrcMessage
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
 internal class PrivmsgProcessor : MessageProcessor {
12
 internal class PrivmsgProcessor : MessageProcessor {
7
 
13
 
8
     override val commands = arrayOf("PRIVMSG")
14
     override val commands = arrayOf("PRIVMSG")
9
 
15
 
10
     override fun process(message: IrcMessage) = message.sourceUser?.let { user ->
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
     } ?: emptyList()
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 查看文件

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
 sealed class MessageTag(val name: String) {
32
 sealed class MessageTag(val name: String) {
33
     /** Specifies the account name of the user, if the `account-tag` capability is negotiated. */
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 查看文件

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

+ 1
- 1
src/main/kotlin/com/dmdirc/ktirc/util/Time.kt 查看文件

4
 import java.time.ZoneId
4
 import java.time.ZoneId
5
 
5
 
6
 internal var currentTimeZoneProvider = { ZoneId.systemDefault() }
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 查看文件

1
 package com.dmdirc.ktirc.messages
1
 package com.dmdirc.ktirc.messages
2
 
2
 
3
 import com.dmdirc.ktirc.TestConstants
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
 import com.dmdirc.ktirc.model.IrcMessage
7
 import com.dmdirc.ktirc.model.IrcMessage
5
 import com.dmdirc.ktirc.model.User
8
 import com.dmdirc.ktirc.model.User
6
 import com.dmdirc.ktirc.util.currentTimeProvider
9
 import com.dmdirc.ktirc.util.currentTimeProvider
21
                 IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "hack the planet!".toByteArray())))
24
                 IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", listOf("#crashandburn".toByteArray(), "hack the planet!".toByteArray())))
22
         assertEquals(1, events.size)
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
     @Test
101
     @Test

Loading…
取消
儲存