From d287800763aaac9616f7307e1ca9e44faf9f5013 Mon Sep 17 00:00:00 2001 From: rfresh2 <89827146+rfresh2@users.noreply.github.com> Date: Tue, 4 Apr 2023 20:21:06 -0700 Subject: [PATCH 1/3] Seen, Playtime Commands + 2b2t queue API update --- .../command/commands/PlaytimeCommand.kt | 55 ++++++++++++++++++ .../client/command/commands/SeenCommand.kt | 43 ++++++++++++++ .../client/commons/utils/ConnectionUtils.kt | 9 +-- .../gui/hudgui/elements/misc/Queue2B2T.kt | 57 +++++++++++-------- 4 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt create mode 100644 src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt diff --git a/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt new file mode 100644 index 000000000..f049d076d --- /dev/null +++ b/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt @@ -0,0 +1,55 @@ +package com.lambda.client.command.commands + +import com.google.gson.JsonParser +import com.lambda.client.command.ClientCommand +import com.lambda.client.commons.utils.ConnectionUtils +import com.lambda.client.manager.managers.UUIDManager +import com.lambda.client.util.text.MessageSendHelper + +object PlaytimeCommand: ClientCommand( + name = "playtime", + alias = arrayOf("pt"), + description = "Check a player's playtime on 2b2t" +) { + private val parser = JsonParser() + + init { + string("playerName") { playerName -> + executeAsync("Check a player's playtime on 2b2t") { + UUIDManager.getByName(playerName.value)?.let { profile -> + ConnectionUtils.requestRawJsonFrom("https://api.2b2t.vc/playtime?uuid=${profile.uuid}") { + MessageSendHelper.sendChatMessage("Failed querying playtime data for player: ${it.message}") + }?.let { + if (it.isEmpty()) { + MessageSendHelper.sendChatMessage("No data found for player: ${profile.name}") + return@let + } + val jsonElement = parser.parse(it) + val playtimeSeconds = jsonElement.asJsonObject["playtimeSeconds"].asInt + MessageSendHelper.sendChatMessage("${profile.name} has played for ${formatDuration(playtimeSeconds.toLong())}") + } + } ?: run{ + MessageSendHelper.sendChatMessage("Failed to find player with name ${playerName.value}") + } + } + } + } + + private fun formatDuration(durationInSeconds: Long): String { + val secondsInMinute = 60L + val secondsInHour = secondsInMinute * 60L + val secondsInDay = secondsInHour * 24L + val secondsInMonth = secondsInDay * 30L // assuming 30 days per month + + val months = durationInSeconds / secondsInMonth + val days = (durationInSeconds % secondsInMonth) / secondsInDay + val hours = (durationInSeconds % secondsInDay) / secondsInHour + return buildString { + append(if(months > 0) "$months month${if(months != 1L) "s" else ""}, " else "") + append(if(days > 0) "$days day${if(days != 1L) "s" else ""}, " else "") + append(hours) + append(" hour") + append(if(hours != 1L) "s" else "") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt new file mode 100644 index 000000000..51ae08a92 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt @@ -0,0 +1,43 @@ +package com.lambda.client.command.commands + +import com.google.gson.JsonParser +import com.lambda.client.command.ClientCommand +import com.lambda.client.commons.utils.ConnectionUtils +import com.lambda.client.manager.managers.UUIDManager +import com.lambda.client.util.text.MessageSendHelper +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +object SeenCommand : ClientCommand( + name = "seen", + alias = arrayOf("lastseen"), + description = "Check when a player was last seen" +) { + + private val parser = JsonParser() + + init { + string("playerName") { playerName -> + executeAsync("Check when a player was last seen") { + UUIDManager.getByName(playerName.value)?.let { profile -> + ConnectionUtils.requestRawJsonFrom("https://api.2b2t.vc/seen?uuid=${profile.uuid}") { + MessageSendHelper.sendChatMessage("Failed querying seen data for player: ${it.message}") + }?.let { + if (it.isEmpty()) { + MessageSendHelper.sendChatMessage("No data found for player: ${profile.name}") + return@let + } + val jsonElement = parser.parse(it) + val dateRaw = jsonElement.asJsonObject["time"].asString + val parsedDate = ZonedDateTime.parse(dateRaw).withZoneSameInstant(ZoneId.systemDefault()) + val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.LONG) + MessageSendHelper.sendChatMessage("${profile.name} was last seen on ${parsedDate.format(dateFormatter)}") + } + } ?: run { + MessageSendHelper.sendChatMessage("Failed to find player with name ${playerName.value}") + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/commons/utils/ConnectionUtils.kt b/src/main/kotlin/com/lambda/client/commons/utils/ConnectionUtils.kt index b72478aa8..0280abaee 100644 --- a/src/main/kotlin/com/lambda/client/commons/utils/ConnectionUtils.kt +++ b/src/main/kotlin/com/lambda/client/commons/utils/ConnectionUtils.kt @@ -1,13 +1,15 @@ package com.lambda.client.commons.utils import com.lambda.client.module.modules.client.Plugins +import java.net.HttpURLConnection import java.net.URL -import javax.net.ssl.HttpsURLConnection object ConnectionUtils { fun requestRawJsonFrom(url: String, catch: (Exception) -> Unit = { it.printStackTrace() }): String? { return runConnection(url, { connection -> + connection.setRequestProperty("User-Agent", "LambdaClient") + connection.setRequestProperty("Connection", "close") connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8") if (Plugins.token.isNotBlank()) connection.setRequestProperty("Authorization", "token ${Plugins.token}") connection.requestMethod = "GET" @@ -15,8 +17,8 @@ object ConnectionUtils { }, catch) } - fun runConnection(url: String, block: (HttpsURLConnection) -> T?, catch: (Exception) -> Unit = { it.printStackTrace() }): T? { - (URL(url).openConnection() as HttpsURLConnection).run { + fun runConnection(url: String, block: (HttpURLConnection) -> T?, catch: (Exception) -> Unit = { it.printStackTrace() }): T? { + (URL(url).openConnection() as HttpURLConnection).run { return try { doOutput = true doInput = true @@ -29,5 +31,4 @@ object ConnectionUtils { } } } - } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt index e981b0117..8c4243d98 100644 --- a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt +++ b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt @@ -1,7 +1,8 @@ package com.lambda.client.gui.hudgui.elements.misc import com.google.gson.Gson -import com.google.gson.annotations.SerializedName +import com.lambda.client.LambdaMod +import com.lambda.client.commons.utils.ConnectionUtils import com.lambda.client.commons.utils.grammar import com.lambda.client.event.SafeClientEvent import com.lambda.client.gui.hudgui.LabelHud @@ -9,11 +10,12 @@ import com.lambda.client.manager.managers.NetworkManager import com.lambda.client.util.CachedValue import com.lambda.client.util.TickTimer import com.lambda.client.util.TimeUnit -import com.lambda.client.util.WebUtils import com.lambda.client.util.text.MessageSendHelper import com.lambda.client.util.threads.defaultScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.time.Instant +import java.time.ZonedDateTime internal object Queue2B2T : LabelHud( name = "2B2T Queue", @@ -22,22 +24,26 @@ internal object Queue2B2T : LabelHud( ) { private val hasShownWarning = setting("Has Shown Warning", false, { false }) private val show by setting("Show", Show.BOTH) + private val showUpdatedTime by setting("Show Updated Time", true) private enum class Show { BOTH, PRIORITY, REGULAR } - private const val apiUrl = "https://2bqueue.info/queue" + private const val apiUrl = "https://api.2b2t.vc/queue" private val gson = Gson() private val dataUpdateTimer = TickTimer(TimeUnit.SECONDS) + private var hasInitialized = false - private var queueData = QueueData(0, 0, 0, 0) + private var queueData = QueueData(0, 0, ZonedDateTime.now().toString()) private val lastUpdate by CachedValue(1L, TimeUnit.SECONDS) { - val difference = System.currentTimeMillis() - queueData.lastUpdated + val dateRaw = queueData.time + val parsedDate = ZonedDateTime.parse(dateRaw) + val difference = Instant.now().epochSecond - parsedDate.toEpochSecond() - val minuteAmt = (difference / 60000L % 60L).toInt() - val secondAmt = (difference / 1000L % 60L).toInt() + val minuteAmt = (difference / 60L % 60L).toInt() + val secondAmt = (difference % 60L).toInt() val minutes = grammar(minuteAmt, "minute", "minutes") val seconds = grammar(secondAmt, "second", "seconds") @@ -53,32 +59,35 @@ internal object Queue2B2T : LabelHud( sendWarning() } - if (dataUpdateTimer.tick(15L)) { + if (dataUpdateTimer.tick(300L) // API caches queue data for 5 minutes + || !hasInitialized) { + hasInitialized = true updateQueueData() } if (NetworkManager.isOffline) { - displayText.addLine("Cannot connect to 2bqueue.info", primaryColor) + displayText.addLine("Cannot connect to api.2b2t.vc", primaryColor) displayText.add("Make sure your internet is working!", primaryColor) } else { if (showPriority) { displayText.add("Priority: ", primaryColor) - displayText.add("${queueData.priority}", secondaryColor) + displayText.add("${queueData.prio}", secondaryColor) } if (showRegular) { displayText.add("Regular: ", primaryColor) displayText.add("${queueData.regular}", secondaryColor) } - - displayText.addLine("", primaryColor) - displayText.add("Last updated $lastUpdate ago", primaryColor) + if (showUpdatedTime) { + displayText.addLine("", primaryColor) + displayText.add("Last updated $lastUpdate ago", primaryColor) + } } } private fun sendWarning() { MessageSendHelper.sendWarningMessage( - "This module uses an external API, 2bqueue.info, which is operated by tycrek#0001." + + "This module uses an external API, api.2b2t.vc, which is operated by rfresh#2222." + "If you do not trust this external API / have not verified the safety yourself, disable this HUD component." ) hasShownWarning.value = true @@ -87,10 +96,15 @@ internal object Queue2B2T : LabelHud( private fun updateQueueData() { defaultScope.launch(Dispatchers.IO) { runCatching { - val json = WebUtils.getUrlContents(apiUrl) - gson.fromJson(json, QueueData::class.java) - }.getOrNull()?.let { - queueData = it + ConnectionUtils.requestRawJsonFrom(apiUrl) { + LambdaMod.LOG.error("Failed querying queue data", it) + }?.let { + gson.fromJson(it, QueueData::class.java)?.let { data -> + queueData = data + } ?: run { + LambdaMod.LOG.error("No queue data received. Is 2b2t down?") + } + } } } } @@ -99,11 +113,8 @@ internal object Queue2B2T : LabelHud( private val showRegular get() = show == Show.BOTH || show == Show.REGULAR private class QueueData( - @SerializedName("prio") - val priority: Int, + val prio: Int, val regular: Int, - val total: Int, - @SerializedName("timems") - val lastUpdated: Long + val time: String ) } From 03bad181ac944ca5dc63212906487032d1f2ab25 Mon Sep 17 00:00:00 2001 From: rfresh2 <89827146+rfresh2@users.noreply.github.com> Date: Sat, 13 May 2023 15:10:30 -0700 Subject: [PATCH 2/3] Use grammar helper for playtime command --- .../lambda/client/command/commands/PlaytimeCommand.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt index f049d076d..a96403619 100644 --- a/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt +++ b/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt @@ -3,6 +3,7 @@ package com.lambda.client.command.commands import com.google.gson.JsonParser import com.lambda.client.command.ClientCommand import com.lambda.client.commons.utils.ConnectionUtils +import com.lambda.client.commons.utils.grammar import com.lambda.client.manager.managers.UUIDManager import com.lambda.client.util.text.MessageSendHelper @@ -45,11 +46,9 @@ object PlaytimeCommand: ClientCommand( val days = (durationInSeconds % secondsInMonth) / secondsInDay val hours = (durationInSeconds % secondsInDay) / secondsInHour return buildString { - append(if(months > 0) "$months month${if(months != 1L) "s" else ""}, " else "") - append(if(days > 0) "$days day${if(days != 1L) "s" else ""}, " else "") - append(hours) - append(" hour") - append(if(hours != 1L) "s" else "") + append(if(months > 0) "${grammar(months.toInt(), "month", "months")}, " else "") + append(if(days > 0) "${grammar(days.toInt(), "day", "days")}, " else "") + append(grammar(hours.toInt(), "hour", "hours")) } } } \ No newline at end of file From e16d90797063340a02b36061d0d194afce2cb770 Mon Sep 17 00:00:00 2001 From: Constructor Date: Wed, 12 Jul 2023 02:11:56 +0200 Subject: [PATCH 3/3] Cleanup --- .../command/commands/PlaytimeCommand.kt | 34 +++++++------------ .../client/command/commands/SeenCommand.kt | 10 +++--- .../gui/hudgui/elements/misc/Queue2B2T.kt | 34 ++++++++++--------- 3 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt index a96403619..a1f3c7f0a 100644 --- a/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt +++ b/src/main/kotlin/com/lambda/client/command/commands/PlaytimeCommand.kt @@ -6,6 +6,8 @@ import com.lambda.client.commons.utils.ConnectionUtils import com.lambda.client.commons.utils.grammar import com.lambda.client.manager.managers.UUIDManager import com.lambda.client.util.text.MessageSendHelper +import kotlin.time.DurationUnit +import kotlin.time.toDuration object PlaytimeCommand: ClientCommand( name = "playtime", @@ -17,38 +19,26 @@ object PlaytimeCommand: ClientCommand( init { string("playerName") { playerName -> executeAsync("Check a player's playtime on 2b2t") { - UUIDManager.getByName(playerName.value)?.let { profile -> + UUIDManager.getByName(playerName.value)?.let outer@ { profile -> ConnectionUtils.requestRawJsonFrom("https://api.2b2t.vc/playtime?uuid=${profile.uuid}") { MessageSendHelper.sendChatMessage("Failed querying playtime data for player: ${it.message}") }?.let { if (it.isEmpty()) { MessageSendHelper.sendChatMessage("No data found for player: ${profile.name}") - return@let + return@outer } val jsonElement = parser.parse(it) - val playtimeSeconds = jsonElement.asJsonObject["playtimeSeconds"].asInt - MessageSendHelper.sendChatMessage("${profile.name} has played for ${formatDuration(playtimeSeconds.toLong())}") + val playtimeSeconds = jsonElement.asJsonObject["playtimeSeconds"].asDouble + MessageSendHelper.sendChatMessage("${profile.name} has played for ${ + playtimeSeconds.toDuration(DurationUnit.SECONDS) + }") } - } ?: run{ - MessageSendHelper.sendChatMessage("Failed to find player with name ${playerName.value}") - } - } - } - } - private fun formatDuration(durationInSeconds: Long): String { - val secondsInMinute = 60L - val secondsInHour = secondsInMinute * 60L - val secondsInDay = secondsInHour * 24L - val secondsInMonth = secondsInDay * 30L // assuming 30 days per month + return@executeAsync + } - val months = durationInSeconds / secondsInMonth - val days = (durationInSeconds % secondsInMonth) / secondsInDay - val hours = (durationInSeconds % secondsInDay) / secondsInHour - return buildString { - append(if(months > 0) "${grammar(months.toInt(), "month", "months")}, " else "") - append(if(days > 0) "${grammar(days.toInt(), "day", "days")}, " else "") - append(grammar(hours.toInt(), "hour", "hours")) + MessageSendHelper.sendChatMessage("Failed to find player with name ${playerName.value}") + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt index 51ae08a92..81e1e3c91 100644 --- a/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt +++ b/src/main/kotlin/com/lambda/client/command/commands/SeenCommand.kt @@ -20,13 +20,13 @@ object SeenCommand : ClientCommand( init { string("playerName") { playerName -> executeAsync("Check when a player was last seen") { - UUIDManager.getByName(playerName.value)?.let { profile -> + UUIDManager.getByName(playerName.value)?.let outer@ { profile -> ConnectionUtils.requestRawJsonFrom("https://api.2b2t.vc/seen?uuid=${profile.uuid}") { MessageSendHelper.sendChatMessage("Failed querying seen data for player: ${it.message}") }?.let { if (it.isEmpty()) { MessageSendHelper.sendChatMessage("No data found for player: ${profile.name}") - return@let + return@outer } val jsonElement = parser.parse(it) val dateRaw = jsonElement.asJsonObject["time"].asString @@ -34,9 +34,11 @@ object SeenCommand : ClientCommand( val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.LONG) MessageSendHelper.sendChatMessage("${profile.name} was last seen on ${parsedDate.format(dateFormatter)}") } - } ?: run { - MessageSendHelper.sendChatMessage("Failed to find player with name ${playerName.value}") + + return@executeAsync } + + MessageSendHelper.sendChatMessage("Failed to find player with name ${playerName.value}") } } } diff --git a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt index 8c4243d98..a7696736c 100644 --- a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt +++ b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/Queue2B2T.kt @@ -68,27 +68,28 @@ internal object Queue2B2T : LabelHud( if (NetworkManager.isOffline) { displayText.addLine("Cannot connect to api.2b2t.vc", primaryColor) displayText.add("Make sure your internet is working!", primaryColor) - } else { - if (showPriority) { - displayText.add("Priority: ", primaryColor) - displayText.add("${queueData.prio}", secondaryColor) - } + return + } - if (showRegular) { - displayText.add("Regular: ", primaryColor) - displayText.add("${queueData.regular}", secondaryColor) - } - if (showUpdatedTime) { - displayText.addLine("", primaryColor) - displayText.add("Last updated $lastUpdate ago", primaryColor) - } + if (showPriority) { + displayText.add("Priority: ", primaryColor) + displayText.add("${queueData.prio}", secondaryColor) + } + + if (showRegular) { + displayText.add("Regular: ", primaryColor) + displayText.add("${queueData.regular}", secondaryColor) + } + if (showUpdatedTime) { + displayText.addLine("", primaryColor) + displayText.add("Last updated $lastUpdate ago", primaryColor) } } private fun sendWarning() { MessageSendHelper.sendWarningMessage( "This module uses an external API, api.2b2t.vc, which is operated by rfresh#2222." + - "If you do not trust this external API / have not verified the safety yourself, disable this HUD component." + " If you do not trust this external API / have not verified the safety yourself, disable this HUD component." ) hasShownWarning.value = true } @@ -101,9 +102,10 @@ internal object Queue2B2T : LabelHud( }?.let { gson.fromJson(it, QueueData::class.java)?.let { data -> queueData = data - } ?: run { - LambdaMod.LOG.error("No queue data received. Is 2b2t down?") + return@runCatching } + + LambdaMod.LOG.error("No queue data received. Is 2b2t down?") } } }