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 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package com.dmdirc.ktirc
  2. import com.dmdirc.ktirc.events.*
  3. import com.dmdirc.ktirc.io.*
  4. import com.dmdirc.ktirc.messages.*
  5. import com.dmdirc.ktirc.model.*
  6. import com.dmdirc.ktirc.util.currentTimeProvider
  7. import com.dmdirc.ktirc.util.logger
  8. import io.ktor.util.KtorExperimentalAPI
  9. import kotlinx.coroutines.*
  10. import kotlinx.coroutines.channels.map
  11. import java.util.concurrent.atomic.AtomicBoolean
  12. /**
  13. * Primary interface for interacting with KtIrc.
  14. */
  15. interface IrcClient {
  16. val serverState: ServerState
  17. val channelState: ChannelStateMap
  18. val userState: UserState
  19. val profile: Profile
  20. val caseMapping: CaseMapping
  21. get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
  22. /**
  23. * Begins a connection attempt to the IRC server.
  24. *
  25. * This method will return immediately, and the attempt to connect will be executed in a coroutine on the
  26. * IO scheduler. To check the status of the connection, monitor events using [onEvent].
  27. */
  28. fun connect()
  29. /**
  30. * Disconnect immediately from the IRC server, without sending a QUIT.
  31. */
  32. fun disconnect()
  33. /**
  34. * Sends the given raw line to the IRC server, followed by a carriage return and line feed.
  35. *
  36. * Standard IRC messages can be constructed using the methods in [com.dmdirc.ktirc.messages]
  37. * such as [sendJoin].
  38. *
  39. * @param message The line to be sent to the IRC server.
  40. */
  41. fun send(message: String)
  42. /**
  43. * Registers a new handler for all events on this connection.
  44. *
  45. * All events are subclasses of [IrcEvent]; the idiomatic way to handle them is using a `when` statement:
  46. *
  47. * ```
  48. * client.onEvent {
  49. * when(it) {
  50. * is MessageReceived -> println(it.message)
  51. * }
  52. * }
  53. * ```
  54. *
  55. * *Note*: at present handlers cannot be removed; they last the lifetime of the [IrcClient].
  56. *
  57. * @param handler The method to call when a new event occurs.
  58. */
  59. fun onEvent(handler: (IrcEvent) -> Unit)
  60. /**
  61. * Utility method to determine if the given user is the one we are connected to IRC as.
  62. */
  63. fun isLocalUser(user: User) = isLocalUser(user.nickname)
  64. /**
  65. * Utility method to determine if the given user is the one we are connected to IRC as.
  66. */
  67. fun isLocalUser(nickname: String) = caseMapping.areEquivalent(nickname, serverState.localNickname)
  68. }
  69. /**
  70. * Concrete implementation of an [IrcClient].
  71. *
  72. * @param server The server to connect to.
  73. * @param profile The user details to use when connecting.
  74. */
  75. // TODO: How should alternative nicknames work?
  76. // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
  77. // TODO: Should there be a default profile?
  78. class IrcClientImpl(private val server: Server, override val profile: Profile) : IrcClient, CoroutineScope {
  79. private val log by logger()
  80. @ExperimentalCoroutinesApi
  81. override val coroutineContext = GlobalScope.newCoroutineContext(Dispatchers.IO)
  82. @ExperimentalCoroutinesApi
  83. @KtorExperimentalAPI
  84. internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
  85. override val serverState = ServerState(profile.initialNick, server.host)
  86. override val channelState = ChannelStateMap { caseMapping }
  87. override val userState = UserState { caseMapping }
  88. private val messageHandler = MessageHandler(messageProcessors.toList(), eventHandlers.toMutableList())
  89. private val parser = MessageParser()
  90. private var socket: LineBufferedSocket? = null
  91. private val connecting = AtomicBoolean(false)
  92. override fun send(message: String) {
  93. socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message"}
  94. }
  95. override fun connect() {
  96. check(!connecting.getAndSet(true))
  97. @Suppress("EXPERIMENTAL_API_USAGE")
  98. with(socketFactory(this, server.host, server.port, server.tls)) {
  99. // TODO: Proper error handling - what if connect() fails?
  100. socket = this
  101. emitEvent(ServerConnecting(currentTimeProvider()))
  102. launch {
  103. connect()
  104. emitEvent(ServerConnected(currentTimeProvider()))
  105. sendCapabilityList()
  106. sendPasswordIfPresent()
  107. sendNickChange(profile.initialNick)
  108. // TODO: Send correct host
  109. sendUser(profile.userName, profile.realName)
  110. messageHandler.processMessages(this@IrcClientImpl, receiveChannel.map { parser.parse(it) })
  111. reset()
  112. emitEvent(ServerDisconnected(currentTimeProvider()))
  113. }
  114. }
  115. }
  116. override fun disconnect() {
  117. socket?.disconnect()
  118. }
  119. override fun onEvent(handler: (IrcEvent) -> Unit) {
  120. messageHandler.handlers.add(object : EventHandler {
  121. override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
  122. handler(event)
  123. return emptyList()
  124. }
  125. })
  126. }
  127. private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
  128. private fun sendPasswordIfPresent() = server.password?.let(this::sendPassword)
  129. internal fun reset() {
  130. serverState.reset()
  131. channelState.clear()
  132. userState.reset()
  133. socket = null
  134. connecting.set(false)
  135. }
  136. }