From f4a56553cc9d3b68b45ce60228a823a88a872a73 Mon Sep 17 00:00:00 2001 From: Hamza Ali Date: Sat, 12 Oct 2019 11:56:25 +0700 Subject: [PATCH] kotlin: refactor entire plugin to use mc-kutils, use an object-based configuration system, and convert to okhttp --- KotlinPlugin/.gitignore | 1 + .../com/terraocean/plugin/Configuration.kt | 49 ++++++++++ .../com/terraocean/plugin/TerraOceanPlugin.kt | 23 ++--- .../com/terraocean/plugin/afk/AFKRecorder.kt | 29 +++--- .../com/terraocean/plugin/bridge/AFKBridge.kt | 9 +- .../com/terraocean/plugin/bridge/WebSocket.kt | 89 +++++++++---------- 6 files changed, 115 insertions(+), 85 deletions(-) create mode 100644 KotlinPlugin/src/main/kotlin/com/terraocean/plugin/Configuration.kt diff --git a/KotlinPlugin/.gitignore b/KotlinPlugin/.gitignore index 4cfdd8e..31a5838 100644 --- a/KotlinPlugin/.gitignore +++ b/KotlinPlugin/.gitignore @@ -2,3 +2,4 @@ build/ gradle/ .gradle/ .idea/ +**/out diff --git a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/Configuration.kt b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/Configuration.kt new file mode 100644 index 0000000..cb0f60b --- /dev/null +++ b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/Configuration.kt @@ -0,0 +1,49 @@ +package com.terraocean.plugin + +import hazae41.minecraft.kutils.bukkit.ConfigFile +import hazae41.minecraft.kutils.get +import java.io.File +import kotlin.reflect.KProperty + +object Settings : TerraConfig(instance.dataFolder["config.yml"]) { + val socketURL by item("socket.url", "ws://localhost:8080/ws") +} + +object Strings : TerraConfig(instance.dataFolder["strings.yml"]) { + val voteNew by message("vote.new") + val voteWarn by message("vote.warn") + val voteTimeout by message("vote.timeout") + + val kickVoiceChannel by message("kick.voiceChannelLeave") + val voteKick by message("kick.voteKick") + val kickRemovedFromWhitelist by message("kick.removedFromWhitelist") +} + +open class TerraConfig(file: File) : ConfigFile(file) { + inner class message(val path: String) { + init { + if (!config.contains(path)) { + set(path, "Message not found: $path") + } + } + + operator fun getValue(ref: Any?, prop: KProperty<*>): String = + config.getString(path, "Message not found: $path")!! + + operator fun setValue(ref: Any?, prop: KProperty<*>, value: String) = set(path, value) + } + + inner class item(private val path: String, private val default: T) { + init { + if (!config.contains(path)) { + set(path, default) + } + } + + @Suppress("UNCHECKED_CAST") + operator fun getValue(ref: Any?, prop: KProperty<*>): T = + config.get(path, default) as? T ?: run { + throw IllegalStateException("Path $path [value ${config["path"]}]] does not conform to type required") + } + } +} \ No newline at end of file diff --git a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/TerraOceanPlugin.kt b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/TerraOceanPlugin.kt index 59f0f22..f5add5e 100644 --- a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/TerraOceanPlugin.kt +++ b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/TerraOceanPlugin.kt @@ -1,26 +1,21 @@ package com.terraocean.plugin +import com.terraocean.plugin.afk.onPlayerMovement import com.terraocean.plugin.bridge.establishConnection -import io.ktor.util.KtorExperimentalAPI -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import org.bukkit.plugin.java.JavaPlugin -import java.io.File +import hazae41.minecraft.kutils.bukkit.* internal lateinit var instance: TerraOceanPlugin -class TerraOceanPlugin: JavaPlugin() { - @KtorExperimentalAPI +class TerraOceanPlugin: BukkitPlugin() { override fun onEnable() { instance = this - config.load(File("plugin/terraocean.yml")) - MainScope().launch { + Strings // Initialize the config classes + Settings + + listen(callback = ::onPlayerMovement) + + schedule(async = true) { establishConnection() } - logger.info("TerraOcean plugin has started.") - } - override fun onDisable() { - logger.info("TerraOcean plugin has stopped.") } } diff --git a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/afk/AFKRecorder.kt b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/afk/AFKRecorder.kt index 0995c85..29add38 100644 --- a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/afk/AFKRecorder.kt +++ b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/afk/AFKRecorder.kt @@ -1,8 +1,6 @@ package com.terraocean.plugin.afk import com.terraocean.plugin.bridge.reportPlayerActivity -import org.bukkit.event.EventHandler -import org.bukkit.event.Listener import org.bukkit.event.player.PlayerMoveEvent //activityRequirement is the time it takes for a player to be considered inactive. @@ -11,23 +9,20 @@ const val activityRequirement = 1000 * 30 var lastMovement = HashMap() var lastRecord = HashMap() -class AFKRecorder: Listener { - @EventHandler - fun onPlayerMovement(e: PlayerMoveEvent) { - val playerID = e.player.uniqueId.toString() - val time = System.currentTimeMillis() +fun onPlayerMovement(e: PlayerMoveEvent) { + val playerID = e.player.uniqueId.toString() + val time = System.currentTimeMillis() - lastMovement[playerID]?.let{ lastActive -> - if (time <= lastActive + activityRequirement) { - return@let - } - reportPlayerActivity(e.player.name, lastActive-lastRecord[playerID]!!) - lastRecord[playerID] = time + lastMovement[playerID]?.let { lastActive -> + if (time <= lastActive + activityRequirement) { + return@let } + reportPlayerActivity(e.player.name, lastActive - lastRecord[playerID]!!) + lastRecord[playerID] = time + } - if (lastRecord[playerID] == null) { - lastRecord[playerID] = time - } - lastMovement[playerID] = time + if (lastRecord[playerID] == null) { + lastRecord[playerID] = time } + lastMovement[playerID] = time } diff --git a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/AFKBridge.kt b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/AFKBridge.kt index fcfffa0..e13de2a 100644 --- a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/AFKBridge.kt +++ b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/AFKBridge.kt @@ -1,11 +1,10 @@ package com.terraocean.plugin.bridge -import io.ktor.http.cio.websocket.send -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch +import com.terraocean.plugin.instance +import hazae41.minecraft.kutils.bukkit.schedule fun reportPlayerActivity(username: String, activity: Long) { - MainScope().launch { - webSocketSession.send("active $username $activity") + instance.schedule(async = true) { + socket.send("active $username $activity") } } \ No newline at end of file diff --git a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/WebSocket.kt b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/WebSocket.kt index b9b2d68..9c30403 100644 --- a/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/WebSocket.kt +++ b/KotlinPlugin/src/main/kotlin/com/terraocean/plugin/bridge/WebSocket.kt @@ -1,84 +1,75 @@ package com.terraocean.plugin.bridge +import com.terraocean.plugin.Settings +import com.terraocean.plugin.Strings import com.terraocean.plugin.instance -import io.ktor.client.HttpClient -import io.ktor.client.features.websocket.DefaultClientWebSocketSession -import io.ktor.client.features.websocket.WebSockets -import io.ktor.client.features.websocket.ws -import io.ktor.http.cio.websocket.Frame -import io.ktor.http.cio.websocket.readText -import io.ktor.util.KtorExperimentalAPI +import hazae41.minecraft.kutils.bukkit.info +import hazae41.minecraft.kutils.bukkit.warning +import okhttp3.* import org.bukkit.Bukkit +import java.util.concurrent.TimeUnit -@KtorExperimentalAPI -val client = HttpClient { - install(WebSockets) -} +val client = OkHttpClient.Builder() + .readTimeout(0, TimeUnit.MILLISECONDS) + .build() -lateinit var webSocketSession: DefaultClientWebSocketSession +lateinit var socket: WebSocket -@KtorExperimentalAPI -suspend fun establishConnection() { - client.ws( - host = "localhost", - port = 8080, - path = "/ws" - ) { - webSocketSession = this - while (true) { - val message = incoming.receive() - if (message is Frame.Text) { - processMessage(message.readText().split(' ')) - } - } - } +fun establishConnection() { + instance.info("establishing connection to ${Settings.socketURL}...") + val request = Request.Builder() + .url(Settings.socketURL) + .build() + + socket = client.newWebSocket(request, WSListener()) } fun processMessage(msg: List) { when (msg[0]) { "leave" -> { - Bukkit.getPlayer(msg[1])?.kickPlayer( - instance.config.getString("voicechannel") - ) + Bukkit.getPlayer(msg[1])?.kickPlayer(Strings.kickVoiceChannel) } "vote" -> { - for (p in Bukkit.getOnlinePlayers()) { - p.sendMessage( - instance.config.getString("vote.new") ?: "Message not found: vote.new" - ) + Bukkit.getOnlinePlayers().forEach { + it.sendMessage(Strings.voteNew) } } "votewarn" -> { - Bukkit.getPlayer(msg[1])?.sendMessage( - instance.config.getString("vote.warn") ?: "Message not found: vote.warn" - ) + Bukkit.getPlayer(msg[1])?.sendMessage(Strings.voteWarn) } "votetimeout" -> { - Bukkit.getPlayer(msg[1])?.kickPlayer( - instance.config.getString("vote.timeout") ?: "Message not found: vote.timeout" - ) + Bukkit.getPlayer(msg[1])?.kickPlayer(Strings.voteTimeout) } "kick" -> { - Bukkit.getPlayer(msg[1])?.kickPlayer( - instance.config.getString("kick.votekick") ?: "Message not found: kick.votekick" - ) + Bukkit.getPlayer(msg[1])?.kickPlayer(Strings.voteKick) } "whitelistadd" -> { - instance.server.run { + instance.server.run { getOfflinePlayer(msg[1]).isWhitelisted = true } } "whitelistremove" -> { instance.server.run { - getPlayer(msg[1])?.kickPlayer( - instance.config.getString("kick.removedFromWhitelist") ?: - "Message not found: kick.removedFromWhitelist" - ) + getPlayer(msg[1])?.kickPlayer(Strings.kickRemovedFromWhitelist) getOfflinePlayer(msg[1]).isWhitelisted = false } } - else -> instance.logger.warning( + else -> instance.warning( "Potentially out of date plugin. Unknown message: " + msg.joinToString(" ") ) } } + +class WSListener: WebSocketListener() { + override fun onMessage(webSocket: WebSocket, text: String) { + processMessage(text.split(' ')) + } + + override fun onOpen(webSocket: WebSocket, response: Response) { + instance.info("Socket connection established to $response.") + } + + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + TODO("Automatic reconnection") + } +} \ No newline at end of file