You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IrcClient.kt 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package com.dmdirc.ktirc
  2. import com.dmdirc.ktirc.events.IrcEvent
  3. import com.dmdirc.ktirc.io.CaseMapping
  4. import com.dmdirc.ktirc.messages.sendJoin
  5. import com.dmdirc.ktirc.messages.tagMap
  6. import com.dmdirc.ktirc.model.*
  7. import com.dmdirc.ktirc.util.RemoveIn
  8. import kotlinx.coroutines.Deferred
  9. import java.time.Duration
  10. /**
  11. * Primary interface for interacting with KtIrc.
  12. */
  13. interface IrcClient {
  14. /**
  15. * Holds state relating to the current server, its features, and capabilities.
  16. */
  17. val serverState: ServerState
  18. /**
  19. * Holds the state for each channel we are currently joined to.
  20. */
  21. val channelState: ChannelStateMap
  22. /**
  23. * Holds the state for all known users (those in common channels).
  24. */
  25. val userState: UserState
  26. /**
  27. * Details of our user on IRC.
  28. */
  29. val localUser: User
  30. /**
  31. * The configured behaviour of the client.
  32. */
  33. val behaviour: ClientBehaviour
  34. /**
  35. * The current case-mapping of the server, defining how uppercase and lowercase nicks/channels/etc are mapped
  36. * to one another.
  37. *
  38. * This may change over the lifetime of an [IrcClient]. It should not be stored, and should be checked each
  39. * time it is needed.
  40. */
  41. val caseMapping: CaseMapping
  42. get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
  43. /**
  44. * Begins a connection attempt to the IRC server.
  45. *
  46. * This method will return immediately, and the attempt to connect will be executed in a coroutine on the
  47. * IO scheduler. To check the status of the connection, monitor events using [onEvent].
  48. */
  49. fun connect()
  50. /**
  51. * Disconnect immediately from the IRC server, without sending a QUIT.
  52. */
  53. fun disconnect()
  54. /**
  55. * Sends the given raw line to the IRC server, followed by a carriage return and line feed.
  56. *
  57. * Standard IRC messages can be constructed using the methods in [com.dmdirc.ktirc.messages]
  58. * such as [sendJoin].
  59. *
  60. * @param message The line to be sent to the IRC server.
  61. */
  62. @Deprecated("Use structured send instead", ReplaceWith("send(command, arguments)"))
  63. @RemoveIn("2.0.0")
  64. fun send(message: String)
  65. /**
  66. * Sends the given command to the IRC server.
  67. *
  68. * This should only be needed to send raw/custom commands; standard messages can be sent using the
  69. * extension methods in [com.dmdirc.ktirc.messages] such as [sendJoin].
  70. *
  71. * This method will return immediately; the message will be delivered by a coroutine. Messages
  72. * are guaranteed to be delivered in order when this method is called multiple times.
  73. *
  74. * @param tags The IRCv3 tags to prefix the message with, if any.
  75. * @param command The command to be sent.
  76. * @param arguments The arguments to the command.
  77. */
  78. fun send(tags: Map<MessageTag, String>, command: String, vararg arguments: String)
  79. /**
  80. * Sends the given command to the IRC server.
  81. *
  82. * This should only be needed to send raw/custom commands; standard messages can be sent using the
  83. * extension methods in [com.dmdirc.ktirc.messages] such as [sendJoin].
  84. *
  85. * This method will return immediately; the message will be delivered by a coroutine. Messages
  86. * are guaranteed to be delivered in order when this method is called multiple times.
  87. *
  88. * @param command The command to be sent.
  89. * @param arguments The arguments to the command.
  90. */
  91. fun send(command: String, vararg arguments: String) = send(emptyMap(), command, *arguments)
  92. /**
  93. * Registers a new handler for all events on this connection.
  94. *
  95. * All events are subclasses of [IrcEvent]; the idiomatic way to handle them is using a `when` statement:
  96. *
  97. * ```
  98. * client.onEvent {
  99. * when(it) {
  100. * is MessageReceived -> println(it.message)
  101. * }
  102. * }
  103. * ```
  104. *
  105. * *Note*: at present handlers cannot be removed; they last the lifetime of the [IrcClient].
  106. *
  107. * @param handler The method to call when a new event occurs.
  108. */
  109. fun onEvent(handler: (IrcEvent) -> Unit)
  110. /**
  111. * Utility method to determine if the given user is the one we are connected to IRC as. Should only be used after a
  112. * [com.dmdirc.ktirc.events.ServerReady] event has been received.
  113. */
  114. fun isLocalUser(user: User) = isLocalUser(user.nickname)
  115. /**
  116. * Utility method to determine if the given user is the one we are connected to IRC as. Should only be used after a
  117. * [com.dmdirc.ktirc.events.ServerReady] event has been received.
  118. */
  119. fun isLocalUser(nickname: String) = caseMapping.areEquivalent(nickname, localUser.nickname)
  120. /**
  121. * Determines if the given [target] appears to be a channel or not. Should only be used after a
  122. * [com.dmdirc.ktirc.events.ServerReady] event has been received.
  123. */
  124. fun isChannel(target: String) = target.isNotEmpty() && serverState.channelTypes.contains(target[0])
  125. }
  126. internal interface ExperimentalIrcClient : IrcClient {
  127. /**
  128. * Sends the given command to the IRC server, and waits for a response back.
  129. *
  130. * This should only be needed to send raw/custom commands; standard messages can be sent using the
  131. * extension methods in [com.dmdirc.ktirc.messages] such as [com.dmdirc.ktirc.messages.sendPartAsync].
  132. *
  133. * This method will return immediately. The returned [Deferred] will eventually be populated with
  134. * the server's response. If the server supports the labeled-responses capability, a label will
  135. * be added to the outgoing message to identify the correct response; otherwise the [matcher]
  136. * will be invoked on all incoming events to select the appropriate response.
  137. *
  138. * If the response times out, `null` will be supplied instead of an event.
  139. *
  140. * @param command The command to be sent.
  141. * @param arguments The arguments to the command.
  142. * @param matcher The matcher to use to find a matching event.
  143. * @return A deferred [IrcEvent]? that contains the server's response to the command.
  144. */
  145. fun sendAsync(command: String, arguments: Array<String>, matcher: (IrcEvent) -> Boolean) = sendAsync(tagMap(), command, arguments, matcher)
  146. /**
  147. * Sends the given command to the IRC server, and waits for a response back.
  148. *
  149. * This should only be needed to send raw/custom commands; standard messages can be sent using the
  150. * extension methods in [com.dmdirc.ktirc.messages] such as [com.dmdirc.ktirc.messages.sendPartAsync].
  151. *
  152. * This method will return immediately. The returned [Deferred] will eventually be populated with
  153. * the server's response. If the server supports the labeled-responses capability, a label will
  154. * be added to the outgoing message to identify the correct response; otherwise the [matcher]
  155. * will be invoked on all incoming events to select the appropriate response.
  156. *
  157. * If the response times out, `null` will be supplied instead of an event.
  158. *
  159. * @param tags The IRCv3 tags to prefix the message with, if any.
  160. * @param command The command to be sent.
  161. * @param arguments The arguments to the command.
  162. * @param matcher The matcher to use to find a matching event.
  163. * @return A deferred [IrcEvent]? that contains the server's response to the command.
  164. */
  165. fun sendAsync(tags: Map<MessageTag, String>, command: String, arguments: Array<String>, matcher: (IrcEvent) -> Boolean): Deferred<IrcEvent?>
  166. }
  167. /**
  168. * Defines the configuration for sending pings and dealing with ping timeouts.
  169. */
  170. interface PingTimeouts {
  171. /**
  172. * The period of time we will wait between sending pings to the server.
  173. *
  174. * If [incomingLinesResetTimer] is enabled, then a ping will be sent this period of time after the last line
  175. * is received from the server. Otherwise, it will be sent this period of time after the last PONG response.
  176. */
  177. val sendPeriod: Duration
  178. /**
  179. * The period of time to wait for a reply.
  180. *
  181. * If the server does not respond to a PING in this period, we consider it stoned and disconnect.
  182. */
  183. val responseGracePeriod: Duration
  184. /**
  185. * Whether to treat incoming lines from the server as an indication that it is still active.
  186. *
  187. * This reduces the amount of pings that KtIrc will send, but can result in KtIrc staying connected even if the
  188. * server is severely lagged or ignores all lines sent to it (for example).
  189. */
  190. val incomingLinesResetTimer: Boolean
  191. }
  192. /**
  193. * Defines the behaviour of an [IrcClient].
  194. */
  195. interface ClientBehaviour {
  196. /** Whether or not to request channel modes when we join a channel. */
  197. val requestModesOnJoin: Boolean
  198. /**
  199. * If enabled, all messages (`PRIVMSG`s) sent by the client will always be "echoed" back as a MessageReceived
  200. * event.
  201. *
  202. * This makes the behaviour consistent across ircds that support the echo-message capability and those that
  203. * don't. If disabled, messages will only be echoed back when the server supports the capability.
  204. */
  205. val alwaysEchoMessages: Boolean
  206. /**
  207. * If enabled, KtIRC will try to connect to IRC servers over IPv6 if they publish the appropriate DNS entries.
  208. *
  209. * Otherwise, KtIrc will prefer IPv4.
  210. */
  211. val preferIPv6: Boolean
  212. /**
  213. * The settings to use for sending pings to the server, and timing out connections that don't reply to those
  214. * pings.
  215. *
  216. * If unset (i.e., `null`), no pings will be sent, and KtIrc will never time out servers.
  217. */
  218. val pingTimeouts: PingTimeouts?
  219. }
  220. /**
  221. * Constructs a new [IrcClient] using a configuration DSL.
  222. *
  223. * See [IrcClientConfigBuilder] for details of all options
  224. */
  225. @IrcClientDsl
  226. @Suppress("FunctionName")
  227. fun IrcClient(block: IrcClientConfigBuilder.() -> Unit): IrcClient =
  228. IrcClientImpl(IrcClientConfigBuilder().apply(block).build())