Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

IrcClient.kt 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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 kotlinx.coroutines.*
  8. import kotlinx.coroutines.channels.Channel
  9. import kotlinx.coroutines.channels.map
  10. import java.util.concurrent.atomic.AtomicBoolean
  11. import java.util.logging.Level
  12. import java.util.logging.LogManager
  13. /**
  14. * Primary interface for interacting with KtIrc.
  15. */
  16. interface IrcClient {
  17. val serverState: ServerState
  18. val channelState: ChannelStateMap
  19. val userState: UserState
  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) = caseMapping.areEquivalent(user.nickname, serverState.localNickname)
  64. }
  65. /**
  66. * Concrete implementation of an [IrcClient].
  67. *
  68. * @param server The server to connect to.
  69. * @param profile The user details to use when connecting.
  70. */
  71. // TODO: How should alternative nicknames work?
  72. // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
  73. // TODO: Should there be a default profile?
  74. class IrcClientImpl(private val server: Server, private val profile: Profile) : IrcClient {
  75. internal var socketFactory: (String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
  76. override val serverState = ServerState(profile.initialNick, server.host)
  77. override val channelState = ChannelStateMap { caseMapping }
  78. override val userState = UserState { caseMapping }
  79. private val messageHandler = MessageHandler(messageProcessors.toList(), eventHandlers.toMutableList())
  80. private val parser = MessageParser()
  81. private var socket: LineBufferedSocket? = null
  82. private val scope = CoroutineScope(Dispatchers.IO)
  83. private val connecting = AtomicBoolean(false)
  84. private var connectionJob: Job? = null
  85. internal var writeChannel: Channel<ByteArray>? = null
  86. override fun send(message: String) {
  87. writeChannel?.offer(message.toByteArray())
  88. }
  89. override fun connect() {
  90. check(!connecting.getAndSet(true))
  91. connectionJob = scope.launch {
  92. with(socketFactory(server.host, server.port, server.tls)) {
  93. // TODO: Proper error handling - what if connect() fails?
  94. socket = this
  95. connect()
  96. with(Channel<ByteArray>(Channel.UNLIMITED)) {
  97. writeChannel = this
  98. scope.launch {
  99. writeChannel?.let {
  100. writeLines(it)
  101. }
  102. }
  103. }
  104. emitEvent(ServerConnected(currentTimeProvider()))
  105. sendCapabilityList()
  106. sendPasswordIfPresent()
  107. sendNickChange(profile.initialNick)
  108. // TODO: Send correct host
  109. sendUser(profile.userName, "localhost", server.host, profile.realName)
  110. messageHandler.processMessages(this@IrcClientImpl, readLines(scope).map { parser.parse(it) })
  111. }
  112. }
  113. }
  114. override fun disconnect() {
  115. socket?.disconnect()
  116. }
  117. /**
  118. * Joins the coroutine running the message loop, and blocks until it is completed.
  119. */
  120. suspend fun join() {
  121. connectionJob?.join()
  122. }
  123. override fun onEvent(handler: (IrcEvent) -> Unit) {
  124. messageHandler.handlers.add(object : EventHandler {
  125. override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
  126. handler(event)
  127. return emptyList()
  128. }
  129. })
  130. }
  131. private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
  132. private fun sendPasswordIfPresent() = server.password?.let(this::sendPassword)
  133. }
  134. internal fun main() {
  135. val rootLogger = LogManager.getLogManager().getLogger("")
  136. rootLogger.level = Level.FINEST
  137. for (h in rootLogger.handlers) {
  138. h.level = Level.FINEST
  139. }
  140. runBlocking {
  141. with(IrcClientImpl(Server("testnet.inspircd.org", 6667), Profile("KtIrc", "Kotlin!", "kotlin"))) {
  142. onEvent { event ->
  143. when (event) {
  144. is ServerWelcome -> sendJoin("#ktirc")
  145. is MessageReceived ->
  146. if (event.message == "!test")
  147. reply(event, "Test successful!")
  148. }
  149. }
  150. connect()
  151. join()
  152. }
  153. }
  154. }