From b2741c8acb5e8c52186e7a39a39ab339a5ff1187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:29:47 +0800 Subject: [PATCH] refactor(legacy): improve downloads 1. fix StaffDetector incorrect use 2. preload SRG remapper file 3. HTTP code cleanup --- .../net/ccbluex/liquidbounce/LiquidBounce.kt | 5 + .../net/ccbluex/liquidbounce/api/ClientApi.kt | 62 +--- .../ccbluex/liquidbounce/cape/CapeService.kt | 50 +-- .../liquidbounce/config/SettingsUtils.kt | 32 +- .../command/commands/SettingsCommand.kt | 12 +- .../module/modules/misc/StaffDetector.kt | 82 ++--- .../features/special/ClientRichPresence.kt | 6 +- .../liquidbounce/script/remapper/Remapper.kt | 19 +- .../net/ccbluex/liquidbounce/tabs/HeadsTab.kt | 8 +- .../liquidbounce/ui/client/GuiContributors.kt | 17 +- .../liquidbounce/ui/client/GuiServerStatus.kt | 12 +- .../ui/client/altmanager/GuiAltManager.kt | 6 +- .../net/ccbluex/liquidbounce/ui/font/Fonts.kt | 3 +- .../liquidbounce/utils/io/HttpUtils.kt | 345 ++++++++---------- .../liquidbounce/utils/login/UserUtils.kt | 12 +- 15 files changed, 296 insertions(+), 375 deletions(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt b/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt index 24f1a55f47a..5b582647a8c 100644 --- a/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt +++ b/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt @@ -50,6 +50,7 @@ import net.ccbluex.liquidbounce.utils.inventory.InventoryManager import net.ccbluex.liquidbounce.utils.inventory.InventoryUtils import net.ccbluex.liquidbounce.utils.inventory.SilentHotbar import net.ccbluex.liquidbounce.utils.io.MiscUtils +import net.ccbluex.liquidbounce.utils.io.MiscUtils.showErrorPopup import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.movement.BPSUtils import net.ccbluex.liquidbounce.utils.movement.MovementUtils @@ -140,6 +141,9 @@ object LiquidBounce { // Load alt generators loadActiveGenerators() + // Load SRG file + loadSrg() + LOGGER.info("Preload tasks of $CLIENT_NAME are completed!") future.complete(Unit) @@ -259,6 +263,7 @@ object LiquidBounce { FileManager.loadBackground() } catch (e: Exception) { LOGGER.error("Failed to start client: ${e.message}") + e.showErrorPopup() } finally { // Set is starting status isStarting = false diff --git a/src/main/java/net/ccbluex/liquidbounce/api/ClientApi.kt b/src/main/java/net/ccbluex/liquidbounce/api/ClientApi.kt index 1d0d4cb8738..a7327f4ee61 100644 --- a/src/main/java/net/ccbluex/liquidbounce/api/ClientApi.kt +++ b/src/main/java/net/ccbluex/liquidbounce/api/ClientApi.kt @@ -1,8 +1,10 @@ package net.ccbluex.liquidbounce.api import net.ccbluex.liquidbounce.LiquidBounce -import net.ccbluex.liquidbounce.utils.io.HttpUtils.applyBypassHttps +import net.ccbluex.liquidbounce.utils.io.applyBypassHttps import net.ccbluex.liquidbounce.utils.io.decodeJson +import net.ccbluex.liquidbounce.utils.io.get +import net.ccbluex.liquidbounce.utils.io.post import net.ccbluex.liquidbounce.utils.kotlin.RandomUtils import okhttp3.MultipartBody import okhttp3.OkHttpClient @@ -49,71 +51,46 @@ object ClientApi { fun getNewestBuild(branch: String = HARD_CODED_BRANCH, release: Boolean = false): Build { val url = "$API_V1_ENDPOINT/version/newest/$branch${if (release) "/release" else "" }" - val request = Request.Builder() - .url(url) - .get() - .build() - - client.newCall(request).execute().use { response -> + client.get(url).use { response -> if (!response.isSuccessful) error("Request failed: ${response.code}") - return response.body!!.charStream().decodeJson() + return response.body.charStream().decodeJson() } } fun getMessageOfTheDay(branch: String = HARD_CODED_BRANCH): MessageOfTheDay { val url = "$API_V1_ENDPOINT/client/$branch/motd" - val request = Request.Builder() - .url(url) - .get() - .build() - - client.newCall(request).execute().use { response -> + client.get(url).use { response -> if (!response.isSuccessful) error("Request failed: ${response.code}") - return response.body!!.charStream().decodeJson() + return response.body.charStream().decodeJson() } } fun getSettingsList(branch: String = HARD_CODED_BRANCH): List { val url = "$API_V1_ENDPOINT/client/$branch/settings" - val request = Request.Builder() - .url(url) - .get() - .build() - - client.newCall(request).execute().use { response -> + client.get(url).use { response -> if (!response.isSuccessful) error("Request failed: ${response.code}") - return response.body!!.charStream().decodeJson() + return response.body.charStream().decodeJson() } } fun getSettingsScript(branch: String = HARD_CODED_BRANCH, settingId: String): String { val url = "$API_V1_ENDPOINT/client/$branch/settings/$settingId" - val request = Request.Builder() - .url(url) - .get() - .build() - - client.newCall(request).execute().use { response -> + client.get(url).use { response -> if (!response.isSuccessful) error("Request failed: ${response.code}") - return response.body!!.string() + return response.body.string() } } - // TODO: backend not implemented yet + @Deprecated("Removed API") fun reportSettings(branch: String = HARD_CODED_BRANCH, settingId: String): ReportResponse { val url = "$API_V1_ENDPOINT/client/$branch/settings/report/$settingId" - val request = Request.Builder() - .url(url) - .get() - .build() - - client.newCall(request).execute().use { response -> + client.get(url).use { response -> if (!response.isSuccessful) error("Request failed: ${response.code}") - return response.body!!.charStream().decodeJson() + return response.body.charStream().decodeJson() } } - // TODO: backend not implemented yet + @Deprecated("Removed API") fun uploadSettings( branch: String = HARD_CODED_BRANCH, name: RequestBody, @@ -128,14 +105,9 @@ object ClientApi { .addPart(settingsFile) .build() - val request = Request.Builder() - .url(url) - .post(requestBody) - .build() - - client.newCall(request).execute().use { response -> + client.post(url, requestBody).use { response -> if (!response.isSuccessful) error("Request failed: ${response.code}") - return response.body!!.charStream().decodeJson() + return response.body.charStream().decodeJson() } } } diff --git a/src/main/java/net/ccbluex/liquidbounce/cape/CapeService.kt b/src/main/java/net/ccbluex/liquidbounce/cape/CapeService.kt index 057030586aa..d57b9325624 100644 --- a/src/main/java/net/ccbluex/liquidbounce/cape/CapeService.kt +++ b/src/main/java/net/ccbluex/liquidbounce/cape/CapeService.kt @@ -11,15 +11,13 @@ import net.ccbluex.liquidbounce.event.SessionUpdateEvent import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER import net.ccbluex.liquidbounce.utils.client.MinecraftInstance -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.* import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.login.UserUtils -import net.ccbluex.liquidbounce.utils.io.HttpUtils.get -import net.ccbluex.liquidbounce.utils.io.decodeJson -import net.ccbluex.liquidbounce.utils.io.parseJson import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.internal.commonEmptyRequestBody import java.util.* import java.util.concurrent.atomic.AtomicLong @@ -71,8 +69,6 @@ object CapeService : Listenable, MinecraftInstance { private val lastUpdate = AtomicLong(0L) private var refreshJob: Job? = null - private val client = HttpUtils.httpClient - /** * Refresh cape carriers, capture from the API. * It will take a list of (uuid, cape_name) tuples. @@ -84,19 +80,20 @@ object CapeService : Listenable, MinecraftInstance { refreshJob = SharedScopes.IO.launch { runCatching { // Capture data from API and parse JSON - val (json, code) = get(CAPE_CARRIERS_URL) - if (code != 200) throw RuntimeException("Failed to get cape carriers. Status code: $code") - - // Should be a JSON Array. It will fail if not. - // Format: [["8f617b6a-bea0-4af5-8e4b-d026d8fa9de8", "marco"], ...] - capeCarriers = json.parseJson().asJsonArray.associate { objInArray -> - // Should be a JSON Array. It will fail if not. - val arrayInArray = objInArray.asJsonArray - // 1. is UUID 2. is name of cape - val uuid = arrayInArray[0].asString - val name = arrayInArray[1].asString - - UUID.fromString(uuid) to name + HttpClient.get(CAPE_CARRIERS_URL).use { + if (!it.isSuccessful) { + throw RuntimeException("Failed to get cape carriers. Status code: ${it.code}") + } + + it.body.charStream().readJson().asJsonArray.associate { objInArray -> + // Should be a JSON Array. It will fail if not. + val arrayInArray = objInArray.asJsonArray + // 1. is UUID 2. is name of cape + val uuid = arrayInArray[0].asString + val name = arrayInArray[1].asString + + UUID.fromString(uuid) to name + } } lastUpdate.set(currentTime) @@ -137,10 +134,13 @@ object CapeService : Listenable, MinecraftInstance { .addHeader("Authorization", token) .build() - client.newCall(request).execute().use { response -> + HttpClient.newCall(request).execute().use { response -> if (response.isSuccessful) { - val json = response.body?.charStream()?.decodeJson() - ?: throw RuntimeException("Failed to decode JSON of self cape. Response: ${response.body?.string()}") + val json = try { + response.body.charStream().decodeJson() + } catch (e: Exception) { + throw RuntimeException("Failed to decode JSON of self cape. Response: ${response.body.string()}", e) + } clientCapeUser = CapeSelfUser(token, json.enabled, json.uuid, json.cape) LOGGER.info("Logged in successfully. Cape: ${json.cape}") @@ -167,14 +167,14 @@ object CapeService : Listenable, MinecraftInstance { .url(SELF_CAPE_URL) .apply { if (capeUser.enabled) delete() - else put("".toRequestBody(null)) + else put(commonEmptyRequestBody) } .addHeader("Content-Type", "application/json") .addHeader("Authorization", capeUser.token) .build() try { - val statusCode = client.newCall(request).execute().use { response -> + val statusCode = HttpClient.newCall(request).execute().use { response -> response.code } @@ -216,7 +216,7 @@ object CapeService : Listenable, MinecraftInstance { .addHeader("Authorization", capeUser.token) .build() - client.newCall(request).execute().use { response -> + HttpClient.newCall(request).execute().use { response -> if (response.code == 204) { // HTTP 204 No Content capeUser.uuid = uuid LOGGER.info("[Donator Cape] Successfully transferred cape to $uuid ($username)") diff --git a/src/main/java/net/ccbluex/liquidbounce/config/SettingsUtils.kt b/src/main/java/net/ccbluex/liquidbounce/config/SettingsUtils.kt index ddf703464c4..764219e56e6 100644 --- a/src/main/java/net/ccbluex/liquidbounce/config/SettingsUtils.kt +++ b/src/main/java/net/ccbluex/liquidbounce/config/SettingsUtils.kt @@ -5,14 +5,14 @@ */ package net.ccbluex.liquidbounce.config -import kotlinx.coroutines.runBlocking import net.ccbluex.liquidbounce.LiquidBounce.moduleManager import net.ccbluex.liquidbounce.api.ClientApi import net.ccbluex.liquidbounce.features.module.Module import net.ccbluex.liquidbounce.file.FileManager import net.ccbluex.liquidbounce.utils.attack.EntityUtils.Targets import net.ccbluex.liquidbounce.utils.client.chat -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.get import net.ccbluex.liquidbounce.utils.kotlin.StringUtils import net.ccbluex.liquidbounce.utils.render.ColorUtils.translateAlternateColorCodes import org.lwjgl.input.Keyboard @@ -23,6 +23,18 @@ import kotlin.reflect.KMutableProperty0 */ object SettingsUtils { + fun loadFromUrl(url: String) = if (url.startsWith("http")) { + HttpClient.get(url).use { + if (!it.isSuccessful) { + error(it.message) + } + + it.body.string() + } + } else { + ClientApi.getSettingsScript(settingId = url) + } + /** * Execute settings script. * @param script The script to apply. @@ -64,21 +76,7 @@ object SettingsUtils { "load" -> { val url = StringUtils.toCompleteString(args, 1) runCatching { - val settings = if (url.startsWith("http")) { - val (text, code) = HttpUtils.get(url) - - if (code != 200) { - error(text) - } - - text - } else { - runBlocking { - ClientApi.getSettingsScript(settingId = url) - } - } - - applyScript(settings) + applyScript(loadFromUrl(url)) }.onSuccess { chat("§7[§3§lAutoSettings§7] §7Loaded settings §a§l$url§7.") }.onFailure { diff --git a/src/main/java/net/ccbluex/liquidbounce/features/command/commands/SettingsCommand.kt b/src/main/java/net/ccbluex/liquidbounce/features/command/commands/SettingsCommand.kt index 20a2ad9ddd7..46f465da9fb 100644 --- a/src/main/java/net/ccbluex/liquidbounce/features/command/commands/SettingsCommand.kt +++ b/src/main/java/net/ccbluex/liquidbounce/features/command/commands/SettingsCommand.kt @@ -19,7 +19,6 @@ import net.ccbluex.liquidbounce.features.command.Command import net.ccbluex.liquidbounce.ui.client.hud.HUD.addNotification import net.ccbluex.liquidbounce.ui.client.hud.element.elements.Notification import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER -import net.ccbluex.liquidbounce.utils.io.HttpUtils.get import net.ccbluex.liquidbounce.utils.io.MiscUtils import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.kotlin.StringUtils @@ -68,16 +67,7 @@ object SettingsCommand : Command("autosettings", "autosetting", "settings", "set } try { - val settings = if (args[2].startsWith("http")) { - val (text, code) = get(args[2]) - if (code != 200) { - error(text) - } - - text - } else { - runBlocking { ClientApi.getSettingsScript(settingId = args[2]) } - } + val settings = SettingsUtils.loadFromUrl(args[2]) chat("Applying settings...") SettingsUtils.applyScript(settings) diff --git a/src/main/java/net/ccbluex/liquidbounce/features/module/modules/misc/StaffDetector.kt b/src/main/java/net/ccbluex/liquidbounce/features/module/modules/misc/StaffDetector.kt index 12c163a0456..7fd80178c36 100644 --- a/src/main/java/net/ccbluex/liquidbounce/features/module/modules/misc/StaffDetector.kt +++ b/src/main/java/net/ccbluex/liquidbounce/features/module/modules/misc/StaffDetector.kt @@ -16,7 +16,8 @@ import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.Module import net.ccbluex.liquidbounce.ui.client.hud.element.elements.Notification import net.ccbluex.liquidbounce.utils.client.chat -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.get import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.minecraft.entity.Entity import net.minecraft.entity.player.EntityPlayer @@ -51,15 +52,15 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa private val inGame by boolean("InGame", true) { autoLeave != "Off" } private val warn by choices("Warn", arrayOf("Chat", "Notification"), "Chat") - private val checkedStaff = ConcurrentHashMap.newKeySet() - private val checkedSpectator = ConcurrentHashMap.newKeySet() - private val playersInSpectatorMode = ConcurrentHashMap.newKeySet() + private val checkedStaff: MutableSet = ConcurrentHashMap.newKeySet() + private val checkedSpectator: MutableSet = ConcurrentHashMap.newKeySet() + private val playersInSpectatorMode: MutableSet = ConcurrentHashMap.newKeySet() private var attemptLeave = false private var alertClearVanish = false - private var staffList: Map?> = emptyMap() + private val staffList = ConcurrentHashMap>() private var serverIp = "" private var moduleJob: Job? = null @@ -74,6 +75,9 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa alertClearVanish = false } + private fun isStaff(player: String): Boolean = + staffList.values.any { staffNames -> staffNames.any { player.contains(it) } } + /** * Reset on World Change */ @@ -105,7 +109,7 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa serverIp = serverIpMap[staffMode.lowercase()] ?: return moduleJob = SharedScopes.IO.launch { - staffList = loadStaffList("$CLIENT_CLOUD/staffs/$serverIp") + loadStaffList("$CLIENT_CLOUD/staffs/$serverIp") } } @@ -133,24 +137,24 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa if (teamName.equals("Z_Spectator", true)) { val players = packet.players ?: return@handler - val staffSpectateList = players.filter { it in staffList.keys } - checkedSpectator - val nonStaffSpectateList = players.filter { it !in staffList.keys } - checkedSpectator + val staffSpectateList = players.filter { it !in checkedSpectator && isStaff(it) } + val nonStaffSpectateList = players.filter { it !in checkedSpectator && !isStaff(it) } // Check for players who are using spectator menu val miscSpectatorList = playersInSpectatorMode - players.toSet() staffSpectateList.forEach { player -> - notifySpectators(player!!) + notifySpectators(player) } nonStaffSpectateList.forEach { player -> if (otherSpectator) { - notifySpectators(player!!) + notifySpectators(player) } } miscSpectatorList.forEach { player -> - val isStaff = player in staffList + val isStaff = isStaff(player) if (isStaff && spectator) { chat("§c[STAFF] §d${player} §3is using the spectator menu §e(compass/left)") @@ -199,9 +203,7 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa return } - val isStaff = staffList.any { entry -> - entry.value?.any { staffName -> player.contains(staffName) } == true - } + val isStaff = isStaff(player) if (isStaff && spectator) { if (warn == "Chat") { @@ -249,9 +251,7 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa } playerInfos.forEach { (player, responseTime) -> - val isStaff = staffList.any { entry -> - entry.value?.any { staffName -> player.contains(staffName) } == true - } + val isStaff = isStaff(player) val condition = when { responseTime > 0 -> "§e(${responseTime}ms)" @@ -288,11 +288,7 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa } val isStaff = if (staff is EntityPlayer) { - val playerName = staff.gameProfile.name - - staffList.any { entry -> - entry.value?.any { staffName -> playerName.contains(staffName) } == true - } + isStaff(staff.gameProfile.name) } else { false } @@ -409,35 +405,31 @@ object StaffDetector : Module("StaffDetector", Category.MISC, gameDetecting = fa notifyStaffPacket(staff) } - private fun loadStaffList(url: String): Map> { - return try { - val (response, code) = HttpUtils.requestStream(url) - - when (code) { - 200 -> { - val staffList = response.bufferedReader().lineSequence() - .filter { it.isNotBlank() } - .map { it.trim() } - .toSet() - - chat("§aSuccessfully loaded §9${staffList.size} §astaff names.") - mapOf(url to staffList) - } + private fun loadStaffList(url: String) { + try { + HttpClient.get(url).use { response -> + when (val code = response.code) { + 200 -> { + val staffs = response.body.charStream().buffered().lineSequence() + .mapNotNullTo(hashSetOf()) { line -> + line.trim().takeIf { it.isNotBlank() } + } + + chat("§aSuccessfully loaded §9${staffs.size} §astaff names.") + staffList[url] = staffs + } - 404 -> { - chat("§cFailed to load staff list. §9(§3Doesn't exist in LiquidCloud§9)") - emptyMap() - } + 404 -> { + chat("§cFailed to load staff list. §9(§3Doesn't exist in LiquidCloud§9)") + } - else -> { - chat("§cFailed to load staff list. §9(§3ERROR CODE: $code§9)") - emptyMap() + else -> { + chat("§cFailed to load staff list. §9(§3ERROR CODE: $code§9)") + } } } } catch (e: Exception) { chat("§cFailed to load staff list. §9(${e.message})") - e.printStackTrace() - emptyMap() } } diff --git a/src/main/java/net/ccbluex/liquidbounce/features/special/ClientRichPresence.kt b/src/main/java/net/ccbluex/liquidbounce/features/special/ClientRichPresence.kt index ce58bbd0893..9061e5a7cf4 100644 --- a/src/main/java/net/ccbluex/liquidbounce/features/special/ClientRichPresence.kt +++ b/src/main/java/net/ccbluex/liquidbounce/features/special/ClientRichPresence.kt @@ -24,7 +24,9 @@ import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER import net.ccbluex.liquidbounce.utils.client.MinecraftInstance import net.ccbluex.liquidbounce.utils.client.ServerUtils -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.get +import net.ccbluex.liquidbounce.utils.io.jsonBody import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import org.json.JSONObject import java.io.IOException @@ -158,7 +160,7 @@ object ClientRichPresence : Configurable("DiscordRPC"), MinecraftInstance, Liste * @throws IOException If reading failed */ private fun loadConfiguration() { - val discordConf = HttpUtils.getJson("$CLIENT_CLOUD/discord.json") ?: return + val discordConf = HttpClient.get("$CLIENT_CLOUD/discord.json").jsonBody() ?: return // Check has app id discordConf.appID?.let { appID = it } diff --git a/src/main/java/net/ccbluex/liquidbounce/script/remapper/Remapper.kt b/src/main/java/net/ccbluex/liquidbounce/script/remapper/Remapper.kt index c08fda17013..9e65eb16912 100644 --- a/src/main/java/net/ccbluex/liquidbounce/script/remapper/Remapper.kt +++ b/src/main/java/net/ccbluex/liquidbounce/script/remapper/Remapper.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.runBlocking import net.ccbluex.liquidbounce.LiquidBounce.CLIENT_CLOUD import net.ccbluex.liquidbounce.file.FileManager.dir import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER -import net.ccbluex.liquidbounce.utils.io.HttpUtils.Downloader +import net.ccbluex.liquidbounce.utils.io.Downloader import net.ccbluex.liquidbounce.utils.io.isEmpty import net.ccbluex.liquidbounce.utils.io.sha256 import java.io.File @@ -21,10 +21,11 @@ import java.io.File */ object Remapper { - private const val srgName = "stable_22" - private val srgFile = File(dir, "mcp-$srgName.srg") + private const val SRG_NAME = "stable_22" + private val srgFile = File(dir, "mcp-$SRG_NAME.srg") - internal var mappingsLoaded = false + var mappingsLoaded = false + private set private val fields = hashMapOf>() private val methods = hashMapOf>() @@ -41,12 +42,12 @@ object Remapper { mappingsLoaded = false // Download sha256 file - val sha256File = File(dir, "mcp-$srgName.srg.sha256") + val sha256File = File(dir, "mcp-$SRG_NAME.srg.sha256") if (!sha256File.exists() || !sha256File.isFile || sha256File.isEmpty) { sha256File.createNewFile() - Downloader.downloadWholeFile("$CLIENT_CLOUD/srgs/mcp-$srgName.srg.sha256", sha256File) - LOGGER.info("[Remapper] Downloaded $srgName sha256.") + Downloader.downloadWholeFile("$CLIENT_CLOUD/srgs/mcp-$SRG_NAME.srg.sha256", sha256File) + LOGGER.info("[Remapper] Downloaded $SRG_NAME sha256.") } // Check if srg file is already downloaded @@ -55,9 +56,9 @@ object Remapper { srgFile.createNewFile() runBlocking { - Downloader.download("$CLIENT_CLOUD/srgs/mcp-$srgName.srg", srgFile) + Downloader.download("$CLIENT_CLOUD/srgs/mcp-$SRG_NAME.srg", srgFile) } - LOGGER.info("[Remapper] Downloaded $srgName.") + LOGGER.info("[Remapper] Downloaded $SRG_NAME.") } // Load srg diff --git a/src/main/java/net/ccbluex/liquidbounce/tabs/HeadsTab.kt b/src/main/java/net/ccbluex/liquidbounce/tabs/HeadsTab.kt index 56e906c72fc..beb8c946c1e 100644 --- a/src/main/java/net/ccbluex/liquidbounce/tabs/HeadsTab.kt +++ b/src/main/java/net/ccbluex/liquidbounce/tabs/HeadsTab.kt @@ -10,7 +10,9 @@ import net.ccbluex.liquidbounce.LiquidBounce.CLIENT_CLOUD import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.inventory.ItemUtils -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.get +import net.ccbluex.liquidbounce.utils.io.jsonBody import net.minecraft.creativetab.CreativeTabs import net.minecraft.init.Items import net.minecraft.item.Item @@ -36,14 +38,14 @@ class HeadsTab : CreativeTabs("Heads") { LOGGER.info("Loading heads...") // Asynchronously fetch the heads configuration - val headsConf = HttpUtils.getJson("$CLIENT_CLOUD/heads.json") ?: return + val headsConf = HttpClient.get("$CLIENT_CLOUD/heads.json").jsonBody() ?: return if (headsConf.enabled) { val url = headsConf.url LOGGER.info("Loading heads from $url...") - val headsMap = HttpUtils.getJson>(url) ?: return + val headsMap = HttpClient.get(url).jsonBody>() ?: return heads = headsMap.values.map { head -> ItemUtils.createItem("skull 1 3 {display:{Name:\"${head.name}\"},SkullOwner:{Id:\"${head.uuid}\",Properties:{textures:[{Value:\"${head.value}\"}]}}}")!! diff --git a/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiContributors.kt b/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiContributors.kt index 8756756ad29..cd029937afc 100644 --- a/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiContributors.kt +++ b/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiContributors.kt @@ -12,7 +12,9 @@ import net.ccbluex.liquidbounce.lang.translationMenu import net.ccbluex.liquidbounce.ui.font.AWTFontRenderer.Companion.assumeNonVolatile import net.ccbluex.liquidbounce.ui.font.Fonts import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.get +import net.ccbluex.liquidbounce.utils.io.jsonBody import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.render.CustomTexture import net.ccbluex.liquidbounce.utils.render.RenderUtils.drawLoadingCircle @@ -22,7 +24,6 @@ import net.minecraft.client.gui.GuiButton import net.minecraft.client.gui.GuiScreen import net.minecraft.client.gui.GuiSlot import net.minecraft.client.renderer.GlStateManager.* -import okhttp3.Request import org.lwjgl.input.Keyboard import org.lwjgl.opengl.GL11.* import java.awt.Color @@ -174,17 +175,17 @@ class GuiContributors(private val prevGui: GuiScreen) : AbstractScreen() { private fun loadCredits() { try { - val gitHubContributors = HttpUtils.getJson>( + val gitHubContributors = HttpClient.get( "https://api.github.com/repos/CCBlueX/LiquidBounce/stats/contributors" - ) ?: run { + ).jsonBody>() ?: run { failed = true return } // Note: this API is not available in China - val additionalInformation = HttpUtils.getJson>( + val additionalInformation = HttpClient.get( "https://raw.githubusercontent.com/CCBlueX/LiquidCloud/master/LiquidBounce/contributors.json" - ) ?: emptyMap() + ).jsonBody>() ?: emptyMap() val credits = ArrayList(gitHubContributors.size) @@ -291,8 +292,8 @@ private class Credit( ) { val avatar by lazy { runCatching { - HttpUtils.httpClient.newCall(Request.Builder().url(avatarUrl).build()).execute().use { response -> - response.body!!.byteStream().use(ImageIO::read).let(::CustomTexture) + HttpClient.get(avatarUrl).use { response -> + response.body.byteStream().use(ImageIO::read).let(::CustomTexture) } }.onFailure { LOGGER.error("Failed to load avatar.", it) diff --git a/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiServerStatus.kt b/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiServerStatus.kt index fb282435f24..02ad0e61852 100644 --- a/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiServerStatus.kt +++ b/src/main/java/net/ccbluex/liquidbounce/ui/client/GuiServerStatus.kt @@ -9,12 +9,15 @@ import kotlinx.coroutines.launch import net.ccbluex.liquidbounce.lang.translationMenu import net.ccbluex.liquidbounce.ui.font.AWTFontRenderer.Companion.assumeNonVolatile import net.ccbluex.liquidbounce.ui.font.Fonts -import net.ccbluex.liquidbounce.utils.io.HttpUtils.responseCode +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.defaultAgent +import net.ccbluex.liquidbounce.utils.io.newCall import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.render.RenderUtils.drawRect import net.ccbluex.liquidbounce.utils.ui.AbstractScreen import net.minecraft.client.gui.GuiButton import net.minecraft.client.gui.GuiScreen +import okhttp3.Request import org.lwjgl.input.Keyboard import java.awt.Color import java.io.IOException @@ -87,8 +90,11 @@ class GuiServerStatus(private val prevGui: GuiScreen) : AbstractScreen() { status[url] = null SharedScopes.IO.launch { try { - val responseCode = responseCode(url, "GET") - status[url] = if (responseCode in 200..499) "green" else "red" + status[url] = HttpClient.newCall(fun Request.Builder.() { + url(url).head().defaultAgent() + }).execute().use { + if (it.code in 200..499) "green" else "red" + } } catch (e: IOException) { status[url] = "red" } diff --git a/src/main/java/net/ccbluex/liquidbounce/ui/client/altmanager/GuiAltManager.kt b/src/main/java/net/ccbluex/liquidbounce/ui/client/altmanager/GuiAltManager.kt index 2c420708624..c406e937910 100644 --- a/src/main/java/net/ccbluex/liquidbounce/ui/client/altmanager/GuiAltManager.kt +++ b/src/main/java/net/ccbluex/liquidbounce/ui/client/altmanager/GuiAltManager.kt @@ -27,9 +27,7 @@ import net.ccbluex.liquidbounce.ui.font.AWTFontRenderer.Companion.assumeNonVolat import net.ccbluex.liquidbounce.ui.font.Fonts import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER import net.ccbluex.liquidbounce.utils.client.MinecraftInstance.Companion.mc -import net.ccbluex.liquidbounce.utils.io.FileFilters -import net.ccbluex.liquidbounce.utils.io.HttpUtils -import net.ccbluex.liquidbounce.utils.io.MiscUtils +import net.ccbluex.liquidbounce.utils.io.* import net.ccbluex.liquidbounce.utils.kotlin.RandomUtils.randomAccount import net.ccbluex.liquidbounce.utils.kotlin.SharedScopes import net.ccbluex.liquidbounce.utils.kotlin.swap @@ -468,7 +466,7 @@ class GuiAltManager(private val prevGui: GuiScreen) : AbstractScreen() { fun loadActiveGenerators() { try { // Read versions json from cloud - activeGenerators += HttpUtils.getJson>("$CLIENT_CLOUD/generators.json")!! + activeGenerators += HttpClient.get("$CLIENT_CLOUD/generators.json").jsonBody>()!! } catch (throwable: Throwable) { // Print throwable to console LOGGER.error("Failed to load enabled generators.", throwable) diff --git a/src/main/java/net/ccbluex/liquidbounce/ui/font/Fonts.kt b/src/main/java/net/ccbluex/liquidbounce/ui/font/Fonts.kt index 2ca938bd168..ea1d41a27aa 100644 --- a/src/main/java/net/ccbluex/liquidbounce/ui/font/Fonts.kt +++ b/src/main/java/net/ccbluex/liquidbounce/ui/font/Fonts.kt @@ -11,7 +11,7 @@ import net.ccbluex.liquidbounce.file.FileManager.fontsDir import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER import net.ccbluex.liquidbounce.utils.client.MinecraftInstance import net.ccbluex.liquidbounce.utils.io.* -import net.ccbluex.liquidbounce.utils.io.HttpUtils.Downloader +import net.ccbluex.liquidbounce.utils.io.Downloader import net.minecraft.client.gui.FontRenderer import java.awt.Font import java.io.File @@ -154,6 +154,7 @@ object Fonts : MinecraftInstance { } fun downloadFonts() { + fontsDir.mkdirs() val outputFile = File(fontsDir, "outfit.zip") if (!outputFile.exists()) { LOGGER.info("Downloading fonts...") diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/io/HttpUtils.kt b/src/main/java/net/ccbluex/liquidbounce/utils/io/HttpUtils.kt index 4aa059ee43f..b26dc6aa3be 100644 --- a/src/main/java/net/ccbluex/liquidbounce/utils/io/HttpUtils.kt +++ b/src/main/java/net/ccbluex/liquidbounce/utils/io/HttpUtils.kt @@ -12,13 +12,13 @@ import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import net.ccbluex.liquidbounce.utils.client.ClientUtils -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody +import okhttp3.* +import okhttp3.internal.commonEmptyRequestBody +import okio.buffer +import okio.sink +import okio.source import java.io.File import java.io.IOException -import java.io.InputStream -import java.io.RandomAccessFile import java.security.SecureRandom import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit @@ -26,228 +26,179 @@ import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager +private const val DEFAULT_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" + /** - * HttpUtils based on OkHttp3 - * - * @author MukjepScarlet + * Global [OkHttpClient] */ -object HttpUtils { - - const val DEFAULT_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" - - val httpClient: OkHttpClient = OkHttpClient.Builder() - .connectTimeout(3, TimeUnit.SECONDS) - .readTimeout(15, TimeUnit.SECONDS) - .followRedirects(true) - .applyBypassHttps() - .build() - - /** - * For legacy Java 8 versions like 8u51 - */ - @JvmStatic - fun OkHttpClient.Builder.applyBypassHttps() = this - .sslSocketFactory(createTrustAllSslSocketFactory(), createTrustAllTrustManager()) - .hostnameVerifier { _, _ -> true } - - @JvmStatic - private fun createTrustAllSslSocketFactory(): SSLSocketFactory { - val trustAllCerts = arrayOf(createTrustAllTrustManager()) - val sslContext = SSLContext.getInstance("TLS") - sslContext.init(null, trustAllCerts, SecureRandom()) - return sslContext.socketFactory - } - - @JvmStatic - private fun createTrustAllTrustManager(): X509TrustManager { - return object : X509TrustManager { - override fun checkClientTrusted(chain: Array, authType: String) {} - override fun checkServerTrusted(chain: Array, authType: String) {} - override fun getAcceptedIssuers(): Array = arrayOf() - } - } - - private fun makeRequest( - url: String, - method: String, - agent: String = DEFAULT_AGENT, - headers: Array> = emptyArray(), - body: RequestBody? = null - ): Request { - val builder = Request.Builder() - .url(url) - .method(method, body) - .header("User-Agent", agent) - - for ((key, value) in headers) { - builder.addHeader(key, value) - } +val HttpClient: OkHttpClient = OkHttpClient.Builder() + .connectTimeout(3, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .followRedirects(true) + .followSslRedirects(true) + .applyBypassHttps() + .build() + +// Requests + +fun OkHttpClient.get(url: String) = newCall { + url(url).defaultAgent().get() +}.execute() + +fun OkHttpClient.head(url: String) = newCall { + url(url).defaultAgent().head() +}.execute() + +fun OkHttpClient.post(url: String, body: RequestBody = commonEmptyRequestBody) = newCall { + url(url).defaultAgent().post(body) +}.execute() + +fun OkHttpClient.delete(url: String, body: RequestBody? = commonEmptyRequestBody) = newCall { + url(url).defaultAgent().delete(body) +}.execute() + +fun OkHttpClient.put(url: String, body: RequestBody = commonEmptyRequestBody) = newCall { + url(url).defaultAgent().put(body) +}.execute() + +fun OkHttpClient.patch(url: String, body: RequestBody = commonEmptyRequestBody) = newCall { + url(url).defaultAgent().patch(body) +}.execute() + +fun OkHttpClient.request(url: String, method: String, body: RequestBody? = null) = newCall { + url(url).defaultAgent().method(method, body) +}.execute() + +// General + +inline fun OkHttpClient.newCall(requestBlock: Request.Builder.() -> Unit): Call = + this.newCall(Request.Builder().apply(requestBlock).build()) + +fun Request.Builder.defaultAgent() = this.header("User-Agent", DEFAULT_AGENT) + +inline fun Response.jsonBody(): T? = use { + runCatching { + this.body.charStream().decodeJson() + }.onFailure { + ClientUtils.LOGGER.error("[HTTP] Failed to parse JSON body (${T::class.java.simpleName})", it) + }.getOrNull() +} - return builder.build() +fun Response.toFile(file: File) = use { response -> + if (response.isSuccessful) { + file.sink().buffer().use(response.body.source()::readAll) + } else { + throw IOException("[HTTP] Failed to write Response to File $file, ${response.message}") } +} - fun requestStream( - url: String, - method: String = "GET", - agent: String = DEFAULT_AGENT, - headers: Array> = emptyArray(), - body: RequestBody? = null - ): Pair { - val request = makeRequest(url, method, agent, headers, body) - val response = httpClient.newCall(request).execute() - - if (!response.isSuccessful) { - throw IOException("Unexpected code ${response.code}") - } - - return response.body.byteStream() to response.code +private fun createTrustAllTrustManager(): X509TrustManager { + return object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) {} + override fun checkServerTrusted(chain: Array, authType: String) {} + override fun getAcceptedIssuers(): Array = arrayOf() } +} - private fun request( - url: String, - method: String, - agent: String = DEFAULT_AGENT, - headers: Array> = emptyArray(), - body: RequestBody? = null - ): Pair { - val request = makeRequest(url, method, agent, headers, body) - httpClient.newCall(request).execute().use { response -> - val responseBody = response.body.string() - return responseBody to response.code - } - } +private fun createTrustAllSslSocketFactory(): SSLSocketFactory { + val trustAllCerts = arrayOf(createTrustAllTrustManager()) + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, trustAllCerts, SecureRandom()) + return sslContext.socketFactory +} - fun get(url: String, agent: String = DEFAULT_AGENT, headers: Array> = emptyArray()): Pair { - return request(url, "GET", agent, headers) - } +/** + * For legacy Java 8 versions like 8u51 + */ +fun OkHttpClient.Builder.applyBypassHttps() = this + .sslSocketFactory(createTrustAllSslSocketFactory(), createTrustAllTrustManager()) + .hostnameVerifier { _, _ -> true } - inline fun getJson(url: String): T? { - return runCatching { - httpClient.newCall(Request.Builder().url(url).build()).execute().use { - it.body.charStream().decodeJson() - } - }.onFailure { - ClientUtils.LOGGER.error("[HTTP] Failed to GET JSON from $url", it) - }.getOrNull() - } +object Downloader { - fun post( + suspend fun download( url: String, - agent: String = DEFAULT_AGENT, - headers: Array> = emptyArray(), - body: RequestBody - ): Pair { - return request(url, "POST", agent, headers, body) - } - - fun responseCode(url: String, method: String, agent: String = DEFAULT_AGENT): Int { - val request = makeRequest(url, method, agent) - httpClient.newCall(request).execute().use { response -> - return response.code + targetFile: File, + parallelism: Int = 4, + chunkSize: Long = 2 * 1024 * 1024 + ) = withContext(Dispatchers.IO) { + require(parallelism > 0) + require(chunkSize >= 1024) + + if (parallelism == 1) { + downloadWholeFile(url, targetFile) + return@withContext } - } - - object Downloader { - - suspend fun download( - url: String, - targetFile: File, - parallelism: Int = 4, - chunkSize: Long = 2 * 1024 * 1024 - ) = withContext(Dispatchers.IO) { - require(parallelism > 0) - require(chunkSize >= 1024) - - if (parallelism == 1) { - downloadWholeFile(url, targetFile) - return@withContext - } - val (fileSize, supportsRange) = getFileSizeAndRangeSupport(url) + val (fileSize, supportsRange) = getFileSizeAndRangeSupport(url) - if (fileSize <= 0 || !supportsRange) { - downloadWholeFile(url, targetFile) - return@withContext - } + if (fileSize <= 0 || !supportsRange) { + downloadWholeFile(url, targetFile) + return@withContext + } - val maxConcurrency = ((fileSize + chunkSize - 1) / chunkSize).toInt() + val maxConcurrency = ((fileSize + chunkSize - 1) / chunkSize).toInt() - val semaphore = Semaphore(parallelism) + val semaphore = Semaphore(parallelism) - ClientUtils.LOGGER.info("[HTTP] Starting ${minOf(parallelism, maxConcurrency)} tasks for downloading $url to $targetFile") + ClientUtils.LOGGER.info( + "[HTTP] Starting ${ + minOf( + parallelism, + maxConcurrency + ) + } tasks for downloading $url to $targetFile" + ) - val tempFiles = (0 until maxConcurrency).map { chunkIndex -> - async { - semaphore.withPermit { - val start = chunkIndex * chunkSize - val end = minOf((chunkIndex + 1) * chunkSize - 1, fileSize - 1) - val tempFile = File(targetFile.parent, "chunk_$chunkIndex.tmp") + val tempFiles = List(maxConcurrency) { chunkIndex -> + async { + semaphore.withPermit { + val start = chunkIndex * chunkSize + val end = minOf((chunkIndex + 1) * chunkSize - 1, fileSize - 1) + val tempFile = File(targetFile.parent, "chunk_$chunkIndex.tmp") - downloadChunk(url, start, end, tempFile) - tempFile - } + downloadChunk(url, start, end, tempFile) + tempFile } - }.awaitAll() - - mergeChunks(tempFiles, targetFile) - } + } + }.awaitAll() - private fun getFileSizeAndRangeSupport(url: String): Pair { - val request = Request.Builder() - .url(url) - .head() - .build() + mergeChunks(tempFiles, targetFile) + } - httpClient.newCall(request).execute().use { response -> - if (!response.isSuccessful) return Pair(-1, false) + private fun getFileSizeAndRangeSupport(url: String): Pair = + HttpClient.head(url).use { response -> + if (!response.isSuccessful) return Pair(-1, false) - val contentLength = response.header("Content-Length")?.toLongOrNull() ?: -1 - val acceptRanges = response.header("Accept-Ranges") - val supportsRange = acceptRanges == "bytes" + val contentLength = response.header("Content-Length")?.toLongOrNull() ?: -1 + val acceptRanges = response.header("Accept-Ranges") + val supportsRange = acceptRanges == "bytes" - return Pair(contentLength, supportsRange) - } + Pair(contentLength, supportsRange) } - fun downloadWholeFile(url: String, targetFile: File) { - val request = Request.Builder() - .url(url) - .build() - - httpClient.newCall(request).execute().use { response -> - if (!response.isSuccessful) throw IOException("Download failed: ${response.code}") - - targetFile.outputStream().use { output -> - response.body.byteStream().copyTo(output) - } - } - } + fun downloadWholeFile(url: String, targetFile: File) { + HttpClient.get(url).toFile(targetFile) + } - private fun downloadChunk(url: String, start: Long, end: Long, tempFile: File) { - val request = Request.Builder() - .url(url) - .addHeader("Range", "bytes=$start-$end") - .build() - - httpClient.newCall(request).execute().use { response -> - if (response.isSuccessful) { - tempFile.outputStream().use { it.write(response.body.bytes()) } - } else { - throw IOException("Failed to download chunk from $start to $end") - } - } + private fun downloadChunk(url: String, start: Long, end: Long, tempFile: File) { + try { + HttpClient.newCall { + url(url).addHeader("Range", "bytes=$start-$end") + }.execute().toFile(tempFile) + } catch (e: IOException) { + throw IOException("Failed to download chunk from $start to $end", e) } + } - private fun mergeChunks(tempFiles: List, targetFile: File) { - RandomAccessFile(targetFile, "rw").use { mergedFile -> - tempFiles.forEach { tempFile -> - tempFile.inputStream().use { input -> - mergedFile.channel.transferFrom(input.channel, mergedFile.length(), tempFile.length()) - } - tempFile.delete() - } + private fun mergeChunks(tempFiles: List, targetFile: File) { + targetFile.sink().buffer().use { mergedSink -> + for (tempFile in tempFiles) { + tempFile.source().buffer().use(mergedSink::writeAll) + tempFile.delete() } } } - } diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/login/UserUtils.kt b/src/main/java/net/ccbluex/liquidbounce/utils/login/UserUtils.kt index ac5eace6742..d30d7424bb5 100644 --- a/src/main/java/net/ccbluex/liquidbounce/utils/login/UserUtils.kt +++ b/src/main/java/net/ccbluex/liquidbounce/utils/login/UserUtils.kt @@ -5,7 +5,9 @@ */ package net.ccbluex.liquidbounce.utils.login -import net.ccbluex.liquidbounce.utils.io.HttpUtils +import net.ccbluex.liquidbounce.utils.io.HttpClient +import net.ccbluex.liquidbounce.utils.io.get +import net.ccbluex.liquidbounce.utils.io.jsonBody object UserUtils { @@ -24,9 +26,9 @@ object UserUtils { fun getUsername(uuid: String): String? { uuidCache[uuid]?.let { return it } - return HttpUtils.getJson( + return HttpClient.get( "https://api.minecraftservices.com/minecraft/profile/lookup/$uuid" - )?.name?.also { + ).jsonBody()?.name?.also { usernameCache[uuid] = it } } @@ -37,9 +39,9 @@ object UserUtils { fun getUUID(username: String): String? { usernameCache[username]?.let { return it } - return HttpUtils.getJson( + return HttpClient.get( "https://api.minecraftservices.com/minecraft/profile/lookup/name/$username" - )?.id?.also { + ).jsonBody()?.id?.also { usernameCache[username] = it } }