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.

Dsl.kt 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package com.dmdirc.ktirc
  2. import java.time.Duration
  3. /**
  4. * Dsl marker for [IrcClient] dsl.
  5. */
  6. @DslMarker
  7. annotation class IrcClientDsl
  8. /**
  9. * Dsl for configuring an IRC Client.
  10. *
  11. * [server] and [profile] blocks are required. The full range of configuration options are:
  12. *
  13. * ```
  14. * server {
  15. * host = "irc.example.com" // Required
  16. * port = 6667
  17. * useTls = true
  18. * password = "H4ckTh3Pl4n3t"
  19. * }
  20. *
  21. * profile {
  22. * nickname = "MyBot" // Required
  23. * username = "bot"
  24. * realName = "Botomatic v1.2"
  25. * }
  26. *
  27. * behaviour {
  28. * requestModesOnJoin = true
  29. * alwaysEchoMessages = true
  30. * preferIPv6 = false
  31. * }
  32. *
  33. * sasl {
  34. * mechanisms += "PLAIN" // or to set the list from scratch:
  35. * mechanisms("PLAIN")
  36. *
  37. * username = "botaccount"
  38. * password = "s3cur3"
  39. * }
  40. * ```
  41. */
  42. @IrcClientDsl
  43. class IrcClientConfigBuilder {
  44. private var server: ServerConfig? = null
  45. set(value) {
  46. check(field == null) { "server may only be specified once" }
  47. check(!value?.host.isNullOrEmpty()) { "server.host must be specified" }
  48. field = value
  49. }
  50. private var profile: ProfileConfig? = null
  51. set(value) {
  52. check(field == null) { "profile may only be specified once" }
  53. check(!value?.nickname.isNullOrEmpty()) { "profile.nickname must be specified" }
  54. field = value
  55. }
  56. private var behaviour: BehaviourConfig? = null
  57. set(value) {
  58. check(field == null) { "behaviour may only be specified once" }
  59. field = value
  60. }
  61. private var sasl: SaslConfig? = null
  62. set(value) {
  63. check(field == null) { "sasl may only be specified once" }
  64. field = value
  65. }
  66. /**
  67. * Configures the server that the IrcClient will connect to.
  68. *
  69. * See [ServerConfig] for details of each parameter.
  70. *
  71. * @param block Optional additional configuration to apply to the [ServerConfig]
  72. */
  73. @IrcClientDsl
  74. fun server(host: String? = null, port: Int? = null, useTls: Boolean? = null, password: String? = null, block: (ServerConfig.() -> Unit)? = null) {
  75. server = ServerConfig().apply {
  76. host?.let { this.host = it }
  77. port?.let { this.port = it }
  78. useTls?.let { this.useTls = it }
  79. password?.let { this.password = it }
  80. block?.let { apply(it) }
  81. }
  82. }
  83. /**
  84. * Configures the profile of the IrcClient user.
  85. *
  86. * See [ProfileConfig] for details of each parameter.
  87. *
  88. * @param block Optional additional configuration to apply to the [ProfileConfig]
  89. */
  90. @IrcClientDsl
  91. fun profile(nickname: String? = null, username: String? = null, realName: String? = null, block: (ProfileConfig.() -> Unit)? = null) {
  92. profile = ProfileConfig().apply {
  93. nickname?.let { this.nickname = it }
  94. username?.let { this.username = it }
  95. realName?.let { this.realName = it }
  96. block?.let { apply(it) }
  97. }
  98. }
  99. /**
  100. * Configures the behaviour of the client (optional).
  101. */
  102. @IrcClientDsl
  103. fun behaviour(block: BehaviourConfig.() -> Unit) {
  104. behaviour = BehaviourConfig().apply(block)
  105. }
  106. /**
  107. * Configures SASL authentication (optional).
  108. */
  109. @IrcClientDsl
  110. fun sasl(block: SaslConfig.() -> Unit) {
  111. sasl = SaslConfig().apply(block)
  112. }
  113. internal fun build() =
  114. IrcClientConfig(
  115. checkNotNull(server) { "Server must be specified " },
  116. checkNotNull(profile) { "Profile must be specified" },
  117. behaviour ?: BehaviourConfig(),
  118. sasl)
  119. }
  120. /**
  121. * Dsl for configuring a server.
  122. */
  123. @IrcClientDsl
  124. class ServerConfig {
  125. /** The hostname (or IP address) of the server to connect to. */
  126. var host: String = ""
  127. /** The port to connect on. Defaults to 6697. */
  128. var port: Int = 6697
  129. /** Whether or not to use TLS (an encrypted connection). */
  130. var useTls: Boolean = true
  131. /** The password required to connect to the server, if any. */
  132. var password: String? = null
  133. }
  134. /**
  135. * Dsl for configuring a profile.
  136. */
  137. @IrcClientDsl
  138. class ProfileConfig {
  139. /** The initial nickname to use when connecting. */
  140. var nickname: String = ""
  141. /** The username (used in place of an ident response) to provide to the server. */
  142. var username: String = "KtIrc"
  143. /** The "real name" to provide to the server. */
  144. var realName: String = "KtIrc User"
  145. }
  146. /**
  147. * Dsl for configuring SASL authentication.
  148. *
  149. * By default the `PLAIN`, `SCRAM-SHA-1`, and `SCRAM-SHA-256` methods will be enabled if SASL is configured.
  150. *
  151. * You can modify the mechanisms either by editing the [mechanisms] collection:
  152. *
  153. * ```
  154. * mechanisms += "EXTERNAL"
  155. * mechanisms.remove("PLAIN")
  156. * ```
  157. *
  158. * or by calling the [mechanisms] function with all the mechanisms you wish
  159. * to enable:
  160. *
  161. * ```
  162. * mechanisms("PLAIN", "EXTERNAL")
  163. * ```
  164. *
  165. * Priority of mechanisms is determined by KtIrc, regardless of the order
  166. * they are specified in here.
  167. */
  168. @IrcClientDsl
  169. class SaslConfig {
  170. /** The SASL mechanisms to enable. */
  171. val mechanisms: MutableCollection<String> = mutableSetOf("PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-256")
  172. /** The username to provide when authenticating using SASL. */
  173. var username: String = ""
  174. /** The username to provide when authenticating using SASL. */
  175. var password: String = ""
  176. /**
  177. * Replaces all enabled SASL mechanisms with the given ones.
  178. */
  179. @IrcClientDsl
  180. fun mechanisms(vararg methods: String) {
  181. with(this.mechanisms) {
  182. clear()
  183. addAll(methods)
  184. }
  185. }
  186. }
  187. @IrcClientDsl
  188. class PingConfig {
  189. /**
  190. * The period of time we will wait between sending pings to the server.
  191. *
  192. * If [incomingLinesResetTimer] is enabled, then a ping will be sent this period of time after the last line
  193. * is received from the server. Otherwise, it will be sent this period of time after the last PONG response.
  194. */
  195. var sendPeriod: Duration? = null
  196. /**
  197. * The period of time to wait for a reply.
  198. *
  199. * If the server does not respond to a PING in this period, we consider it stoned and disconnect.
  200. */
  201. var responseGracePeriod: Duration? = null
  202. /**
  203. * Whether to treat incoming lines from the server as an indication that it is still active.
  204. *
  205. * This reduces the amount of pings that KtIrc will send, but can result in KtIrc staying connected even if the
  206. * server is severely lagged or ignores all lines sent to it (for example).
  207. */
  208. var incomingLinesResetTimer: Boolean = false
  209. }
  210. /**
  211. * Dsl for configuring the behaviour of an [IrcClient].
  212. */
  213. @IrcClientDsl
  214. class BehaviourConfig : ClientBehaviour {
  215. override var requestModesOnJoin = false
  216. override var alwaysEchoMessages = false
  217. override var preferIPv6 = true
  218. override var pingTimeouts: PingTimeouts? = null
  219. internal set(value) {
  220. check(field == null) { "ping timeouts may only be specified once" }
  221. field = value
  222. }
  223. /**
  224. * Configures how frequently KtIrc will send pings, and how it will deal with non-responsive servers.
  225. *
  226. * If not specified, KtIrc will not send pings.
  227. *
  228. * See [PingConfig].
  229. */
  230. @IrcClientDsl
  231. fun sendPings(block: PingConfig.() -> Unit) {
  232. val config = PingConfig().apply(block)
  233. requireNotNull(config.sendPeriod) { "send period must be specified" }
  234. requireNotNull(config.responseGracePeriod) { "response grace period must be specified" }
  235. pingTimeouts = IrcPingTimeouts(config.sendPeriod!!, config.responseGracePeriod!!, config.incomingLinesResetTimer)
  236. }
  237. }