Utility IRC bot
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. package com.chameth.hexbot.modules
  2. import com.chameth.hexbot.isModuleCommand
  3. import com.chameth.hexbot.moduleCommandArgs
  4. import com.dmdirc.ktirc.IrcClient
  5. import com.dmdirc.ktirc.events.IrcEvent
  6. import com.dmdirc.ktirc.events.MessageReceived
  7. import com.dmdirc.ktirc.events.react
  8. import com.dmdirc.ktirc.events.reply
  9. import com.google.gson.annotations.SerializedName
  10. import com.ufoscout.properlty.Properlty
  11. import io.ktor.client.HttpClient
  12. import io.ktor.client.engine.okhttp.OkHttp
  13. import io.ktor.client.features.json.GsonSerializer
  14. import io.ktor.client.features.json.JsonFeature
  15. import io.ktor.client.request.*
  16. import org.jetbrains.exposed.sql.SchemaUtils
  17. import org.jetbrains.exposed.sql.Table
  18. import org.jetbrains.exposed.sql.insert
  19. import org.jetbrains.exposed.sql.select
  20. import org.jetbrains.exposed.sql.transactions.transaction
  21. import org.joda.time.DateTime
  22. import java.sql.Connection
  23. import java.util.*
  24. import kotlin.contracts.ExperimentalContracts
  25. class Hue : Module {
  26. private var appId: String? = null
  27. private var clientId: String? = null
  28. private var clientSecret: String? = null
  29. private val basicHeader by lazy { Base64.getEncoder().encodeToString("$clientId:$clientSecret".toByteArray()) }
  30. private val httpClient = HttpClient(OkHttp) {
  31. engine {
  32. config {
  33. followRedirects(true)
  34. }
  35. }
  36. install(JsonFeature) {
  37. serializer = GsonSerializer()
  38. }
  39. }
  40. override fun init(settings: Properlty): Boolean {
  41. clientId = settings["hue.clientid", null]
  42. clientSecret = settings["hue.clientsecret", null]
  43. appId = settings["hue.appid", null]
  44. if (clientId == null || clientSecret == null || appId == null) {
  45. return false
  46. }
  47. transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
  48. SchemaUtils.create(HueRecords)
  49. }
  50. return true
  51. }
  52. @ExperimentalContracts
  53. override suspend fun processEvent(client: IrcClient, event: IrcEvent) {
  54. if (client.isModuleCommand(event, "hue")) {
  55. val account = event.user.account ?: client.userState[event.user]?.details?.account
  56. if (account == null) {
  57. client.reply(event, "You must be authenticated to use this command", true)
  58. return
  59. }
  60. val args = event.moduleCommandArgs
  61. when (args.firstOrNull()) {
  62. "auth" -> handleAuth(args.getOrNull(1), account) { client.reply(event, it, true) }
  63. "list" -> handleList(account) { client.reply(event, it, true) }
  64. "turn" -> handleTurn(account, args[1].toInt(), args[2] == "on", client, event)
  65. }
  66. }
  67. }
  68. private suspend fun handleTurn(account: String, id: Int, state: Boolean, ircClient: IrcClient, event: MessageReceived) {
  69. lateinit var token: String
  70. lateinit var username: String
  71. // TODO: This continues with HTTP request even if the record is empty
  72. transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
  73. val records = HueRecords.select { HueRecords.userName eq account }
  74. if (records.empty()) {
  75. ircClient.reply(event, "You do not seem to have set up Hue. Use the 'hue auth' command to get started.")
  76. return@transaction
  77. }
  78. val record = records.first()
  79. token = record[HueRecords.accessToken]
  80. username = record[HueRecords.bridgeUsername]
  81. }
  82. try {
  83. httpClient.put<Unit>("https://api.meethue.com/bridge/$username/lights/$id/state") {
  84. header("Authorization", "Bearer $token")
  85. header("Content-type", "application/json")
  86. body = mapOf("on" to state)
  87. }
  88. ircClient.react(event, "💡 ${if (state) "On" else "Off"}")
  89. } catch (ex : Exception) {
  90. ircClient.reply(event, "+++ Unable to complete action. Please Reinstall Universe And Reboot. +++")
  91. }
  92. }
  93. private suspend fun handleList(account: String, replier: (String) -> Unit) {
  94. lateinit var token: String
  95. lateinit var username: String
  96. transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
  97. val records = HueRecords.select { HueRecords.userName eq account }
  98. if (records.empty()) {
  99. replier("You do not seem to have set up Hue. Use the 'hue auth' command to get started.")
  100. return@transaction
  101. }
  102. val record = records.first()
  103. token = record[HueRecords.accessToken]
  104. username = record[HueRecords.bridgeUsername]
  105. }
  106. val lights = httpClient.get<Map<String, HueLight>>("https://api.meethue.com/bridge/$username/lights") {
  107. header("Authorization", "Bearer $token")
  108. }
  109. lights.forEach { id, light ->
  110. replier("Light $id is a ${light.manufacturerName} ${light.productName} named ${light.name}")
  111. }
  112. }
  113. private suspend fun handleAuth(arg: String?, account: String, replier: (String) -> Unit) {
  114. when (arg) {
  115. null -> replier("Please go to https://api.meethue.com/oauth2/auth?clientid=$clientId&appid=$appId&deviceid=IRC&devicename=irc&state=hexbot&response_type=code and send me the code in another `auth` command")
  116. else ->
  117. try {
  118. val response = httpClient.post<AccessTokenResponse>("https://api.meethue.com/oauth2/token?code=$arg&grant_type=authorization_code") {
  119. body = ""
  120. headers {
  121. set("Authorization", "Basic $basicHeader")
  122. }
  123. }
  124. httpClient.put<Unit>("https://api.meethue.com/bridge/0/config") {
  125. headers {
  126. set("Authorization", "Bearer ${response.accessToken}")
  127. set("Content-Type", "application/json")
  128. }
  129. body = mapOf("linkbutton" to true)
  130. }
  131. val username = httpClient.post<List<Map<String, Map<String, String>>>>("https://api.meethue.com/bridge/") {
  132. headers {
  133. set("Authorization", "Bearer ${response.accessToken}")
  134. set("Content-Type", "application/json")
  135. }
  136. body = mapOf("devicetype" to "hexbot")
  137. }[0]["success"]?.getValue("username")!!
  138. transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
  139. HueRecords.insert {
  140. it[userName] = account
  141. it[accessToken] = response.accessToken!!
  142. it[accessTokenExpiry] = DateTime.now().plusSeconds(response.accessTokenExpiry!!.toInt())
  143. it[refreshToken] = response.refreshToken!!
  144. it[refreshTokenExpiry] = DateTime.now().plusSeconds(response.refreshTokenExpiry!!.toInt())
  145. it[bridgeUsername] = username
  146. }
  147. }
  148. replier("I have successfully authenticated to Hue!")
  149. } catch (ex: Exception) {
  150. ex.printStackTrace()
  151. replier("+++ Unable to complete action. Please Reinstall Universe And Reboot. +++")
  152. }
  153. }
  154. }
  155. }
  156. class AccessTokenResponse {
  157. @SerializedName("access_token")
  158. var accessToken: String? = null
  159. @SerializedName("access_token_expires_in")
  160. var accessTokenExpiry: String? = null
  161. @SerializedName("refresh_token")
  162. var refreshToken: String? = null
  163. @SerializedName("refresh_token_expires_in")
  164. var refreshTokenExpiry: String? = null
  165. }
  166. class HueLight {
  167. var type: String? = null
  168. var name: String? = null
  169. @SerializedName("manufacturername") var manufacturerName: String? = null
  170. @SerializedName("productname") var productName: String? = null
  171. }
  172. object HueRecords : Table() {
  173. val id = varchar("id", 10).primaryKey()
  174. val userName = varchar("name", length = 50)
  175. val accessToken = varchar("access_token", length = 100)
  176. val accessTokenExpiry = datetime("access_token_expiry")
  177. val refreshToken = varchar("refresh_token", length = 100)
  178. val refreshTokenExpiry = datetime("refresh_token_expiry")
  179. val bridgeUsername = varchar("bridge_username", length = 100)
  180. }