123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- package com.chameth.hexbot.modules
-
- import com.chameth.hexbot.isModuleCommand
- import com.chameth.hexbot.moduleCommandArgs
- import com.dmdirc.ktirc.IrcClient
- import com.dmdirc.ktirc.events.IrcEvent
- import com.dmdirc.ktirc.events.MessageReceived
- import com.dmdirc.ktirc.events.react
- import com.dmdirc.ktirc.events.reply
- import com.google.gson.annotations.SerializedName
- import com.ufoscout.properlty.Properlty
- import io.ktor.client.HttpClient
- import io.ktor.client.engine.okhttp.OkHttp
- import io.ktor.client.features.json.GsonSerializer
- import io.ktor.client.features.json.JsonFeature
- import io.ktor.client.request.*
- import org.jetbrains.exposed.sql.SchemaUtils
- import org.jetbrains.exposed.sql.Table
- import org.jetbrains.exposed.sql.insert
- import org.jetbrains.exposed.sql.select
- import org.jetbrains.exposed.sql.transactions.transaction
- import org.joda.time.DateTime
- import java.sql.Connection
- import java.util.*
- import kotlin.contracts.ExperimentalContracts
-
- class Hue : Module {
-
- private var appId: String? = null
- private var clientId: String? = null
- private var clientSecret: String? = null
- private val basicHeader by lazy { Base64.getEncoder().encodeToString("$clientId:$clientSecret".toByteArray()) }
-
- private val httpClient = HttpClient(OkHttp) {
- engine {
- config {
- followRedirects(true)
- }
- }
- install(JsonFeature) {
- serializer = GsonSerializer()
- }
- }
-
- override fun init(settings: Properlty): Boolean {
- clientId = settings["hue.clientid", null]
- clientSecret = settings["hue.clientsecret", null]
- appId = settings["hue.appid", null]
-
- if (clientId == null || clientSecret == null || appId == null) {
- return false
- }
-
- transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
- SchemaUtils.create(HueRecords)
- }
-
- return true
- }
-
- @ExperimentalContracts
- override suspend fun processEvent(client: IrcClient, event: IrcEvent) {
- if (client.isModuleCommand(event, "hue")) {
- val account = event.user.account ?: client.userState[event.user]?.details?.account
- if (account == null) {
- client.reply(event, "You must be authenticated to use this command", true)
- return
- }
-
- val args = event.moduleCommandArgs
- when (args.firstOrNull()) {
- "auth" -> handleAuth(args.getOrNull(1), account) { client.reply(event, it, true) }
- "list" -> handleList(account) { client.reply(event, it, true) }
- "turn" -> handleTurn(account, args[1].toInt(), args[2] == "on", client, event)
- }
- }
- }
-
- private suspend fun handleTurn(account: String, id: Int, state: Boolean, ircClient: IrcClient, event: MessageReceived) {
- lateinit var token: String
- lateinit var username: String
-
- // TODO: This continues with HTTP request even if the record is empty
- transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
- val records = HueRecords.select { HueRecords.userName eq account }
- if (records.empty()) {
- ircClient.reply(event, "You do not seem to have set up Hue. Use the 'hue auth' command to get started.")
- return@transaction
- }
-
- val record = records.first()
- token = record[HueRecords.accessToken]
- username = record[HueRecords.bridgeUsername]
- }
-
- try {
- httpClient.put<Unit>("https://api.meethue.com/bridge/$username/lights/$id/state") {
- header("Authorization", "Bearer $token")
- header("Content-type", "application/json")
- body = mapOf("on" to state)
- }
- ircClient.react(event, "💡 ${if (state) "On" else "Off"}")
- } catch (ex : Exception) {
- ircClient.reply(event, "+++ Unable to complete action. Please Reinstall Universe And Reboot. +++")
- }
- }
-
- private suspend fun handleList(account: String, replier: (String) -> Unit) {
- lateinit var token: String
- lateinit var username: String
-
- transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
- val records = HueRecords.select { HueRecords.userName eq account }
- if (records.empty()) {
- replier("You do not seem to have set up Hue. Use the 'hue auth' command to get started.")
- return@transaction
- }
-
- val record = records.first()
- token = record[HueRecords.accessToken]
- username = record[HueRecords.bridgeUsername]
- }
-
- val lights = httpClient.get<Map<String, HueLight>>("https://api.meethue.com/bridge/$username/lights") {
- header("Authorization", "Bearer $token")
- }
- lights.forEach { id, light ->
- replier("Light $id is a ${light.manufacturerName} ${light.productName} named ${light.name}")
- }
- }
-
- private suspend fun handleAuth(arg: String?, account: String, replier: (String) -> Unit) {
- when (arg) {
- 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")
- else ->
- try {
- val response = httpClient.post<AccessTokenResponse>("https://api.meethue.com/oauth2/token?code=$arg&grant_type=authorization_code") {
- body = ""
- headers {
- set("Authorization", "Basic $basicHeader")
- }
- }
-
- httpClient.put<Unit>("https://api.meethue.com/bridge/0/config") {
- headers {
- set("Authorization", "Bearer ${response.accessToken}")
- set("Content-Type", "application/json")
- }
- body = mapOf("linkbutton" to true)
- }
-
- val username = httpClient.post<List<Map<String, Map<String, String>>>>("https://api.meethue.com/bridge/") {
- headers {
- set("Authorization", "Bearer ${response.accessToken}")
- set("Content-Type", "application/json")
- }
- body = mapOf("devicetype" to "hexbot")
- }[0]["success"]?.getValue("username")!!
-
- transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
- HueRecords.insert {
- it[userName] = account
- it[accessToken] = response.accessToken!!
- it[accessTokenExpiry] = DateTime.now().plusSeconds(response.accessTokenExpiry!!.toInt())
- it[refreshToken] = response.refreshToken!!
- it[refreshTokenExpiry] = DateTime.now().plusSeconds(response.refreshTokenExpiry!!.toInt())
- it[bridgeUsername] = username
- }
- }
-
- replier("I have successfully authenticated to Hue!")
- } catch (ex: Exception) {
- ex.printStackTrace()
- replier("+++ Unable to complete action. Please Reinstall Universe And Reboot. +++")
- }
- }
- }
-
- }
-
- class AccessTokenResponse {
- @SerializedName("access_token")
- var accessToken: String? = null
-
- @SerializedName("access_token_expires_in")
- var accessTokenExpiry: String? = null
-
- @SerializedName("refresh_token")
- var refreshToken: String? = null
-
- @SerializedName("refresh_token_expires_in")
- var refreshTokenExpiry: String? = null
- }
-
- class HueLight {
- var type: String? = null
- var name: String? = null
- @SerializedName("manufacturername") var manufacturerName: String? = null
- @SerializedName("productname") var productName: String? = null
- }
-
- object HueRecords : Table() {
- val id = varchar("id", 10).primaryKey()
- val userName = varchar("name", length = 50)
- val accessToken = varchar("access_token", length = 100)
- val accessTokenExpiry = datetime("access_token_expiry")
- val refreshToken = varchar("refresh_token", length = 100)
- val refreshTokenExpiry = datetime("refresh_token_expiry")
- val bridgeUsername = varchar("bridge_username", length = 100)
- }
|