Browse Source

Support IRCv3 away notifications

Issue #15
tags/v1.1.0
Chris Smith 5 years ago
parent
commit
4e5aa5b827

+ 3
- 0
CHANGELOG View File

1
 vNEXT (in development)
1
 vNEXT (in development)
2
 
2
 
3
+ * Away support:
4
+   * Added sendAway() method
5
+   * Added UserAway event and fanned-out ChannelAway
3
  * (Internal) improved the way byte buffers are used to
6
  * (Internal) improved the way byte buffers are used to
4
    reduce array copying and clean up code
7
    reduce array copying and clean up code
5
 
8
 

+ 65
- 38
docs/index.adoc View File

964
 Raised when a user changes the topic of a channel we are joined to. If
964
 Raised when a user changes the topic of a channel we are joined to. If
965
 the topic was cleared/removed, the `topic` parameter will be `null`.
965
 the topic was cleared/removed, the `topic` parameter will be `null`.
966
 
966
 
967
+==== ChannelAway
968
+* Type: IrcEvent, TargetedEvent, SourcedEvent
969
+* Properties:
970
+** `user`: `User` - the user whose away state has changed
971
+** `target`: `String` - the channel that the user is in
972
+** `message`: `String?` - the away message, or `null` if the user is back
973
+
974
+After a <<UserAway>> event, KtIrc will "fan out" the event to all of the
975
+channels that we share with the user and raise a `ChannelAway`
976
+event for each channel. This is designed to make implementing certain features
977
+easier; if you fully handle a UserAway event there is no need to also
978
+handle the ChannelAway events, and vice-versa.
979
+
967
 === Channel/User events
980
 === Channel/User events
968
 
981
 
969
 TODO
982
 TODO
988
 
1001
 
989
 TODO
1002
 TODO
990
 
1003
 
1004
+==== UserAway
1005
+* Type: IrcEvent, SourcedEvent
1006
+* Properties:
1007
+** `user`: `User` - the user who has changed their away state
1008
+** `message`: `String?` - the away message, or `null` if the user is back
1009
+
1010
+Raised when we are informed that a user has changed away states. If the server
1011
+supports the `away-notify` capability we will receive notifications for all
1012
+users in our common channels; otherwise, we will only receive notifications
1013
+for our own user.
1014
+
1015
+If the user is away but we don't know the reason for it, the `message`
1016
+property will be empty.
1017
+
991
 ==== UserQuit
1018
 ==== UserQuit
992
 
1019
 
993
 TODO
1020
 TODO
1136
 3+h| Capability negotiation
1163
 3+h| Capability negotiation
1137
 
1164
 
1138
 | https://ircv3.net/specs/core/capability-negotiation.html[CAP]
1165
 | https://ircv3.net/specs/core/capability-negotiation.html[CAP]
