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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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. interface IrcClient {
  12. fun send(message: String)
  13. val serverState: ServerState
  14. val channelState: ChannelStateMap
  15. val userState: UserState
  16. fun onEvent(handler: (IrcEvent) -> Unit)
  17. val caseMapping: CaseMapping
  18. get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
  19. fun isLocalUser(user: User): Boolean = caseMapping.areEquivalent(user.nickname, serverState.localNickname)
  20. }
  21. // TODO: How should alternative nicknames work?
  22. // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
  23. // TODO: Should there be a default profile?
  24. class IrcClientImpl(private val server: Server, private val profile: Profile) : IrcClient {
  25. var socketFactory: (String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
  26. override val serverState = ServerState(profile.initialNick)
  27. override val channelState = ChannelStateMap { caseMapping }
  28. override val userState = UserState { caseMapping }
  29. private val messageHandler = MessageHandler(messageProcessors.toList(), eventHandlers.toMutableList())
  30. private val parser = MessageParser()
  31. private var socket: LineBufferedSocket? = null
  32. private val scope = CoroutineScope(Dispatchers.IO)
  33. private val connecting = AtomicBoolean(false)
  34. private var connectionJob: Job? = null
  35. override fun send(message: String) {
  36. scope.launch {
  37. socket?.sendLine(message)
  38. }
  39. }
  40. fun connect() {
  41. check(!connecting.getAndSet(true))
  42. connectionJob = scope.launch {
  43. with(socketFactory(server.host, server.port, server.tls)) {
  44. socket = this
  45. connect()
  46. sendLine("CAP LS 302")
  47. server.password?.let { pass -> sendLine(passwordMessage(pass)) }
  48. sendLine(nickMessage(profile.initialNick))
  49. // TODO: Send correct host
  50. sendLine(userMessage(profile.userName, "localhost", server.host, profile.realName))
  51. // TODO: This should be elsewhere
  52. messageHandler.processMessages(this@IrcClientImpl, readLines(scope).map { parser.parse(it) })
  53. }
  54. }
  55. }
  56. fun disconnect() {
  57. socket?.disconnect()
  58. }
  59. suspend fun join() {
  60. connectionJob?.join()
  61. }
  62. override fun onEvent(handler: (IrcEvent) -> Unit) {
  63. messageHandler.handlers.add(object : EventHandler {
  64. override fun processEvent(client: IrcClient, event: IrcEvent) {
  65. handler(event)
  66. }
  67. })
  68. }
  69. }
  70. fun main() {
  71. val rootLogger = LogManager.getLogManager().getLogger("")
  72. rootLogger.level = Level.FINEST
  73. for (h in rootLogger.handlers) {
  74. h.level = Level.FINEST
  75. }
  76. runBlocking {
  77. val client = IrcClientImpl(Server("testnet.inspircd.org", 6667), Profile("KtIrc", "Kotlin!", "kotlin"))
  78. client.onEvent { event ->
  79. when (event) {
  80. is ServerWelcome -> client.send(joinMessage("#ktirc"))
  81. is MessageReceived ->
  82. if (event.message == "!test")
  83. client.send(privmsgMessage(event.target, "Test successful!"))
  84. }
  85. }
  86. client.connect()
  87. client.join()
  88. }
  89. }