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.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 kotlinx.coroutines.*
  7. import kotlinx.coroutines.channels.map
  8. import java.util.concurrent.atomic.AtomicBoolean
  9. import java.util.logging.Level
  10. import java.util.logging.LogManager
  11. /**
  12. * Primary interface for interacting with KtIrc.
  13. */
  14. interface IrcClient {
  15. val serverState: ServerState
  16. val channelState: ChannelStateMap
  17. val userState: UserState
  18. val caseMapping: CaseMapping
  19. get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
  20. /**
  21. * Begins a connection attempt to the IRC server.
  22. *
  23. * This method will return immediately, and the attempt to connect will be executed in a coroutine on the
  24. * IO scheduler. To check the status of the connection, monitor events using [onEvent].
  25. */
  26. fun connect()
  27. /**
  28. * Disconnect immediately from the IRC server, without sending a QUIT.
  29. */
  30. fun disconnect()
  31. /**
  32. * Sends the given raw line to the IRC server, followed by a carriage return and line feed.
  33. *
  34. * Standard IRC messages can be constructed using the methods in [com.dmdirc.ktirc.messages]
  35. * such as [joinMessage].
  36. *
  37. * @param message The line to be sent to the IRC server.
  38. */
  39. fun send(message: String)
  40. /**
  41. * Registers a new handler for all events on this connection.
  42. *
  43. * All events are subclasses of [IrcEvent]; the idomatic way to handle them is using a `when` statement:
  44. *
  45. * ```
  46. * client.onEvent {
  47. * when(it) {
  48. * is MessageReceived -> println(it.message)
  49. * }
  50. * }
  51. * ```
  52. *
  53. * *Note*: at present handlers cannot be removed; they last the lifetime of the [IrcClient].
  54. *
  55. * @param handler The method to call when a new event occurs.
  56. */
  57. fun onEvent(handler: (IrcEvent) -> Unit)
  58. /**
  59. * Utility method to determine if the given user is the one we are connected to IRC as.
  60. */
  61. fun isLocalUser(user: User) = caseMapping.areEquivalent(user.nickname, serverState.localNickname)
  62. }
  63. /**
  64. * Concrete implementation of an [IrcClient].
  65. *
  66. * @param server The server to connect to.
  67. * @param profile The user details to use when connecting.
  68. */
  69. // TODO: How should alternative nicknames work?
  70. // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
  71. // TODO: Should there be a default profile?
  72. class IrcClientImpl(private val server: Server, private val profile: Profile) : IrcClient {
  73. internal var socketFactory: (String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
  74. override val serverState = ServerState(profile.initialNick)
  75. override val channelState = ChannelStateMap { caseMapping }
  76. override val userState = UserState { caseMapping }
  77. private val messageHandler = MessageHandler(messageProcessors.toList(), eventHandlers.toMutableList())
  78. private val parser = MessageParser()
  79. private var socket: LineBufferedSocket? = null
  80. private val scope = CoroutineScope(Dispatchers.IO)
  81. private val connecting = AtomicBoolean(false)
  82. private var connectionJob: Job? = null
  83. override fun send(message: String) {
  84. scope.launch {
  85. socket?.sendLine(message)
  86. }
  87. }
  88. override fun connect() {
  89. check(!connecting.getAndSet(true))
  90. connectionJob = scope.launch {
  91. with(socketFactory(server.host, server.port, server.tls)) {
  92. socket = this
  93. connect()
  94. sendLine("CAP LS 302")
  95. server.password?.let { pass -> sendPassword(pass) }
  96. sendNickChange(profile.initialNick)
  97. // TODO: Send correct host
  98. sendUser(profile.userName, "localhost", server.host, profile.realName)
  99. // TODO: This should be elsewhere
  100. messageHandler.processMessages(this@IrcClientImpl, readLines(scope).map { parser.parse(it) })
  101. }
  102. }
  103. }
  104. override fun disconnect() {
  105. socket?.disconnect()
  106. }
  107. suspend fun join() {
  108. connectionJob?.join()
  109. }
  110. override fun onEvent(handler: (IrcEvent) -> Unit) {
  111. messageHandler.handlers.add(object : EventHandler {
  112. override fun processEvent(client: IrcClient, event: IrcEvent) {
  113. handler(event)
  114. }
  115. })
  116. }
  117. }
  118. internal fun main() {
  119. val rootLogger = LogManager.getLogManager().getLogger("")
  120. rootLogger.level = Level.FINEST
  121. for (h in rootLogger.handlers) {
  122. h.level = Level.FINEST
  123. }
  124. runBlocking {
  125. with(IrcClientImpl(Server("testnet.inspircd.org", 6667), Profile("KtIrc", "Kotlin!", "kotlin"))) {
  126. onEvent { event ->
  127. when (event) {
  128. is ServerWelcome -> sendJoin("#ktirc")
  129. is MessageReceived ->
  130. if (event.message == "!test")
  131. reply(event, "Test successful!")
  132. }
  133. }
  134. connect()
  135. join()
  136. }
  137. }
  138. }