1139
-| {set:cellbgcolor:green} Supported
1166
+| {set:cellbgcolor:#a7eeaa} Supported
1140
 | {set:cellbgcolor!}
1167
 | {set:cellbgcolor!}
1141
 See <<Supported capabilities>> for the caps KtIrc will negotiate
1168
 See <<Supported capabilities>> for the caps KtIrc will negotiate
1142
 
1169
 
1143
 | https://ircv3.net/specs/core/capability-negotiation.html#cap-ls-version[CAP 302]
1170
 | https://ircv3.net/specs/core/capability-negotiation.html#cap-ls-version[CAP 302]
1144
-| {set:cellbgcolor:green} Supported
1171
+| {set:cellbgcolor:#a7eeaa} Supported
1145
 | {set:cellbgcolor!}
1172
 | {set:cellbgcolor!}
1146
 See <<Supported capabilities>> for the caps KtIrc will negotiate
1173
 See <<Supported capabilities>> for the caps KtIrc will negotiate
1147
 
1174
 
1148
 | https://ircv3.net/specs/core/capability-negotiation.html#cap-notify[cap-notify]
1175
 | https://ircv3.net/specs/core/capability-negotiation.html#cap-notify[cap-notify]
1149
-| {set:cellbgcolor:red} Not yet supported
1176
+| {set:cellbgcolor:#f7d5d3} No support
1150
 | {set:cellbgcolor!}
1177
 | {set:cellbgcolor!}
1151
 
1178
 
1152
 3+h| Published specifications
1179
 3+h| Published specifications
1153
 
1180
 
1154
 | https://ircv3.net/specs/extensions/account-notify-3.1.html[account-notify] v3.1
1181
 | https://ircv3.net/specs/extensions/account-notify-3.1.html[account-notify] v3.1
1155
-| {set:cellbgcolor:green} Supported
1182
+| {set:cellbgcolor:#a7eeaa} Supported
1156
 | {set:cellbgcolor!}
1183
 | {set:cellbgcolor!}
1157
 See <<UserAccountChanged>>
1184
 See <<UserAccountChanged>>
1158
 
1185
 
1159
 | https://ircv3.net/specs/extensions/account-tag-3.2.html[account-tag] v3.2
1186
 | https://ircv3.net/specs/extensions/account-tag-3.2.html[account-tag] v3.2
1160
-| {set:cellbgcolor:green} Supported
1187
+| {set:cellbgcolor:#a7eeaa} Supported
1161
 | {set:cellbgcolor!}
1188
 | {set:cellbgcolor!}
1162
 Accounts are automatically added to `User` properties in events
1189
 Accounts are automatically added to `User` properties in events
1163
 
1190
 
1164
 | https://ircv3.net/specs/extensions/away-notify-3.1.html[away-notify] v3.1
1191
 | https://ircv3.net/specs/extensions/away-notify-3.1.html[away-notify] v3.1
1165
-| {set:cellbgcolor:orange} Partial support
1192
+| {set:cellbgcolor:#eeeeaa} Partial support
1166
 | {set:cellbgcolor!}
1193
 | {set:cellbgcolor!}
1167
-Negotiated but not yet exposed as an event
1194
+See <<UserAway>>. State not yet tracked.
1168
 
1195
 
1169
 | https://ircv3.net/specs/extensions/batch-3.2.html[batch] v3.2
1196
 | https://ircv3.net/specs/extensions/batch-3.2.html[batch] v3.2
1170
-| {set:cellbgcolor:green} Supported
1197
+| {set:cellbgcolor:#a7eeaa} Supported
1171
 | {set:cellbgcolor!}
1198
 | {set:cellbgcolor!}
1172
 See <<BatchReceived>>
1199
 See <<BatchReceived>>
1173
 
1200
 
1174
 | https://ircv3.net/specs/extensions/chghost-3.2.html[chghost] v3.2
1201
 | https://ircv3.net/specs/extensions/chghost-3.2.html[chghost] v3.2
1175
-| {set:cellbgcolor:green} Supported
1202
+| {set:cellbgcolor:#a7eeaa} Supported
1176
 | {set:cellbgcolor!}
1203
 | {set:cellbgcolor!}
1177
 See <<UserHostChanged>>
1204
 See <<UserHostChanged>>
1178
 
1205
 
1179
 | https://ircv3.net/specs/extensions/echo-message-3.2.html[echo-message] v3.2
1206
 | https://ircv3.net/specs/extensions/echo-message-3.2.html[echo-message] v3.2
1180
-| {set:cellbgcolor:green} Supported
1207
+| {set:cellbgcolor:#a7eeaa} Supported
1181
 | {set:cellbgcolor!}
1208
 | {set:cellbgcolor!}
1182
 See also the `alwaysEchoMessages` <<Behaviour>>
1209
 See also the `alwaysEchoMessages` <<Behaviour>>
1183
 
1210
 
1184
 | https://ircv3.net/specs/extensions/extended-join-3.1.html[extended-join] v3.1
1211
 | https://ircv3.net/specs/extensions/extended-join-3.1.html[extended-join] v3.1
1185
-| {set:cellbgcolor:green} Supported
1212
+| {set:cellbgcolor:#a7eeaa} Supported
1186
 | {set:cellbgcolor!}
1213
 | {set:cellbgcolor!}
1187
 Additional details are automatically added to `User` properties in events
1214
 Additional details are automatically added to `User` properties in events
1188
 
1215
 
1189
 | https://ircv3.net/specs/extensions/invite-notify-3.2.html[invite-notify] v3.2
1216
 | https://ircv3.net/specs/extensions/invite-notify-3.2.html[invite-notify] v3.2
1190
-| {set:cellbgcolor:red} Not yet supported
1217
+| {set:cellbgcolor:#f7d5d3} No support
1191
 | {set:cellbgcolor!}
1218
 | {set:cellbgcolor!}
1192
 
1219
 
1193
 | https://ircv3.net/specs/extensions/message-tags.html[message-tags]
1220
 | https://ircv3.net/specs/extensions/message-tags.html[message-tags]
1194
-| {set:cellbgcolor:green} Supported
1221
+| {set:cellbgcolor:#a7eeaa} Supported
1195
 | {set:cellbgcolor!}
1222
 | {set:cellbgcolor!}
1196
 Exposed in the metadata property of <<Events>>
1223
 Exposed in the metadata property of <<Events>>
1197
 
1224
 
1198
 | https://ircv3.net/specs/core/monitor-3.2.html[monitor]
1225
 | https://ircv3.net/specs/core/monitor-3.2.html[monitor]
1199
-| {set:cellbgcolor:red} Not yet supported
1226
+| {set:cellbgcolor:#f7d5d3} No support
1200
 | {set:cellbgcolor!}
1227
 | {set:cellbgcolor!}
1201
 
1228
 
1202
 | https://ircv3.net/specs/extensions/multi-prefix-3.1.html[multi-prefix] v3.1
1229
 | https://ircv3.net/specs/extensions/multi-prefix-3.1.html[multi-prefix] v3.1
1203
-| {set:cellbgcolor:green} Supported
1230
+| {set:cellbgcolor:#a7eeaa} Supported
1204
 | {set:cellbgcolor!}
1231
 | {set:cellbgcolor!}
1205
 Automatically included in <<ChannelState>>
1232
 Automatically included in <<ChannelState>>
1206
 
1233
 
1207
 | https://ircv3.net/specs/extensions/sasl-3.1.html[SASL] v3.1
1234
 | https://ircv3.net/specs/extensions/sasl-3.1.html[SASL] v3.1
1208
-| {set:cellbgcolor:green} Supported
1235
+| {set:cellbgcolor:#a7eeaa} Supported
1209
 | {set:cellbgcolor!}
1236
 | {set:cellbgcolor!}
1210
 See <<SASL configuration>>
1237
 See <<SASL configuration>>
1211
 
1238
 
1212
 | https://ircv3.net/specs/extensions/sasl-3.2.html[SASL] v3.2
1239
 | https://ircv3.net/specs/extensions/sasl-3.2.html[SASL] v3.2
1213
-| {set:cellbgcolor:orange} Partial support
1240
+| {set:cellbgcolor:#eeeeaa} Partial support
1214
 | {set:cellbgcolor!}
1241
 | {set:cellbgcolor!}
1215
 Notifications via `cap-notify` not yet supported. See <<SASL configuration>>
1242
 Notifications via `cap-notify` not yet supported. See <<SASL configuration>>
1216
 
1243
 
1217
 | https://ircv3.net/specs/extensions/server-time-3.2.html[server-time] v3.2
1244
 | https://ircv3.net/specs/extensions/server-time-3.2.html[server-time] v3.2
1218
-| {set:cellbgcolor:green} Supported
1245
+| {set:cellbgcolor:#a7eeaa} Supported
1219
 | {set:cellbgcolor!}
1246
 | {set:cellbgcolor!}
1220
 Exposed in the metadata property of <<Events>>
1247
 Exposed in the metadata property of <<Events>>
1221
 
1248
 
1222
 | https://ircv3.net/specs/extensions/sts.html[sts]
1249
 | https://ircv3.net/specs/extensions/sts.html[sts]
1223
-| {set:cellbgcolor:red} Not yet supported
1250
+| {set:cellbgcolor:#f7d5d3} No support
1224
 | {set:cellbgcolor!}
1251
 | {set:cellbgcolor!}
1225
 
1252
 
1226
 | https://ircv3.net/specs/extensions/userhost-in-names-3.2.html[userhost-in-names] v3.2
1253
 | https://ircv3.net/specs/extensions/userhost-in-names-3.2.html[userhost-in-names] v3.2
1227
-| {set:cellbgcolor:green} Supported
1254
+| {set:cellbgcolor:#a7eeaa} Supported
1228
 | {set:cellbgcolor!}
1255
 | {set:cellbgcolor!}
1229
 Automatically included in <<UserState>>
1256
 Automatically included in <<UserState>>
1230
 
1257
 
1231
 | https://ircv3.net/specs/extensions/webirc.html[webirc]
1258
 | https://ircv3.net/specs/extensions/webirc.html[webirc]
1232
-| {set:cellbgcolor:red} Not yet supported
1259
+| {set:cellbgcolor:#f7d5d3} No support
1233
 | {set:cellbgcolor!}
1260
 | {set:cellbgcolor!}
1234
 
1261
 
1235
 3+h| Draft specifications
1262
 3+h| Draft specifications
1236
 
1263
 
1237
 | https://github.com/ircv3/ircv3-specifications/pull/363[brb]
1264
 | https://github.com/ircv3/ircv3-specifications/pull/363[brb]
1238
-| {set:cellbgcolor:red} Not yet supported
1265
+| {set:cellbgcolor:#f7d5d3} No support
1239
 | {set:cellbgcolor!}
1266
 | {set:cellbgcolor!}
1240
 
1267
 
1241
 | https://github.com/ircv3/ircv3-specifications/pull/308[channel renaming]
1268
 | https://github.com/ircv3/ircv3-specifications/pull/308[channel renaming]
1242
-| {set:cellbgcolor:red} Not yet supported
1269
+| {set:cellbgcolor:#f7d5d3} No support
1243
 | {set:cellbgcolor!}
1270
 | {set:cellbgcolor!}
1244
 
1271
 
1245
 | https://github.com/ircv3/ircv3-specifications/pull/349[chathistory]
1272
 | https://github.com/ircv3/ircv3-specifications/pull/349[chathistory]
1246
-| {set:cellbgcolor:red} Not yet supported
1273
+| {set:cellbgcolor:#f7d5d3} No support
1247
 | {set:cellbgcolor!}
1274
 | {set:cellbgcolor!}
1248
 
1275
 
1249
 | https://github.com/ircv3/ircv3-specifications/pull/346[delivered]
1276
 | https://github.com/ircv3/ircv3-specifications/pull/346[delivered]
1250
-| {set:cellbgcolor:red} Not yet supported
1277
+| {set:cellbgcolor:#f7d5d3} No support
1251
 | {set:cellbgcolor!}
1278
 | {set:cellbgcolor!}
1252
 
1279
 
1253
 | https://github.com/ircv3/ircv3-specifications/pull/304[editmsg]
1280
 | https://github.com/ircv3/ircv3-specifications/pull/304[editmsg]
1254
-| {set:cellbgcolor:red} Not yet supported
1281
+| {set:cellbgcolor:#f7d5d3} No support
1255
 | {set:cellbgcolor!}
1282
 | {set:cellbgcolor!}
1256
 
1283
 
1257
 | https://ircv3.net/specs/extensions/labeled-response.html[labeled-response]
1284
 | https://ircv3.net/specs/extensions/labeled-response.html[labeled-response]
1258
-| {set:cellbgcolor:green} Supported
1285
+| {set:cellbgcolor:#a7eeaa} Supported
1259
 | {set:cellbgcolor!}
1286
 | {set:cellbgcolor!}
1260
 Exposed in the metadata property of <<Events>>
1287
 Exposed in the metadata property of <<Events>>
1261
 
1288
 
1262
 | https://ircv3.net/specs/extensions/message-ids.html[message-ids]
1289
 | https://ircv3.net/specs/extensions/message-ids.html[message-ids]
1263
-| {set:cellbgcolor:red} Not yet supported
1290
+| {set:cellbgcolor:#f7d5d3} No support
1264
 | {set:cellbgcolor!}
1291
 | {set:cellbgcolor!}
1265
 
1292
 
1266
 | https://github.com/ircv3/ircv3-specifications/pull/330[migrate]
1293
 | https://github.com/ircv3/ircv3-specifications/pull/330[migrate]
1267
-| {set:cellbgcolor:red} Not yet supported
1294
+| {set:cellbgcolor:#f7d5d3} No support
1268
 | {set:cellbgcolor!}
1295
 | {set:cellbgcolor!}
1269
 
1296
 
1270
 | https://ircv3.net/specs/client-tags/react.html[react]
1297
 | https://ircv3.net/specs/client-tags/react.html[react]
1271
-| {set:cellbgcolor:orange} Partial support
1298
+| {set:cellbgcolor:#eeeeaa} Partial support
1272
 | {set:cellbgcolor!}
1299
 | {set:cellbgcolor!}
1273
 Sending via <<React>> method, no events generated
1300
 Sending via <<React>> method, no events generated
1274
 
1301
 
1275
 | https://github.com/ircv3/ircv3-specifications/pull/347[read]
1302
 | https://github.com/ircv3/ircv3-specifications/pull/347[read]
1276
-| {set:cellbgcolor:red} Not yet supported
1303
+| {set:cellbgcolor:#f7d5d3} No support
1277
 | {set:cellbgcolor!}
1304
 | {set:cellbgcolor!}
1278
 
1305
 
1279
 | https://github.com/ircv3/ircv3-specifications/pull/276[register]
1306
 | https://github.com/ircv3/ircv3-specifications/pull/276[register]
1280
-| {set:cellbgcolor:red} Not yet supported
1307
+| {set:cellbgcolor:#f7d5d3} No support
1281
 | {set:cellbgcolor!}
1308
 | {set:cellbgcolor!}
1282
 
1309
 
1283
 | https://ircv3.net/specs/client-tags/reply.html[reply]
1310
 | https://ircv3.net/specs/client-tags/reply.html[reply]
1284
-| {set:cellbgcolor:orange} Partial support
1311
+| {set:cellbgcolor:#eeeeaa} Partial support
1285
 | {set:cellbgcolor!}
1312
 | {set:cellbgcolor!}
1286
 Sending via <<Reply>> method, not processed on incoming messages
1313
 Sending via <<Reply>> method, not processed on incoming messages
1287
 
1314
 
1288
 | https://github.com/ircv3/ircv3-specifications/pull/306[resume]
1315
 | https://github.com/ircv3/ircv3-specifications/pull/306[resume]
1289
-| {set:cellbgcolor:red} Not yet supported
1316
+| {set:cellbgcolor:#f7d5d3} No support
1290
 | {set:cellbgcolor!}
1317
 | {set:cellbgcolor!}
1291
 
1318
 
1292
 | https://github.com/ircv3/ircv3-specifications/pull/361[setname]
1319
 | https://github.com/ircv3/ircv3-specifications/pull/361[setname]
1293
-| {set:cellbgcolor:red} Not yet supported
1320
+| {set:cellbgcolor:#f7d5d3} No support
1294
 | {set:cellbgcolor!}
1321
 | {set:cellbgcolor!}
1295
 
1322
 
1296
 | https://github.com/ircv3/ircv3-specifications/pull/357[standard replies]
1323
 | https://github.com/ircv3/ircv3-specifications/pull/357[standard replies]
1297
-| {set:cellbgcolor:red} Not yet supported
1324
+| {set:cellbgcolor:#f7d5d3} No support
1298
 | {set:cellbgcolor!}
1325
 | {set:cellbgcolor!}
1299
 
1326
 
1300
 | https://github.com/ircv3/ircv3-specifications/pull/348[typing]
1327
 | https://github.com/ircv3/ircv3-specifications/pull/348[typing]
1301
-| {set:cellbgcolor:red} Not yet supported
1328
+| {set:cellbgcolor:#f7d5d3} No support
1302
 | {set:cellbgcolor!}
1329
 | {set:cellbgcolor!}
1303
 
1330
 
1304
-3+^h|Vendor specifications
1331
+3+h|Vendor specifications
1305
 
1332
 
1306
 |===
1333
 |===

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

177
     override val replacedUsers: Array<String>? = null
177
     override val replacedUsers: Array<String>? = null
178
 }
178
 }
179
 
179
 
180
+/**
181
+ * Raised when a user's away state changes, and they are in a common channel. If [message] is null then the user is
182
+ * now back.
183
+ *
184
+ * This event is only raised for other users if the server supports the 'away-notify' capability.
185
+ */
186
+class ChannelAway(metadata: EventMetadata, override val user: User, channel: String, val message: String?)
187
+    : TargetedEvent(metadata, channel), SourcedEvent
188
+
180
 /** Raised when a batch of the channel's member list has been received. More batches may follow. */
189
 /** Raised when a batch of the channel's member list has been received. More batches may follow. */
181
 class ChannelNamesReceived(metadata: EventMetadata, channel: String, val names: List<String>) : TargetedEvent(metadata, channel)
190
 class ChannelNamesReceived(metadata: EventMetadata, channel: String, val names: List<String>) : TargetedEvent(metadata, channel)
182
 
191
 
252
  */
261
  */
253
 class UserAccountChanged(metadata: EventMetadata, override val user: User, val newAccount: String?) : IrcEvent(metadata), SourcedEvent
262
 class UserAccountChanged(metadata: EventMetadata, override val user: User, val newAccount: String?) : IrcEvent(metadata), SourcedEvent
254
 
263
 
264
+/**
265
+ * Raised when a user's away state changes. If [message] is null then the user is now back.
266
+ *
267
+ * This event is only raised for other users if the server supports the 'away-notify' capability.
268
+ */
269
+class UserAway(metadata: EventMetadata, override val user: User, val message: String?) : IrcEvent(metadata), SourcedEvent
270
+
255
 /** Raised when available server capabilities are received. More batches may follow. */
271
 /** Raised when available server capabilities are received. More batches may follow. */
256
 class ServerCapabilitiesReceived(metadata: EventMetadata, val capabilities: Map<String, String>) : IrcEvent(metadata)
272
 class ServerCapabilitiesReceived(metadata: EventMetadata, val capabilities: Map<String, String>) : IrcEvent(metadata)
257
 
273
 

+ 10
- 0
src/main/kotlin/com/dmdirc/ktirc/events/mutators/ChannelFanOutMutator.kt View File

12
     override fun mutateEvent(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent) = sequence<IrcEvent> {
12
     override fun mutateEvent(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent) = sequence<IrcEvent> {
13
         yield(event)
13
         yield(event)
14
         when (event) {
14
         when (event) {
15
+            is UserAway -> handleAway(client, event)
15
             is UserQuit -> handleQuit(client, event)
16
             is UserQuit -> handleQuit(client, event)
16
             is UserNickChanged -> handleNickChanged(client, event)
17
             is UserNickChanged -> handleNickChanged(client, event)
17
         }
18
         }
18
     }.toList()
19
     }.toList()
19
 
20
 
21
+
20
     private suspend fun SequenceScope<IrcEvent>.handleQuit(client: IrcClient, event: UserQuit) {
22
     private suspend fun SequenceScope<IrcEvent>.handleQuit(client: IrcClient, event: UserQuit) {
21
         client.channelState.forEach {
23
         client.channelState.forEach {
22
             if (it.users.contains(event.user.nickname)) {
24
             if (it.users.contains(event.user.nickname)) {
25
         }
27
         }
26
     }
28
     }
27
 
29
 
30
+    private suspend fun SequenceScope<IrcEvent>.handleAway(client: IrcClient, event: UserAway) {
31
+        client.channelState.forEach {
32
+            if (it.users.contains(event.user.nickname)) {
33
+                yield(ChannelAway(event.metadata, event.user, it.name, event.message))
34
+            }
35
+        }
36
+    }
37
+
28
     private suspend fun SequenceScope<IrcEvent>.handleNickChanged(client: IrcClient, event: UserNickChanged) {
38
     private suspend fun SequenceScope<IrcEvent>.handleNickChanged(client: IrcClient, event: UserNickChanged) {
29
         client.channelState.forEach {
39
         client.channelState.forEach {
30
             it.users[event.user.nickname]?.let { chanUser ->
40
             it.users[event.user.nickname]?.let { chanUser ->

+ 25
- 0
src/main/kotlin/com/dmdirc/ktirc/messages/InternalMessageBuilders.kt View File

1
+package com.dmdirc.ktirc.messages
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+
5
+/** Sends a message to ask the server to list capabilities. */
6
+internal fun IrcClient.sendCapabilityList() = send("CAP", "LS", "302")
7
+
8
+/** Sends a message indicating the end of capability negotiation. */
9
+internal fun IrcClient.sendCapabilityEnd() = send("CAP", "END")
10
+
11
+/** Sends a message requesting the specified caps are enabled. */
12
+internal fun IrcClient.sendCapabilityRequest(capabilities: Collection<String>) =
13
+        send("CAP", "REQ", capabilities.joinToString(" "))
14
+
15
+/** Sends the connection password to the server. */
16
+internal fun IrcClient.sendPassword(password: String) = send("PASS", password)
17
+
18
+/** Sends a response to a PING event. */
19
+internal fun IrcClient.sendPong(nonce: ByteArray) = send("PONG", String(nonce))
20
+
21
+/** Sends a message to register a user with the server. */
22
+internal fun IrcClient.sendUser(userName: String, realName: String) = send("USER", userName, "0", "*", realName)
23
+
24
+/** Starts an authentication request. */
25
+internal fun IrcClient.sendAuthenticationMessage(data: String = "+") = send("AUTHENTICATE", data)

+ 5
- 21
src/main/kotlin/com/dmdirc/ktirc/messages/MessageBuilders.kt View File

3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.model.MessageTag
4
 import com.dmdirc.ktirc.model.MessageTag
5
 
5
 
6
-/** Sends a message to ask the server to list capabilities. */
7
-internal fun IrcClient.sendCapabilityList() = send("CAP", "LS", "302")
8
-
9
-/** Sends a message indicating the end of capability negotiation. */
10
-internal fun IrcClient.sendCapabilityEnd() = send("CAP", "END")
11
-
12
-/** Sends a message requesting the specified caps are enabled. */
13
-internal fun IrcClient.sendCapabilityRequest(capabilities: Collection<String>) =
14
-        send("CAP", "REQ", capabilities.joinToString(" "))
15
-
16
 /** Sends a request to join the given channel. */
6
 /** Sends a request to join the given channel. */
17
 fun IrcClient.sendJoin(channel: String) = send("JOIN", channel)
7
 fun IrcClient.sendJoin(channel: String) = send("JOIN", channel)
18
 
8
 
26
 /** Sends a request to change to the given nickname. */
16
 /** Sends a request to change to the given nickname. */
27
 fun IrcClient.sendNickChange(nick: String) = send("NICK", nick)
17
 fun IrcClient.sendNickChange(nick: String) = send("NICK", nick)
28
 
18
 
29
-/** Sends the connection password to the server. */
30
-internal fun IrcClient.sendPassword(password: String) = send("PASS", password)
31
-
32
-/** Sends a response to a PING event. */
33
-internal fun IrcClient.sendPong(nonce: ByteArray) = send("PONG", String(nonce))
19
+/** Sends a request to set or unset our away state. */
20
+fun IrcClient.sendAway(reason: String? = null) =
21
+        // We need to pass emptyMap() in the second case to avoid using the deprecated send(String) method
22
+        // Once that's removed, the redundant map can be taken out.
23
+        reason?.let { send("AWAY", reason) } ?: send(emptyMap(), "AWAY")
34
 
24
 
35
 /** Sends a CTCP message of the specified [type] and with optional [data] to [target] (a user or a channel). */
25
 /** Sends a CTCP message of the specified [type] and with optional [data] to [target] (a user or a channel). */
36
 fun IrcClient.sendCtcp(target: String, type: String, data: String? = null) =
26
 fun IrcClient.sendCtcp(target: String, type: String, data: String? = null) =
56
     send(inReplyTo?.let { tags + (MessageTag.Reply to inReplyTo) } ?: tags, "TAGMSG", target)
46
     send(inReplyTo?.let { tags + (MessageTag.Reply to inReplyTo) } ?: tags, "TAGMSG", target)
57
 }
47
 }
58
 
48
 
59
-/** Sends a message to register a user with the server. */
60
-internal fun IrcClient.sendUser(userName: String, realName: String) = send("USER", userName, "0", "*", realName)
61
-
62
-/** Starts an authentication request. */
63
-internal fun IrcClient.sendAuthenticationMessage(data: String = "+") = send("AUTHENTICATE", data)
64
-
65
 /**
49
 /**
66
  * Utility method for creating a map of tags to avoid type inference problems.
50
  * Utility method for creating a map of tags to avoid type inference problems.
67
  */
51
  */

+ 21
- 0
src/main/kotlin/com/dmdirc/ktirc/messages/processors/AwayProcessor.kt View File

1
+package com.dmdirc.ktirc.messages.processors
2
+
3
+import com.dmdirc.ktirc.events.UserAway
4
+import com.dmdirc.ktirc.model.IrcMessage
5
+import com.dmdirc.ktirc.model.asUser
6
+
7
+internal class AwayProcessor : MessageProcessor {
8
+
9
+    override val commands = arrayOf("AWAY")
10
+
11
+    override fun process(message: IrcMessage) = sequence {
12
+        when (message.command) {
13
+            "AWAY" -> message.prefix?.let { yield(UserAway(message.metadata, it.asUser(), message.awayMessage)) }
14
+        }
15
+    }.toList()
16
+
17
+    private val IrcMessage.awayMessage: String?
18
+        get() = if (params.isEmpty()) null else String(params[0])
19
+
20
+}
21
+

+ 1
- 0
src/main/kotlin/com/dmdirc/ktirc/messages/processors/MessageProcessor.kt View File

19
 
19
 
20
 internal val messageProcessors = listOf(
20
 internal val messageProcessors = listOf(
21
         AccountProcessor(),
21
         AccountProcessor(),
22
+        AwayProcessor(),
22
         AuthenticationProcessor(),
23
         AuthenticationProcessor(),
23
         BatchProcessor(),
24
         BatchProcessor(),
24
         CapabilityProcessor(),
25
         CapabilityProcessor(),

+ 36
- 0
src/test/kotlin/com/dmdirc/ktirc/events/mutators/ChannelFanOutMutatorTest.kt View File

64
         assertTrue("#dumpsterdiving" in names)
64
         assertTrue("#dumpsterdiving" in names)
65
     }
65
     }
66
 
66
 
67
+    @Test
68
+    fun `raises ChannelAway event for each channel a user shares when away`() {
69
+        with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
70
+            users += ChannelUser("ZeroCool")
71
+            fakeChannelStateMap += this
72
+        }
73
+
74
+        with (ChannelState("#dumpsterdiving") { CaseMapping.Rfc }) {
75
+            users += ChannelUser("ZeroCool")
76
+            fakeChannelStateMap += this
77
+        }
78
+
79
+        with (ChannelState("#chat") { CaseMapping.Rfc }) {
80
+            users += ChannelUser("AcidBurn")
81
+            fakeChannelStateMap += this
82
+        }
83
+
84
+        val quitEvent = UserAway(EventMetadata(TestConstants.time), User("zerocool", "dade", "root.localhost"), "Hack the planet!")
85
+        val events = mutator.mutateEvent(ircClient, messageEmitter, quitEvent)
86
+
87
+        val names = mutableListOf<String>()
88
+        assertEquals(3, events.size)
89
+        assertSame(quitEvent, events[0])
90
+        events.subList(1, events.size).forEach { event ->
91
+            (event as ChannelAway).let {
92
+                assertEquals(TestConstants.time, it.metadata.time)
93
+                assertEquals("zerocool", it.user.nickname)
94
+                assertEquals("Hack the planet!", it.message)
95
+                names.add(it.target)
96
+            }
97
+        }
98
+
99
+        assertTrue("#thegibson" in names)
100
+        assertTrue("#dumpsterdiving" in names)
101
+    }
102
+
67
     @Test
103
     @Test
68
     fun `raises ChannelNickChanged event for each channel a user changes nicks in`() {
104
     fun `raises ChannelNickChanged event for each channel a user changes nicks in`() {
69
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {
105
         with (ChannelState("#thegibson") { CaseMapping.Rfc }) {

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

52
         verify { mockClient.send("NICK", "AcidBurn") }
52
         verify { mockClient.send("NICK", "AcidBurn") }
53
     }
53
     }
54
 
54
 
55
+    @Test
56
+    fun `sendAway sends blank AWAY message with no args`() {
57
+        mockClient.sendAway()
58
+        verify { mockClient.send(emptyMap(), "AWAY") }
59
+    }
60
+
61
+    @Test
62
+    fun `sendAway sends AWAY message with reason`() {
63
+        mockClient.sendAway("Hacking the planet")
64
+        verify { mockClient.send("AWAY", "Hacking the planet") }
65
+    }
66
+
55
     @Test
67
     @Test
56
     fun `sendPassword sends correct PASS message`() {
68
     fun `sendPassword sends correct PASS message`() {
57
         mockClient.sendPassword("hacktheplanet")
69
         mockClient.sendPassword("hacktheplanet")

+ 51
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/processors/AwayProcessorTest.kt View File

1
+package com.dmdirc.ktirc.messages.processors
2
+
3
+import com.dmdirc.ktirc.TestConstants
4
+import com.dmdirc.ktirc.model.IrcMessage
5
+import com.dmdirc.ktirc.model.User
6
+import com.dmdirc.ktirc.params
7
+import com.dmdirc.ktirc.util.currentTimeProvider
8
+import org.junit.jupiter.api.Assertions
9
+import org.junit.jupiter.api.Assertions.assertEquals
10
+import org.junit.jupiter.api.Assertions.assertNull
11
+import org.junit.jupiter.api.BeforeEach
12
+import org.junit.jupiter.api.Test
13
+
14
+internal class AwayProcessorTest {
15
+
16
+    @BeforeEach
17
+    fun setUp() {
18
+        currentTimeProvider = { TestConstants.time }
19
+    }
20
+
21
+    @Test
22
+    fun `raises away changed event with reason`() {
23
+        val events = AwayProcessor().process(
24
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "AWAY", params("Hacking the planet")))
25
+        assertEquals(1, events.size)
26
+
27
+        val event = events[0]
28
+        assertEquals(TestConstants.time, event.metadata.time)
29
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
30
+        assertEquals("Hacking the planet", event.message)
31
+    }
32
+
33
+    @Test
34
+    fun `raises away changed event with no reason`() {
35
+        val events = AwayProcessor().process(
36
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "AWAY", emptyList()))
37
+        assertEquals(1, events.size)
38
+
39
+        val event = events[0]
40
+        assertEquals(TestConstants.time, event.metadata.time)
41
+        assertEquals(User("acidburn", "libby", "root.localhost"), event.user)
42
+        assertNull(event.message)
43
+    }
44
+
45
+    @Test
46
+    fun `does nothing on away if prefix missing`() {
47
+        val events = AwayProcessor().process(IrcMessage(emptyMap(), null, "AWAY", params("*")))
48
+        Assertions.assertEquals(0, events.size)
49
+    }
50
+
51
+}

Loading…
Cancel
Save