Skip to content

Commit 5877a74

Browse files
committed
Use ktor instead of fuel
1 parent 4588faa commit 5877a74

File tree

12 files changed

+136
-105
lines changed

12 files changed

+136
-105
lines changed

common/build.gradle.kts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ val modId: String by project
2121
val fabricLoaderVersion: String by project
2222
val kotlinxCoroutinesVersion: String by project
2323
val discordIPCVersion: String by project
24-
val fuelVersion: String by project
25-
val resultVersion: String by project
24+
val ktorVersion: String by project
2625
val mockitoKotlin: String by project
2726
val mockitoInline: String by project
2827
val mockkVersion: String by project
@@ -50,16 +49,19 @@ dependencies {
5049
implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion")
5150
implementation("com.pngencoder:pngencoder:0.15.0")
5251

53-
// Fuel HTTP library and dependencies
54-
implementation("com.github.kittinunf.fuel:fuel:$fuelVersion")
55-
implementation("com.github.kittinunf.fuel:fuel-gson:$fuelVersion")
56-
implementation("com.github.kittinunf.result:result-jvm:$resultVersion")
52+
// Ktor
53+
implementation("io.ktor:ktor-client-core:$ktorVersion")
54+
implementation("io.ktor:ktor-client-cio:$ktorVersion")
55+
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
56+
implementation("io.ktor:ktor-serialization-gson:$ktorVersion")
5757

5858
// Add Kotlin
5959
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
6060

6161
// Baritone
6262
modImplementation("baritone-api:baritone-unoptimized-fabric:1.10.2") { isTransitive = false }
63+
64+
// Test implementations
6365
testImplementation(kotlin("test"))
6466
testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlin")
6567
testImplementation("org.mockito:mockito-inline:$mockitoInline")

common/src/main/kotlin/com/lambda/module/modules/client/Discord.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,9 @@ object Discord : Module(
7979

8080
val auth = rpc.applicationManager.authenticate()
8181

82-
linkDiscord(discordToken = auth.accessToken,
83-
success = { updateToken(it); discordAuth = auth },
84-
failure = { warn("Failed to link the discord account to the minecraft auth") }
85-
)
82+
linkDiscord(discordToken = auth.accessToken)
83+
.fold(onSuccess = { updateToken(it); discordAuth = auth },
84+
onFailure = { warn("Failed to link the discord account to the minecraft auth") })
8685
}
8786

8887
private fun stop() {

common/src/main/kotlin/com/lambda/module/modules/client/Network.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,18 @@ object Network : Module(
6767
hash = BigInteger(computed).toString(16)
6868
}
6969

70-
listenUnsafe<ConnectionEvent.Connect.Post> {
70+
listenUnsafeConcurrently<ConnectionEvent.Connect.Post> {
7171
// FixMe: If the player have the properties but are invalid this doesn't work
72-
if (NetworkManager.isValid || mc.gameProfile.isOffline) return@listenUnsafe
72+
if (NetworkManager.isValid || mc.gameProfile.isOffline) return@listenUnsafeConcurrently
7373

7474
// If we log in right as the client responds to the encryption request, we start
7575
// a race condition where the game server haven't acknowledged the packets
7676
// and posted to the sessionserver api
77-
login(mc.session.username, hash ?: return@listenUnsafe,
78-
success = { updateToken(it) },
79-
failure = { LOG.warn("Unable to authenticate: $it") }
80-
)
77+
login(mc.session.username, hash ?: return@listenUnsafeConcurrently)
78+
.fold(
79+
onSuccess = { updateToken(it) },
80+
onFailure = { LOG.warn("Unable to authenticate: $it") }
81+
)
8182
}
8283
}
8384

common/src/main/kotlin/com/lambda/network/CapeManager.kt

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
package com.lambda.network
1919

20-
import com.github.kittinunf.fuel.Fuel
21-
import com.github.kittinunf.fuel.core.requests.CancellableRequest
2220
import com.lambda.Lambda.mc
2321
import com.lambda.context.SafeContext
2422
import com.lambda.core.Loadable
@@ -29,13 +27,13 @@ import com.lambda.network.api.v1.endpoints.getCape
2927
import com.lambda.network.api.v1.endpoints.setCape
3028
import com.lambda.network.api.v1.models.Cape
3129
import com.lambda.sound.SoundManager.toIdentifier
32-
import com.lambda.util.Communication.info
33-
import com.lambda.util.Communication.logError
30+
import com.lambda.threading.runIO
3431
import com.lambda.util.FolderRegister.capes
3532
import com.lambda.util.extension.get
3633
import com.lambda.util.extension.resolveFile
3734
import net.minecraft.client.texture.NativeImage.read
3835
import net.minecraft.client.texture.NativeImageBackedTexture
36+
import java.io.ByteArrayOutputStream
3937
import java.util.UUID
4038
import java.util.concurrent.ConcurrentHashMap
4139
import kotlin.io.path.ExperimentalPathApi
@@ -58,36 +56,35 @@ object CapeManager : ConcurrentHashMap<UUID, String>(), Loadable {
5856
/**
5957
* Sets the current player's cape
6058
*/
61-
fun SafeContext.updateCape(cape: String): CancellableRequest =
62-
setCape(cape,
63-
success = { fetchCape(player.uuid); info("Successfully update your cape to $cape") },
64-
failure = { logError("Could not update the player cape", it) }
65-
)
59+
fun updateCape(cape: String, block: () -> Unit = {}) = runIO {
60+
setCape(cape)
61+
}.invokeOnCompletion { block() }
6662

6763
/**
6864
* Fetches the cape of the given player id
6965
*/
70-
fun SafeContext.fetchCape(uuid: UUID): CancellableRequest =
71-
getCape(uuid,
72-
success = { mc.textureManager.get(it.identifier) ?: download(it); put(uuid, it.id) },
73-
failure = { logError("Could not fetch the cape of the player", it) }
74-
)
66+
fun SafeContext.fetchCape(uuid: UUID, block: () -> Unit = {}) = runIO {
67+
val cape = getCape(uuid).getOrThrow()
7568

76-
private fun SafeContext.download(cape: Cape): CancellableRequest =
77-
Fuel.download(cape.url)
78-
.fileDestination { _, _ -> capes.resolveFile("${cape.id}.png") }
79-
.response { result ->
80-
result.fold(
81-
success = {
82-
val image = TextureUtils.readImage(it)
83-
val native = NativeImageBackedTexture(image)
84-
val id = cape.identifier
69+
mc.textureManager.get(cape.identifier) ?: download(cape)
70+
put(uuid, cape.id)
71+
}.invokeOnCompletion { block() }
8572

86-
mc.textureManager.registerTexture(id, native)
87-
},
88-
failure = { logError("Could not download the cape", it) }
89-
)
90-
}
73+
private fun SafeContext.download(cape: Cape, block: () -> Unit = {}) = runIO {
74+
val destination = capes.resolveFile("${cape.id}.png")
75+
val output = ByteArrayOutputStream()
76+
77+
LambdaHttp.download(cape.url, output)
78+
79+
val bytes = output.toByteArray()
80+
destination.writeBytes(bytes)
81+
82+
val image = TextureUtils.readImage(bytes)
83+
val native = NativeImageBackedTexture(image)
84+
val id = cape.identifier
85+
86+
mc.textureManager.registerTexture(id, native)
87+
}.invokeOnCompletion { block() }
9188

9289
override fun load() = "Loaded ${images.size} cached capes"
9390

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.network
19+
20+
import com.lambda.Lambda
21+
import io.ktor.client.*
22+
import io.ktor.client.plugins.contentnegotiation.*
23+
import io.ktor.client.request.*
24+
import io.ktor.client.statement.*
25+
import io.ktor.http.*
26+
import io.ktor.serialization.gson.*
27+
import java.io.File
28+
import java.io.OutputStream
29+
30+
val LambdaHttp = HttpClient {
31+
install(ContentNegotiation) {
32+
// Use our gson instance
33+
register(ContentType.Application.Json, GsonConverter(Lambda.gson))
34+
}
35+
}
36+
37+
suspend inline fun HttpClient.download(url: String, file: File, block: HttpRequestBuilder.() -> Unit = {}) =
38+
file.writeBytes(get(url, block).readRawBytes())
39+
40+
suspend inline fun HttpClient.download(url: String, output: OutputStream, block: HttpRequestBuilder.() -> Unit = {}) =
41+
output.write(get(url, block).readRawBytes())
42+
43+
suspend inline fun HttpClient.download(url: String, block: HttpRequestBuilder.() -> Unit) =
44+
get(url, block).readRawBytes()

common/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,12 @@
1717

1818
package com.lambda.network.api.v1.endpoints
1919

20-
import com.github.kittinunf.fuel.Fuel
21-
import com.github.kittinunf.fuel.core.FuelError
22-
import com.github.kittinunf.fuel.core.ResultHandler
23-
import com.github.kittinunf.fuel.gson.responseObject
24-
import com.github.kittinunf.result.failure
25-
import com.github.kittinunf.result.success
2620
import com.lambda.module.modules.client.Network.apiUrl
2721
import com.lambda.module.modules.client.Network.apiVersion
22+
import com.lambda.network.LambdaHttp
2823
import com.lambda.network.api.v1.models.Cape
24+
import io.ktor.client.call.*
25+
import io.ktor.client.request.*
2926
import java.util.UUID
3027

3128
/**
@@ -36,6 +33,6 @@ import java.util.UUID
3633
*
3734
* response: [Cape] or error
3835
*/
39-
fun getCape(uuid: UUID, success: (Cape) -> Unit, failure: (FuelError) -> Unit) =
40-
Fuel.get("$apiUrl/api/${apiVersion.value}/cape?id=$uuid")
41-
.responseObject<Cape> { _, _, result -> result.fold(success, failure) }
36+
suspend fun getCape(uuid: UUID) = runCatching {
37+
LambdaHttp.get("$apiUrl/api/${apiVersion.value}/cape?id=$uuid").body<Cape>()
38+
}

common/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717

1818
package com.lambda.network.api.v1.endpoints
1919

20-
import com.github.kittinunf.fuel.Fuel
21-
import com.github.kittinunf.fuel.core.FuelError
22-
import com.github.kittinunf.fuel.core.extensions.authentication
23-
import com.github.kittinunf.fuel.core.extensions.jsonBody
24-
import com.github.kittinunf.fuel.gson.responseObject
2520
import com.lambda.module.modules.client.Network.apiUrl
2621
import com.lambda.module.modules.client.Network.apiVersion
22+
import com.lambda.network.LambdaHttp
2723
import com.lambda.network.NetworkManager
2824
import com.lambda.network.api.v1.models.Authentication
25+
import io.ktor.client.call.*
26+
import io.ktor.client.request.*
27+
import io.ktor.http.*
2928

3029
/**
3130
* Links a Discord account to a session account
@@ -35,9 +34,10 @@ import com.lambda.network.api.v1.models.Authentication
3534
*
3635
* response: [Authentication] or error
3736
*/
38-
fun linkDiscord(discordToken: String, success: (Authentication) -> Unit, failure: (FuelError) -> Unit) =
39-
Fuel.post("${apiUrl}/api/${apiVersion.value}/link/discord")
40-
.jsonBody("""{ "token": "$discordToken" }""")
41-
.authentication()
42-
.bearer(NetworkManager.accessToken)
43-
.responseObject<Authentication> { _, _, result -> result.fold(success, failure) }
37+
suspend fun linkDiscord(discordToken: String) = runCatching {
38+
LambdaHttp.post("${apiUrl}/api/${apiVersion.value}/link/discord") {
39+
setBody("""{ "token": "$discordToken" }""")
40+
bearerAuth(NetworkManager.accessToken)
41+
contentType(ContentType.Application.Json)
42+
}.body<Authentication>()
43+
}

common/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717

1818
package com.lambda.network.api.v1.endpoints
1919

20-
import com.github.kittinunf.fuel.Fuel
21-
import com.github.kittinunf.fuel.core.FuelError
22-
import com.github.kittinunf.fuel.core.extensions.jsonBody
23-
import com.github.kittinunf.fuel.gson.responseObject
2420
import com.lambda.module.modules.client.Network.apiUrl
2521
import com.lambda.module.modules.client.Network.apiVersion
22+
import com.lambda.network.LambdaHttp
2623
import com.lambda.network.api.v1.models.Authentication
24+
import io.ktor.client.call.*
25+
import io.ktor.client.request.*
26+
import io.ktor.http.*
2727

2828
/**
2929
* Creates a new session account with mojang session hashes
@@ -34,7 +34,9 @@ import com.lambda.network.api.v1.models.Authentication
3434
*
3535
* response: [Authentication] or error
3636
*/
37-
fun login(username: String, hash: String, success: (Authentication) -> Unit, failure: (FuelError) -> Unit) =
38-
Fuel.post("${apiUrl}/api/${apiVersion.value}/login")
39-
.jsonBody("""{ "username": "$username", "hash": "$hash" }""")
40-
.responseObject<Authentication> { _, _, result -> result.fold(success, failure) }
37+
suspend fun login(username: String, hash: String) = runCatching {
38+
LambdaHttp.post("${apiUrl}/api/${apiVersion.value}/login") {
39+
setBody("""{ "username": "$username", "hash": "$hash" }""")
40+
contentType(ContentType.Application.Json)
41+
}.body<Authentication>()
42+
}

common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717

1818
package com.lambda.network.api.v1.endpoints
1919

20-
import com.github.kittinunf.fuel.Fuel
21-
import com.github.kittinunf.fuel.core.FuelError
22-
import com.github.kittinunf.fuel.core.awaitResult
23-
import com.github.kittinunf.fuel.core.extensions.authentication
2420
import com.lambda.module.modules.client.Network.apiUrl
2521
import com.lambda.module.modules.client.Network.apiVersion
22+
import com.lambda.network.LambdaHttp
2623
import com.lambda.network.NetworkManager
24+
import io.ktor.client.request.*
25+
import io.ktor.http.*
2726

2827
/**
2928
* Sets the currently authenticated player's cape
@@ -33,8 +32,9 @@ import com.lambda.network.NetworkManager
3332
*
3433
* response: [Unit] or error
3534
*/
36-
fun setCape(id: String, success: (ByteArray) -> Unit, failure: (FuelError) -> Unit) =
37-
Fuel.put("$apiUrl/api/${apiVersion.value}/cape?id=$id")
38-
.authentication()
39-
.bearer(NetworkManager.accessToken)
40-
.response { _, _, resp -> resp.fold(success, failure) }
35+
suspend fun setCape(id: String) = runCatching {
36+
LambdaHttp.put("$apiUrl/api/${apiVersion.value}/cape?id=$id") {
37+
bearerAuth(NetworkManager.accessToken)
38+
contentType(ContentType.Application.Json)
39+
}
40+
}

fabric/build.gradle.kts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ val fabricApiVersion: String by project
2222
val kotlinFabricVersion: String by project
2323
val discordIPCVersion: String by project
2424
val kotlinVersion: String by project
25-
val fuelVersion: String by project
26-
val resultVersion: String by project
25+
val ktorVersion: String by project
2726

2827
base.archivesName = "${base.archivesName.get()}-fabric"
2928

@@ -90,10 +89,11 @@ dependencies {
9089
includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion")
9190
includeLib("com.pngencoder:pngencoder:0.15.0")
9291

93-
// Fuel HTTP library and dependencies
94-
includeLib("com.github.kittinunf.fuel:fuel:$fuelVersion")
95-
includeLib("com.github.kittinunf.fuel:fuel-gson:$fuelVersion")
96-
includeLib("com.github.kittinunf.result:result-jvm:$resultVersion")
92+
// Ktor
93+
includeLib("io.ktor:ktor-client-core:$ktorVersion")
94+
shadowBundle("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") }
95+
includeLib("io.ktor:ktor-client-content-negotiation:$ktorVersion")
96+
includeLib("io.ktor:ktor-serialization-gson:$ktorVersion")
9797

9898
// Add mods to the mod jar
9999
includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion")

0 commit comments

Comments
 (0)