From 34e800e6d30f9160dd707ec7930fa77911a87d2f Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:13:22 -0400 Subject: [PATCH 01/56] Fix: Lambda main --- common/src/main/kotlin/com/lambda/Lambda.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/Lambda.kt b/common/src/main/kotlin/com/lambda/Lambda.kt index a1b1cb6d6..0e4894e0e 100644 --- a/common/src/main/kotlin/com/lambda/Lambda.kt +++ b/common/src/main/kotlin/com/lambda/Lambda.kt @@ -2,9 +2,9 @@ package com.lambda import com.google.gson.Gson import com.google.gson.GsonBuilder -import com.lambda.config.serializer.BlockPosSerializer -import com.lambda.config.serializer.BlockSerializer -import com.lambda.config.serializer.ColorSerializer +import com.lambda.config.serializer.* +import com.lambda.module.tag.ModuleTag +import com.lambda.util.KeyCode import net.minecraft.block.Block import net.minecraft.client.MinecraftClient import net.minecraft.util.math.BlockPos @@ -19,10 +19,12 @@ object Lambda { const val SYMBOL = "λ" val VERSION: String = LoaderInfo.getVersion() val LOG: Logger = LogManager.getLogger(SYMBOL) - val mc: MinecraftClient by lazy { MinecraftClient.getInstance() } + @JvmStatic val mc: MinecraftClient by lazy { MinecraftClient.getInstance() } val gson: Gson = GsonBuilder() .setPrettyPrinting() + .registerTypeAdapter(ModuleTag::class.java, ModuleTagSerializer) + .registerTypeAdapter(KeyCode::class.java, KeyCodeSerializer) .registerTypeAdapter(Color::class.java, ColorSerializer) .registerTypeAdapter(BlockPos::class.java, BlockPosSerializer) .registerTypeAdapter(Block::class.java, BlockSerializer) From 8845e695deae84c2885ac4d40c1486fd2064be33 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:26:08 -0400 Subject: [PATCH 02/56] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 79b49284a..3a654bb87 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/ run/ + +.architectury-transformer/ From 92e34564e18ffbacaef7b5ef567f6a50d515d2d5 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:12:19 -0400 Subject: [PATCH 03/56] Test: Discord rpc --- common/build.gradle.kts | 13 ++- .../module/modules/client/DiscordRPC.kt | 84 +++++++++++++++++++ fabric/build.gradle.kts | 2 + forge/build.gradle.kts | 2 + gradle.properties | 1 + neoforge/build.gradle.kts | 2 + 6 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 256c15a52..9204970a8 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -3,6 +3,7 @@ val mixinExtrasVersion = property("mixinextras_version").toString() val kotlinVersion = property("kotlin_version").toString() val kotlinxCoroutinesVersion = property("kotlinx_coroutines_version").toString() val architecturyVersion = property("architectury_version").toString() +val discordIPCVersion = property("discord_ipc_version").toString() architectury { common("fabric", "forge", "neoforge") } @@ -13,6 +14,7 @@ loom { repositories { maven("https://maven.fabricmc.net/") + mavenCentral() } dependencies { @@ -25,10 +27,13 @@ dependencies { // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") -} -// Avoid nested jars -tasks.named("remapJar") { - enabled = false + // Add Discord IPC + implementation("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") } +tasks { + remapJar { + enabled = false + } +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt new file mode 100644 index 000000000..51dda859b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -0,0 +1,84 @@ +package com.lambda.module.modules.client + +import com.lambda.Lambda +import com.lambda.Lambda.mc +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.threading.runConcurrent +import dev.cbyrne.kdiscordipc.KDiscordIPC +import dev.cbyrne.kdiscordipc.core.event.DiscordEvent +import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent +import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent +import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent +import dev.cbyrne.kdiscordipc.data.activity.* +import java.util.* + +object DiscordRPC : Module( + name = "DiscordRPC", + description = "Discord Rich Presence configuration", + defaultTags = setOf(ModuleTag.CLIENT) +) { + private val line1Left by setting("Line 1 Left", LineInfo.VERSION) + private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) + private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) + private val line2Right by setting("Line 2 Right", LineInfo.HEALTH) + private val confirmCoordinates by setting("Show Coordinates", false) + private val confirmServer by setting("Show Server", false) + private val delay by setting("Update Delay", 200, 200..2000, 1, unit = "ms") + + private val rpc = KDiscordIPC("835368493150502923") + + private enum class LineInfo(val value: String) { + VERSION(Lambda.VERSION), + WORLD( + if (mc.currentServerEntry != null) "Multiplayer" + else if (mc.isIntegratedServerRunning) "Singleplayer" + else "Main Menu" + ), + USERNAME(mc.session.username), + HEALTH("${mc.player?.health ?: 0} HP"), + HUNGER("${mc.player?.hungerManager?.foodLevel ?: 0} Hunger"), + DIMENSION(mc.world?.dimension?.toString() ?: "Unknown"), + //COORDINATES(if (confirmCoordinates) "${mc.player!!.blockPos}" else "[Redacted]"), + //SERVER(if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" else "[Redacted]"), + FPS("${mc.currentFps} FPS"), + } + + init { + onEnableUnsafe { + runConcurrent(rpc::connect) + } + + onDisableUnsafe(rpc::disconnect) + + runConcurrent { + rpc.on { + Lambda.LOG.info("Discord RPC connected.") + + rpc.subscribe(DiscordEvent.CurrentUserUpdate) + rpc.subscribe(DiscordEvent.ActivityJoinRequest) + rpc.subscribe(DiscordEvent.ActivityJoin) + rpc.subscribe(DiscordEvent.ActivityInvite) + rpc.subscribe(DiscordEvent.ActivitySpectate) + + rpc.activityManager.setActivity { + largeImage("https://avatars.githubusercontent.com/u/71222289?v=4", "KDiscordIPC") + smallImage("https://avatars.githubusercontent.com/u/71222289?v=4", "Testing") + button("a", "https://google.com") + + party(UUID.randomUUID().toString(), 1, 2) + secrets(UUID.randomUUID().toString(), UUID.randomUUID().toString()) + timestamps(System.currentTimeMillis()) + } + } + + rpc.on { + Lambda.LOG.info("Discord RPC invite: $data") + } + + rpc.on { + Lambda.LOG.info("Discord RPC join: $data") + } + } + } +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index d5f3e092c..917924136 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -2,6 +2,7 @@ val fabricLoaderVersion = property("fabric_loader_version").toString() val fabricApiVersion = property("fabric_api_version").toString() val architecturyVersion = property("architectury_version").toString() val kotlinFabricVersion = property("kotlin_fabric_version").toString() +val discordIPCVersion = property("discord_ipc_version").toString() architectury { platformSetupLoomIde() @@ -43,6 +44,7 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion") diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 6bfa37c09..bdaa5bc21 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -2,6 +2,7 @@ val forgeVersion = property("forge_version").toString() val architecturyVersion = property("architectury_version").toString() val mixinExtrasVersion = property("mixinextras_version").toString() val kotlinForgeVersion = property("kotlin_forge_version").toString() +val discordIPCVersion = property("discord_ipc_version").toString() architectury { platformSetupLoomIde() @@ -54,6 +55,7 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") diff --git a/gradle.properties b/gradle.properties index cc7adc17a..7f8e9ca77 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ minecraft_version=1.20.4 mixinextras_version=0.3.5 kotlin_version=1.9.23 kotlinx_coroutines_version=1.8.0 +discord_ipc_version=0.2.2 # Architectury https://docs.architectury.dev/start enabled_platforms=fabric,forge diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index a6e03804c..e7e3bb84e 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -1,6 +1,7 @@ val neoVersion = property("neo_version").toString() val architecturyVersion = property("architectury_version").toString() val kotlinForgeVersion = property("kotlin_forge_version").toString() +val discordIPCVersion = property("discord_ipc_version").toString() architectury { platformSetupLoomIde() @@ -51,6 +52,7 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge-neoforge:$kotlinForgeVersion") From f3e0015cccda352deb0311bef06fc99a2869c3a0 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:13:27 -0400 Subject: [PATCH 04/56] Added rpc coroutine scope --- .../main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 51dda859b..34aa5e27a 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -2,6 +2,7 @@ package com.lambda.module.modules.client import com.lambda.Lambda import com.lambda.Lambda.mc +import com.lambda.event.EventFlow import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runConcurrent @@ -26,7 +27,7 @@ object DiscordRPC : Module( private val confirmServer by setting("Show Server", false) private val delay by setting("Update Delay", 200, 200..2000, 1, unit = "ms") - private val rpc = KDiscordIPC("835368493150502923") + private val rpc = KDiscordIPC("835368493150502923", scope = EventFlow.lambdaScope) private enum class LineInfo(val value: String) { VERSION(Lambda.VERSION), From 71f001c1bd02da6dff5fa84703a8e052f04a0f57 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:31:41 -0400 Subject: [PATCH 05/56] Fix: Crash on disconnect --- .../com/lambda/module/modules/client/DiscordRPC.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 34aa5e27a..2c9280789 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -47,10 +47,16 @@ object DiscordRPC : Module( init { onEnableUnsafe { - runConcurrent(rpc::connect) + runConcurrent { + rpc.connect() + } } - onDisableUnsafe(rpc::disconnect) + onDisableUnsafe { + runConcurrent { + if (rpc.connected) rpc.disconnect() + } + } runConcurrent { rpc.on { From 05146f45209fa2b9ae6ca60b31c3d61c9725cdaa Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:25:49 -0400 Subject: [PATCH 06/56] Added shutdown hook --- common/src/main/kotlin/com/lambda/threading/Hook.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 common/src/main/kotlin/com/lambda/threading/Hook.kt diff --git a/common/src/main/kotlin/com/lambda/threading/Hook.kt b/common/src/main/kotlin/com/lambda/threading/Hook.kt new file mode 100644 index 000000000..260499608 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/threading/Hook.kt @@ -0,0 +1,9 @@ +package com.lambda.threading + +import kotlin.concurrent.thread + +inline fun onShutdown(crossinline block: () -> Unit) { + Runtime.getRuntime().addShutdownHook(thread(start = false) { block() }) +} + + From ef73ea77a43f873b332949d5a3eb14e97fbcdb52 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:26:01 -0400 Subject: [PATCH 07/56] Fix: Enum crash --- .../module/modules/client/DiscordRPC.kt | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 2c9280789..b478011c2 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -5,12 +5,16 @@ import com.lambda.Lambda.mc import com.lambda.event.EventFlow import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.threading.onShutdown import com.lambda.threading.runConcurrent +import com.lambda.util.Nameable import dev.cbyrne.kdiscordipc.KDiscordIPC import dev.cbyrne.kdiscordipc.core.event.DiscordEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent +import dev.cbyrne.kdiscordipc.core.event.impl.DisconnectedEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent +import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.ErrorPacket import dev.cbyrne.kdiscordipc.data.activity.* import java.util.* @@ -19,17 +23,26 @@ object DiscordRPC : Module( description = "Discord Rich Presence configuration", defaultTags = setOf(ModuleTag.CLIENT) ) { + private val details by setting("Details", "Playing on Lambda") + private val playState by setting("Play State", "Playing") + + private val partyId by setting("Party ID", UUID.randomUUID().toString()) + private val joinSecret by setting("Join Secret", UUID.randomUUID().toString()) + private val partySize by setting("Party Size", 1, 1..100, 1) + private val partyMax by setting("Party Max", 2, 2..100, 1) + + private val confirmCoordinates by setting("Show Coordinates", false) + private val confirmServer by setting("Show Server", false) + private val line1Left by setting("Line 1 Left", LineInfo.VERSION) private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) private val line2Right by setting("Line 2 Right", LineInfo.HEALTH) - private val confirmCoordinates by setting("Show Coordinates", false) - private val confirmServer by setting("Show Server", false) private val delay by setting("Update Delay", 200, 200..2000, 1, unit = "ms") - private val rpc = KDiscordIPC("835368493150502923", scope = EventFlow.lambdaScope) + private val rpc = KDiscordIPC("1221289599427416127", scope = EventFlow.lambdaScope) - private enum class LineInfo(val value: String) { + private enum class LineInfo(val value: String) : Nameable { VERSION(Lambda.VERSION), WORLD( if (mc.currentServerEntry != null) "Multiplayer" @@ -40,8 +53,8 @@ object DiscordRPC : Module( HEALTH("${mc.player?.health ?: 0} HP"), HUNGER("${mc.player?.hungerManager?.foodLevel ?: 0} Hunger"), DIMENSION(mc.world?.dimension?.toString() ?: "Unknown"), - //COORDINATES(if (confirmCoordinates) "${mc.player!!.blockPos}" else "[Redacted]"), - //SERVER(if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" else "[Redacted]"), + COORDINATES(if (confirmCoordinates) "${mc.player!!.blockPos}" else "[Redacted]"), + SERVER(if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" else "[Redacted]"), FPS("${mc.currentFps} FPS"), } @@ -52,31 +65,27 @@ object DiscordRPC : Module( } } - onDisableUnsafe { - runConcurrent { - if (rpc.connected) rpc.disconnect() - } - } + onDisableUnsafe(::shutdown) + onShutdown(::shutdown) runConcurrent { rpc.on { Lambda.LOG.info("Discord RPC connected.") + rpc.activityManager.setActivity { + //timestamps(System.currentTimeMillis()) + largeImage("lambda", Lambda.VERSION) + + button("Download", "https://github.com/lambda-client/lambda/releases/latest") + + //party(partyId, partySize, partyMax) + //secrets(joinSecret) + } + rpc.subscribe(DiscordEvent.CurrentUserUpdate) rpc.subscribe(DiscordEvent.ActivityJoinRequest) rpc.subscribe(DiscordEvent.ActivityJoin) rpc.subscribe(DiscordEvent.ActivityInvite) - rpc.subscribe(DiscordEvent.ActivitySpectate) - - rpc.activityManager.setActivity { - largeImage("https://avatars.githubusercontent.com/u/71222289?v=4", "KDiscordIPC") - smallImage("https://avatars.githubusercontent.com/u/71222289?v=4", "Testing") - button("a", "https://google.com") - - party(UUID.randomUUID().toString(), 1, 2) - secrets(UUID.randomUUID().toString(), UUID.randomUUID().toString()) - timestamps(System.currentTimeMillis()) - } } rpc.on { @@ -86,6 +95,17 @@ object DiscordRPC : Module( rpc.on { Lambda.LOG.info("Discord RPC join: $data") } + + rpc.on { + Lambda.LOG.error("Discord RPC error: $message") + } + } + } + + private fun shutdown() { + if (rpc.connected) { + Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") + rpc.disconnect() } } } From 5dbd3613009901a6bdb67c2cabfbd4ce4f946f65 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:07:46 -0400 Subject: [PATCH 08/56] Fix: Rpc not showing on profile --- .../module/modules/client/DiscordRPC.kt | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index b478011c2..913205f11 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -7,15 +7,17 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown import com.lambda.threading.runConcurrent +import com.lambda.util.Communication.info import com.lambda.util.Nameable import dev.cbyrne.kdiscordipc.KDiscordIPC import dev.cbyrne.kdiscordipc.core.event.DiscordEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent -import dev.cbyrne.kdiscordipc.core.event.impl.DisconnectedEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.ErrorPacket +import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.SetActivityPacket import dev.cbyrne.kdiscordipc.data.activity.* +import dev.cbyrne.kdiscordipc.data.user.User import java.util.* object DiscordRPC : Module( @@ -23,13 +25,13 @@ object DiscordRPC : Module( description = "Discord Rich Presence configuration", defaultTags = setOf(ModuleTag.CLIENT) ) { - private val details by setting("Details", "Playing on Lambda") + private val playDetails by setting("Details", "Playing on Lambda") private val playState by setting("Play State", "Playing") - private val partyId by setting("Party ID", UUID.randomUUID().toString()) - private val joinSecret by setting("Join Secret", UUID.randomUUID().toString()) - private val partySize by setting("Party Size", 1, 1..100, 1) - private val partyMax by setting("Party Max", 2, 2..100, 1) + private var joinSecret by setting("Join Secret", UUID.randomUUID().toString(), visibility = { false }) + private var partyId by setting("Party ID", UUID.randomUUID().toString(), visibility = { false }) + private var partySize by setting("Party Size", 1, 1..16, 1, visibility = { false }) + private var partyMax by setting("Party Max Size", 16, 16..16, 1, visibility = { false }) private val confirmCoordinates by setting("Show Coordinates", false) private val confirmServer by setting("Show Server", false) @@ -41,6 +43,7 @@ object DiscordRPC : Module( private val delay by setting("Update Delay", 200, 200..2000, 1, unit = "ms") private val rpc = KDiscordIPC("1221289599427416127", scope = EventFlow.lambdaScope) + private lateinit var lastInviter: User private enum class LineInfo(val value: String) : Nameable { VERSION(Lambda.VERSION), @@ -59,47 +62,56 @@ object DiscordRPC : Module( } init { - onEnableUnsafe { - runConcurrent { - rpc.connect() - } - } - - onDisableUnsafe(::shutdown) - onShutdown(::shutdown) - runConcurrent { rpc.on { - Lambda.LOG.info("Discord RPC connected.") + Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") rpc.activityManager.setActivity { - //timestamps(System.currentTimeMillis()) - largeImage("lambda", Lambda.VERSION) + details = playDetails + state = playState - button("Download", "https://github.com/lambda-client/lambda/releases/latest") + largeImage("lambda", Lambda.VERSION) - //party(partyId, partySize, partyMax) - //secrets(joinSecret) + party(partyId, partySize, partyMax) + secrets(joinSecret) + timestamps(System.currentTimeMillis()) } rpc.subscribe(DiscordEvent.CurrentUserUpdate) rpc.subscribe(DiscordEvent.ActivityJoinRequest) rpc.subscribe(DiscordEvent.ActivityJoin) rpc.subscribe(DiscordEvent.ActivityInvite) + rpc.subscribe(DiscordEvent.ActivitySpectate) } rpc.on { - Lambda.LOG.info("Discord RPC invite: $data") + lastInviter = data.user + info("${lastInviter.username} has invited you to play") + rpc.activityManager.acceptInvite(data) // TODO: Click button to join } rpc.on { - Lambda.LOG.info("Discord RPC join: $data") + joinSecret = data.secret + info("Joined ${lastInviter.username}'s party.") // TODO: Join server button } - rpc.on { - Lambda.LOG.error("Discord RPC error: $message") + rpc.on { + partyId = data?.party?.id ?: partyId + partySize = data?.party?.size?.currentSize ?: partySize + partyMax = data?.party?.size?.maxSize ?: partyMax } + + rpc.connect() } + + /*onEnableUnsafe { + runConcurrent { + rpc.connect() + } + }*/ + + onDisableUnsafe(::shutdown) + onShutdown(::shutdown) } private fun shutdown() { From e75f8aa9656941e6f3dad148e0d082cf73ceca36 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:32:44 -0400 Subject: [PATCH 09/56] Refactor: Use same thread for setup and connect --- .../module/modules/client/DiscordRPC.kt | 116 ++++++++++-------- 1 file changed, 67 insertions(+), 49 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 913205f11..3e0f7d43a 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -14,10 +14,10 @@ import dev.cbyrne.kdiscordipc.core.event.DiscordEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent -import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.ErrorPacket import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.SetActivityPacket import dev.cbyrne.kdiscordipc.data.activity.* import dev.cbyrne.kdiscordipc.data.user.User +import kotlinx.coroutines.Job import java.util.* object DiscordRPC : Module( @@ -28,10 +28,10 @@ object DiscordRPC : Module( private val playDetails by setting("Details", "Playing on Lambda") private val playState by setting("Play State", "Playing") - private var joinSecret by setting("Join Secret", UUID.randomUUID().toString(), visibility = { false }) - private var partyId by setting("Party ID", UUID.randomUUID().toString(), visibility = { false }) - private var partySize by setting("Party Size", 1, 1..16, 1, visibility = { false }) - private var partyMax by setting("Party Max Size", 16, 16..16, 1, visibility = { false }) + private var joinSecret by setting("Join Secret", UUID.randomUUID().toString()) + private var partyId by setting("Party ID", UUID.randomUUID().toString()) + private var partySize by setting("Party Size", 1, 1..16, 1) + private var partyMax by setting("Party Max Size", 16, 2..16, 1) private val confirmCoordinates by setting("Show Coordinates", false) private val confirmServer by setting("Show Server", false) @@ -43,7 +43,8 @@ object DiscordRPC : Module( private val delay by setting("Update Delay", 200, 200..2000, 1, unit = "ms") private val rpc = KDiscordIPC("1221289599427416127", scope = EventFlow.lambdaScope) - private lateinit var lastInviter: User + private var lastInviter: User? = null + private var job: Job? = null private enum class LineInfo(val value: String) : Nameable { VERSION(Lambda.VERSION), @@ -62,62 +63,79 @@ object DiscordRPC : Module( } init { - runConcurrent { - rpc.on { - Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") - - rpc.activityManager.setActivity { - details = playDetails - state = playState - - largeImage("lambda", Lambda.VERSION) - - party(partyId, partySize, partyMax) - secrets(joinSecret) - timestamps(System.currentTimeMillis()) - } - - rpc.subscribe(DiscordEvent.CurrentUserUpdate) - rpc.subscribe(DiscordEvent.ActivityJoinRequest) - rpc.subscribe(DiscordEvent.ActivityJoin) - rpc.subscribe(DiscordEvent.ActivityInvite) - rpc.subscribe(DiscordEvent.ActivitySpectate) - } + lock(runConcurrent { + setup() + connect() + }) // Works + + onEnableUnsafe { + lock(runConcurrent { + setup() + connect() + }) // Doesn't work + } - rpc.on { - lastInviter = data.user - info("${lastInviter.username} has invited you to play") - rpc.activityManager.acceptInvite(data) // TODO: Click button to join - } + onDisableUnsafe(::disconnect) + onShutdown(::disconnect) + } - rpc.on { - joinSecret = data.secret - info("Joined ${lastInviter.username}'s party.") // TODO: Join server button - } + private fun lock(rpc: Job?) { + if (job?.isActive == true) job?.cancel() + job = rpc + } + + private suspend fun setup() { + rpc.on { + Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") + + rpc.activityManager.setActivity { + details = playDetails + state = playState - rpc.on { - partyId = data?.party?.id ?: partyId - partySize = data?.party?.size?.currentSize ?: partySize - partyMax = data?.party?.size?.maxSize ?: partyMax + largeImage("lambda", Lambda.VERSION) + + party(partyId, partySize, partyMax) + secrets(joinSecret) + timestamps(System.currentTimeMillis()) } - rpc.connect() + rpc.subscribe(DiscordEvent.CurrentUserUpdate) + rpc.subscribe(DiscordEvent.ActivityJoinRequest) + rpc.subscribe(DiscordEvent.ActivityJoin) + rpc.subscribe(DiscordEvent.ActivityInvite) + rpc.subscribe(DiscordEvent.ActivitySpectate) } - /*onEnableUnsafe { - runConcurrent { - rpc.connect() - } - }*/ + rpc.on { + lastInviter = data.user + info("${lastInviter?.username} has invited you to play") + rpc.activityManager.acceptInvite(data) // TODO: Click button to join + } - onDisableUnsafe(::shutdown) - onShutdown(::shutdown) + rpc.on { + joinSecret = data.secret + info("Joined ${lastInviter?.username}'s party.") // TODO: Join server button + } + + rpc.on { + partyId = data?.party?.id ?: partyId + partySize = data?.party?.size?.currentSize ?: partySize + partyMax = data?.party?.size?.maxSize ?: partyMax + } + } + + private suspend fun connect() { + if (!rpc.connected) { + Lambda.LOG.info("Connecting to Discord RPC.") + rpc.connect() + } } - private fun shutdown() { + private fun disconnect() { if (rpc.connected) { Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") rpc.disconnect() + lock(null) } } } From 650575b9f83bc3ecb610af20d7b80821a1de9cac Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:42:17 -0400 Subject: [PATCH 10/56] Added error event --- .../kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 3e0f7d43a..23fff26bc 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -13,6 +13,7 @@ import dev.cbyrne.kdiscordipc.KDiscordIPC import dev.cbyrne.kdiscordipc.core.event.DiscordEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent +import dev.cbyrne.kdiscordipc.core.event.impl.ErrorEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.SetActivityPacket import dev.cbyrne.kdiscordipc.data.activity.* @@ -122,6 +123,10 @@ object DiscordRPC : Module( partySize = data?.party?.size?.currentSize ?: partySize partyMax = data?.party?.size?.maxSize ?: partyMax } + + rpc.on { + Lambda.LOG.error("Discord RPC error: ${data.message}") + } } private suspend fun connect() { From 14528525b6c938a6e8abd856d98bb6d250f589ce Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:52:49 -0400 Subject: [PATCH 11/56] Removed useless events --- .../main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 23fff26bc..9f11f414d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -100,11 +100,9 @@ object DiscordRPC : Module( timestamps(System.currentTimeMillis()) } - rpc.subscribe(DiscordEvent.CurrentUserUpdate) rpc.subscribe(DiscordEvent.ActivityJoinRequest) rpc.subscribe(DiscordEvent.ActivityJoin) rpc.subscribe(DiscordEvent.ActivityInvite) - rpc.subscribe(DiscordEvent.ActivitySpectate) } rpc.on { From b5c844f5911aec15686e780795fb95d4ed30378d Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Mon, 25 Mar 2024 18:14:15 -0400 Subject: [PATCH 12/56] Added small image player --- .../main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 9f11f414d..059fbd79c 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -94,6 +94,7 @@ object DiscordRPC : Module( state = playState largeImage("lambda", Lambda.VERSION) + smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) party(partyId, partySize, partyMax) secrets(joinSecret) From 4464cf18eef88db8b3fc24fcb8ea1f21fcfe6a05 Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 26 Mar 2024 04:33:15 +0100 Subject: [PATCH 13/56] Fix DiscordRPC --- common/src/main/kotlin/com/lambda/Lambda.kt | 1 + .../kotlin/com/lambda/config/Configuration.kt | 5 +- .../main/kotlin/com/lambda/event/EventFlow.kt | 1 + .../module/modules/client/DiscordRPC.kt | 175 ++++++++++-------- 4 files changed, 100 insertions(+), 82 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/Lambda.kt b/common/src/main/kotlin/com/lambda/Lambda.kt index f2acfcdd2..13e860390 100644 --- a/common/src/main/kotlin/com/lambda/Lambda.kt +++ b/common/src/main/kotlin/com/lambda/Lambda.kt @@ -17,6 +17,7 @@ object Lambda { const val MOD_NAME = "Lambda" const val MOD_ID = "lambda" const val SYMBOL = "λ" + const val APP_ID = "1221289599427416127" val VERSION: String = LoaderInfo.getVersion() val LOG: Logger = LogManager.getLogger(SYMBOL) @JvmStatic val mc: MinecraftClient by lazy { MinecraftClient.getInstance() } diff --git a/common/src/main/kotlin/com/lambda/config/Configuration.kt b/common/src/main/kotlin/com/lambda/config/Configuration.kt index 30aeb97e3..cb82399c5 100644 --- a/common/src/main/kotlin/com/lambda/config/Configuration.kt +++ b/common/src/main/kotlin/com/lambda/config/Configuration.kt @@ -5,6 +5,7 @@ import com.google.gson.JsonObject import com.google.gson.JsonParser import com.lambda.Lambda.LOG import com.lambda.Lambda.gson +import com.lambda.event.EventFlow.ioScope import com.lambda.event.EventFlow.lambdaScope import com.lambda.event.events.ClientEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener @@ -81,7 +82,7 @@ abstract class Configuration : Jsonable { } fun tryLoad() { - lambdaScope.launch(Dispatchers.IO) { + ioScope.launch { runCatching { load(primary) } .onSuccess { val message = "${configName.capitalize()} config loaded." @@ -108,7 +109,7 @@ abstract class Configuration : Jsonable { } fun trySave() { - lambdaScope.launch(Dispatchers.IO) { + ioScope.launch { runCatching { save() } .onSuccess { val message = "Saved ${configName.capitalize()} config." diff --git a/common/src/main/kotlin/com/lambda/event/EventFlow.kt b/common/src/main/kotlin/com/lambda/event/EventFlow.kt index 3c9a4b313..ebf18b3ea 100644 --- a/common/src/main/kotlin/com/lambda/event/EventFlow.kt +++ b/common/src/main/kotlin/com/lambda/event/EventFlow.kt @@ -31,6 +31,7 @@ object EventFlow { * useful when you have multiple independent [Job]s running in parallel. */ val lambdaScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) + val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val concurrentFlow = MutableSharedFlow( extraBufferCapacity = 1000, onBufferOverflow = BufferOverflow.DROP_OLDEST diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 059fbd79c..5040447b5 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -3,12 +3,14 @@ package com.lambda.module.modules.client import com.lambda.Lambda import com.lambda.Lambda.mc import com.lambda.event.EventFlow +import com.lambda.event.EventFlow.ioScope import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown import com.lambda.threading.runConcurrent import com.lambda.util.Communication.info import com.lambda.util.Nameable +import com.lambda.util.StringUtils.capitalize import dev.cbyrne.kdiscordipc.KDiscordIPC import dev.cbyrne.kdiscordipc.core.event.DiscordEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent @@ -18,128 +20,141 @@ import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.SetActivityPacket import dev.cbyrne.kdiscordipc.data.activity.* import dev.cbyrne.kdiscordipc.data.user.User -import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.util.* object DiscordRPC : Module( name = "DiscordRPC", description = "Discord Rich Presence configuration", - defaultTags = setOf(ModuleTag.CLIENT) + defaultTags = setOf(ModuleTag.CLIENT), +// enabledByDefault = true, // ToDo: Enable before release ) { - private val playDetails by setting("Details", "Playing on Lambda") - private val playState by setting("Play State", "Playing") +// private val playDetails by setting("Details", "Playing on Lambda") +// private val playState by setting("Play State", "Playing") - private var joinSecret by setting("Join Secret", UUID.randomUUID().toString()) - private var partyId by setting("Party ID", UUID.randomUUID().toString()) - private var partySize by setting("Party Size", 1, 1..16, 1) - private var partyMax by setting("Party Max Size", 16, 2..16, 1) +// private var joinSecret by setting("Join Secret", UUID.randomUUID().toString()) +// private var partyId by setting("Party ID", UUID.randomUUID().toString()) +// private var partySize by setting("Party Size", 1, 1..16, 1) +// private var partyMax by setting("Party Max Size", 16, 2..16, 1) private val confirmCoordinates by setting("Show Coordinates", false) private val confirmServer by setting("Show Server", false) - private val line1Left by setting("Line 1 Left", LineInfo.VERSION) + private val line1Left by setting("Line 1 Left", LineInfo.WORLD) private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) - private val line2Right by setting("Line 2 Right", LineInfo.HEALTH) - private val delay by setting("Update Delay", 200, 200..2000, 1, unit = "ms") + private val line2Right by setting("Line 2 Right", LineInfo.FPS) + // Max update delay is 5 per 20 seconds -> every 4 seconds + private val delay by setting("Update Delay", 4, 4..60, 1, unit = "s") - private val rpc = KDiscordIPC("1221289599427416127", scope = EventFlow.lambdaScope) - private var lastInviter: User? = null - private var job: Job? = null + private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) + private val startup = System.currentTimeMillis() +// private var lastInviter: User? = null - private enum class LineInfo(val value: String) : Nameable { - VERSION(Lambda.VERSION), - WORLD( + private enum class LineInfo(val value: () -> String) : Nameable { + VERSION({ Lambda.VERSION }), + WORLD({ if (mc.currentServerEntry != null) "Multiplayer" else if (mc.isIntegratedServerRunning) "Singleplayer" else "Main Menu" - ), - USERNAME(mc.session.username), - HEALTH("${mc.player?.health ?: 0} HP"), - HUNGER("${mc.player?.hungerManager?.foodLevel ?: 0} Hunger"), - DIMENSION(mc.world?.dimension?.toString() ?: "Unknown"), - COORDINATES(if (confirmCoordinates) "${mc.player!!.blockPos}" else "[Redacted]"), - SERVER(if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" else "[Redacted]"), - FPS("${mc.currentFps} FPS"), + }), + USERNAME({ mc.session.username }), + HEALTH({ "${mc.player?.health ?: 0} HP" }), + HUNGER({ "${mc.player?.hungerManager?.foodLevel ?: 0} Hunger" }), + DIMENSION({ mc.world?.dimensionKey?.value?.path?.capitalize() ?: "Unknown" }), + COORDINATES({ if (confirmCoordinates) "Coords: ${mc.player?.blockPos?.toShortString()}" else "[Redacted]" }), + SERVER({ if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" else "[Redacted]" }), + FPS({ "${mc.currentFps} FPS" }), } init { - lock(runConcurrent { - setup() - connect() - }) // Works + ioScope.launch { + rpc.register() + + while (true) { + if (rpc.connected) update() + delay(delay * 1000L) + } + } onEnableUnsafe { - lock(runConcurrent { - setup() - connect() - }) // Doesn't work + connect() + } + + onDisableUnsafe { + disconnect() } - onDisableUnsafe(::disconnect) - onShutdown(::disconnect) + onShutdown { + disconnect() + } } - private fun lock(rpc: Job?) { - if (job?.isActive == true) job?.cancel() - job = rpc + private fun connect() { + ioScope.launch { + if (!rpc.connected) { + Lambda.LOG.info("Connecting to Discord RPC.") + rpc.connect() + } + } } - private suspend fun setup() { - rpc.on { - Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") + private fun disconnect() { + ioScope.launch { + if (rpc.connected) { + Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") + rpc.disconnect() + } + } + } + private fun update() { + ioScope.launch { rpc.activityManager.setActivity { - details = playDetails - state = playState + details = "${line1Left.value()} ${line1Right.value()}".take(128) + state = "${line2Left.value()} ${line2Right.value()}".take(128) largeImage("lambda", Lambda.VERSION) smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - party(partyId, partySize, partyMax) - secrets(joinSecret) - timestamps(System.currentTimeMillis()) - } - - rpc.subscribe(DiscordEvent.ActivityJoinRequest) - rpc.subscribe(DiscordEvent.ActivityJoin) - rpc.subscribe(DiscordEvent.ActivityInvite) - } +// party(partyId, partySize, partyMax) +// secrets(joinSecret) + button("Download", "https://modrinth.com/") // ToDo: Add real link - rpc.on { - lastInviter = data.user - info("${lastInviter?.username} has invited you to play") - rpc.activityManager.acceptInvite(data) // TODO: Click button to join + timestamps(startup) + } } + } - rpc.on { - joinSecret = data.secret - info("Joined ${lastInviter?.username}'s party.") // TODO: Join server button - } + private suspend fun KDiscordIPC.register() { + on { + Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") - rpc.on { - partyId = data?.party?.id ?: partyId - partySize = data?.party?.size?.currentSize ?: partySize - partyMax = data?.party?.size?.maxSize ?: partyMax +// subscribe(DiscordEvent.ActivityJoinRequest) +// subscribe(DiscordEvent.ActivityJoin) +// subscribe(DiscordEvent.ActivityInvite) } - rpc.on { +// on { +// lastInviter = data.user +// info("${lastInviter?.username} has invited you to play") +// activityManager.acceptInvite(data) // TODO: Click button to join +// } +// +// on { +// joinSecret = data.secret +// info("Joined ${lastInviter?.username}'s party.") // TODO: Join server button +// } +// +// on { +// partyId = data?.party?.id ?: partyId +// partySize = data?.party?.size?.currentSize ?: partySize +// partyMax = data?.party?.size?.maxSize ?: partyMax +// } + + on { Lambda.LOG.error("Discord RPC error: ${data.message}") } } - - private suspend fun connect() { - if (!rpc.connected) { - Lambda.LOG.info("Connecting to Discord RPC.") - rpc.connect() - } - } - - private fun disconnect() { - if (rpc.connected) { - Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") - rpc.disconnect() - lock(null) - } - } } From 0f110639771b2244480bc274ac8b6dcb70109ee5 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:53:20 -0400 Subject: [PATCH 14/56] Feature: Discord RPC --- .../module/modules/client/DiscordRPC.kt | 83 ++++++++++++------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 5040447b5..5386a18f8 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -9,10 +9,16 @@ import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown import com.lambda.threading.runConcurrent import com.lambda.util.Communication.info +import com.lambda.util.Communication.toast import com.lambda.util.Nameable import com.lambda.util.StringUtils.capitalize +import com.lambda.util.text.ClickEvents +import com.lambda.util.text.buildText +import com.lambda.util.text.clickEvent +import com.lambda.util.text.literal import dev.cbyrne.kdiscordipc.KDiscordIPC import dev.cbyrne.kdiscordipc.core.event.DiscordEvent +import dev.cbyrne.kdiscordipc.core.event.data.ActivityInviteEventData import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent import dev.cbyrne.kdiscordipc.core.event.impl.ErrorEvent @@ -28,29 +34,31 @@ object DiscordRPC : Module( name = "DiscordRPC", description = "Discord Rich Presence configuration", defaultTags = setOf(ModuleTag.CLIENT), -// enabledByDefault = true, // ToDo: Enable before release + //enabledByDefault = true, // ToDo: Enable before release ) { -// private val playDetails by setting("Details", "Playing on Lambda") -// private val playState by setting("Play State", "Playing") -// private var joinSecret by setting("Join Secret", UUID.randomUUID().toString()) -// private var partyId by setting("Party ID", UUID.randomUUID().toString()) -// private var partySize by setting("Party Size", 1, 1..16, 1) -// private var partyMax by setting("Party Max Size", 16, 2..16, 1) + private var joinSecret by setting("Join Secret", UUID.randomUUID().toString()) // TODO: Server to generate this + private var partyId by setting("Party ID", UUID.randomUUID().toString()) + private var partySize by setting("Party Size", 1, 1..16, 1) + private var partyMax by setting("Party Max Size", 16, 2..16, 1) private val confirmCoordinates by setting("Show Coordinates", false) - private val confirmServer by setting("Show Server", false) + private val confirmServer by setting("Expose server", false, description = "Allow to show what server you are on and allow to join parties.") + private val enableParty by setting("Enable Party", true, description = "Will allow you to create and join parties but cannot have buttons.") + private val showTime by setting("Show Time", true, description = "Show how long you have been playing for.") private val line1Left by setting("Line 1 Left", LineInfo.WORLD) private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) private val line2Right by setting("Line 2 Right", LineInfo.FPS) + // Max update delay is 5 per 20 seconds -> every 4 seconds private val delay by setting("Update Delay", 4, 4..60, 1, unit = "s") private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) private val startup = System.currentTimeMillis() -// private var lastInviter: User? = null + private var lastInviter: User? = null + private var lastInvite: ActivityInviteEventData? = null private enum class LineInfo(val value: () -> String) : Nameable { VERSION({ Lambda.VERSION }), @@ -118,11 +126,15 @@ object DiscordRPC : Module( largeImage("lambda", Lambda.VERSION) smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) -// party(partyId, partySize, partyMax) -// secrets(joinSecret) - button("Download", "https://modrinth.com/") // ToDo: Add real link + if (enableParty && false) { // TODO: Implement parties + // TODO: Only enable if the player is in a server + party(partyId, partySize, partyMax) + secrets(joinSecret) + } else { + button("Download", "https://modrinth.com/") // ToDo: Add real link + } - timestamps(startup) + if (showTime) timestamps(startup) } } } @@ -131,27 +143,34 @@ object DiscordRPC : Module( on { Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") -// subscribe(DiscordEvent.ActivityJoinRequest) -// subscribe(DiscordEvent.ActivityJoin) -// subscribe(DiscordEvent.ActivityInvite) + subscribe(DiscordEvent.ActivityJoinRequest) + subscribe(DiscordEvent.ActivityJoin) + subscribe(DiscordEvent.ActivityInvite) + } + + on { + lastInviter = data.user + + info(buildText { + clickEvent(ClickEvents.runCommand(";rpc accept")) { + literal("Click to join ${data.user.username}'s party.") + } + }) // TODO: Possibility to block users + + toast("You have been invited to play by ${lastInviter?.username}") // TODO: Custom toast ? + lastInvite = data + } + + on { + joinSecret = data.secret + info("Joined ${lastInviter?.username}'s party.") } -// on { -// lastInviter = data.user -// info("${lastInviter?.username} has invited you to play") -// activityManager.acceptInvite(data) // TODO: Click button to join -// } -// -// on { -// joinSecret = data.secret -// info("Joined ${lastInviter?.username}'s party.") // TODO: Join server button -// } -// -// on { -// partyId = data?.party?.id ?: partyId -// partySize = data?.party?.size?.currentSize ?: partySize -// partyMax = data?.party?.size?.maxSize ?: partyMax -// } + on { + partyId = data?.party?.id ?: partyId + partySize = data?.party?.size?.currentSize ?: partySize + partyMax = data?.party?.size?.maxSize ?: partyMax + } on { Lambda.LOG.error("Discord RPC error: ${data.message}") From 89cae7e4bdc82d33c5ed90d49ef7dd95fc883cf9 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 30 Mar 2024 13:31:35 -0400 Subject: [PATCH 15/56] Added extremely detailled kdoc for shutdown hook --- .../main/kotlin/com/lambda/threading/Hook.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/common/src/main/kotlin/com/lambda/threading/Hook.kt b/common/src/main/kotlin/com/lambda/threading/Hook.kt index 260499608..ab77b0715 100644 --- a/common/src/main/kotlin/com/lambda/threading/Hook.kt +++ b/common/src/main/kotlin/com/lambda/threading/Hook.kt @@ -2,6 +2,61 @@ package com.lambda.threading import kotlin.concurrent.thread +/** + * Registers a shutdown hook to execute the specified [block] of code when the application is shutting down. + * This function ensures that the given [block] will be executed before the JVM terminates, allowing graceful cleanup + * or finalization tasks to be performed. + * + * @param block the code to be executed on shutdown. + * + * ## Usage: + * ``` + * onShutdown { + * // Perform cleanup tasks or finalizations here + * println("Shutting down gracefully...") + * } + * ``` + * + * ## Advantages: + * - Provides a convenient and reliable mechanism for executing cleanup tasks when the application exits. + * - Helps ensure that critical resources are released properly, preventing resource leaks or data corruption. + * - Can be used to handle cleanup in various scenarios, such as closing open connections, saving application state, + * or logging shutdown events. + * + * ## Code Examples: + * 1. Registering a simple shutdown hook: + * ``` + * onShutdown { + * println("Performing cleanup...") + * } + * ``` + * 2. Performing resource cleanup on shutdown: + * ``` + * onShutdown { + * databaseConnection.close() + * fileWriter.close() + * println("Resources closed successfully.") + * } + * ``` + * + * ## What Not to Do: + * - Avoid performing time-consuming or blocking operations inside the shutdown hook, as it may delay the shutdown process + * and lead to undesirable behavior. + * - Do not rely solely on shutdown hooks for critical tasks that must be executed reliably. Consider using other + * mechanisms such as proper exception handling or manual cleanup where necessary. + * - Avoid registering multiple shutdown hooks for the same purpose, as it may result in unexpected behavior or conflicts + * between the hooks. + * + * ## Edge Cases: + * - If the JVM is terminated abruptly or forcefully (e.g., via `kill -9` on Unix-like systems), the shutdown hooks may + * not have a chance to run, leading to potential resource leaks or incomplete cleanup. This is a rare scenario but + * should be considered when designing critical cleanup tasks. + * - In certain environments or configurations where the JVM shutdown process is interrupted or bypassed, such as when + * running in a containerized environment where the container itself is forcibly stopped, the shutdown hooks may not + * execute as expected. It's important to be aware of the behavior of the runtime environment and handle such cases + * accordingly, possibly by implementing additional cleanup mechanisms or relying on external systems for graceful + * shutdown coordination. + */ inline fun onShutdown(crossinline block: () -> Unit) { Runtime.getRuntime().addShutdownHook(thread(start = false) { block() }) } From d89bf471086de5e3b60768207ad8752c9970a752 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 30 Mar 2024 13:32:17 -0400 Subject: [PATCH 16/56] Feature: Connection state events --- .../{ => network}/ClientConnectionMixin.java | 4 +- .../network/ClientLoginNetworkMixin.java | 22 +++++++++ .../network/HandshakeC2SPacketMixin.java | 18 ++++++++ .../network/LoginHelloC2SPacketMixin.java | 19 ++++++++ .../mixin/network/LoginKeyC2SPacketMixin.java | 20 ++++++++ .../lambda/event/events/ConnectionEvent.kt | 46 +++++++++++++++---- .../module/modules/client/DiscordRPC.kt | 25 +++++++++- .../main/resources/lambda.mixins.common.json | 6 ++- 8 files changed, 147 insertions(+), 13 deletions(-) rename common/src/main/java/com/lambda/mixin/{ => network}/ClientConnectionMixin.java (96%) create mode 100644 common/src/main/java/com/lambda/mixin/network/ClientLoginNetworkMixin.java create mode 100644 common/src/main/java/com/lambda/mixin/network/HandshakeC2SPacketMixin.java create mode 100644 common/src/main/java/com/lambda/mixin/network/LoginHelloC2SPacketMixin.java create mode 100644 common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java diff --git a/common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java b/common/src/main/java/com/lambda/mixin/network/ClientConnectionMixin.java similarity index 96% rename from common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java rename to common/src/main/java/com/lambda/mixin/network/ClientConnectionMixin.java index 1c8daf224..dee4fb53c 100644 --- a/common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java +++ b/common/src/main/java/com/lambda/mixin/network/ClientConnectionMixin.java @@ -1,4 +1,4 @@ -package com.lambda.mixin; +package com.lambda.mixin.network; import com.lambda.event.EventFlow; import com.lambda.event.events.ConnectionEvent; @@ -66,7 +66,7 @@ private void onConnect( ConnectionIntent intent, CallbackInfo ci ) { - EventFlow.post(new ConnectionEvent.Connect(address, port, listener, intent)); + EventFlow.post(new ConnectionEvent.Connect.Pre(address, port, listener, intent)); } @Inject(method = "disconnect(Lnet/minecraft/text/Text;)V", at = @At("HEAD")) diff --git a/common/src/main/java/com/lambda/mixin/network/ClientLoginNetworkMixin.java b/common/src/main/java/com/lambda/mixin/network/ClientLoginNetworkMixin.java new file mode 100644 index 000000000..4b183a560 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/network/ClientLoginNetworkMixin.java @@ -0,0 +1,22 @@ +package com.lambda.mixin.network; + +import com.lambda.event.EventFlow; +import com.lambda.event.events.ConnectionEvent; +import com.lambda.module.modules.client.DiscordRPC; +import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.network.packet.s2c.login.LoginSuccessS2CPacket; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientLoginNetworkHandler.class) +public class ClientLoginNetworkMixin { + + @Inject(method = "onSuccess(Lnet/minecraft/network/packet/s2c/login/LoginSuccessS2CPacket;)V", at = @At("HEAD")) + private void onSuccess(LoginSuccessS2CPacket packet, CallbackInfo ci) { + EventFlow.post(new ConnectionEvent.Connect.Post(packet.getProfile())); + } +} diff --git a/common/src/main/java/com/lambda/mixin/network/HandshakeC2SPacketMixin.java b/common/src/main/java/com/lambda/mixin/network/HandshakeC2SPacketMixin.java new file mode 100644 index 000000000..5218e76a8 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/network/HandshakeC2SPacketMixin.java @@ -0,0 +1,18 @@ +package com.lambda.mixin.network; + +import com.lambda.event.EventFlow; +import com.lambda.event.events.ConnectionEvent; +import net.minecraft.network.packet.c2s.handshake.ConnectionIntent; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(HandshakeC2SPacket.class) +public class HandshakeC2SPacketMixin { + @Inject(method = "(ILjava/lang/String;ILnet/minecraft/network/packet/c2s/handshake/ConnectionIntent;)V", at = @At("TAIL")) + private void onHandshakeC2SPacket(int i, String string, int j, ConnectionIntent connectionIntent, CallbackInfo ci) { + EventFlow.post(new ConnectionEvent.Connect.Handshake(i, string, j, connectionIntent)); + } +} diff --git a/common/src/main/java/com/lambda/mixin/network/LoginHelloC2SPacketMixin.java b/common/src/main/java/com/lambda/mixin/network/LoginHelloC2SPacketMixin.java new file mode 100644 index 000000000..67667f0a3 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/network/LoginHelloC2SPacketMixin.java @@ -0,0 +1,19 @@ +package com.lambda.mixin.network; + +import com.lambda.event.EventFlow; +import com.lambda.event.events.ConnectionEvent; +import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.UUID; + +@Mixin(LoginHelloC2SPacket.class) +public class LoginHelloC2SPacketMixin { + @Inject(method = "(Ljava/lang/String;Ljava/util/UUID;)V", at = @At("TAIL")) + private void onLoginHelloC2SPacket(String string, UUID uUID, CallbackInfo ci) { + EventFlow.post(new ConnectionEvent.Connect.Login.Hello(string, uUID)); + } +} diff --git a/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java b/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java new file mode 100644 index 000000000..07bda6368 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java @@ -0,0 +1,20 @@ +package com.lambda.mixin.network; + +import com.lambda.event.EventFlow; +import com.lambda.event.events.ConnectionEvent; +import net.minecraft.network.packet.c2s.login.LoginKeyC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.crypto.SecretKey; +import java.security.PublicKey; + +@Mixin(LoginKeyC2SPacket.class) +public class LoginKeyC2SPacketMixin { + @Inject(method = "(Ljavax/crypto/SecretKey;Ljava/security/PublicKey;[B)V", at = @At("TAIL")) + private void onLoginKeyC2SPacket(SecretKey secretKey, PublicKey publicKey, byte[] nonce, CallbackInfo ci) { + EventFlow.post(new ConnectionEvent.Connect.Login.Key(secretKey, publicKey, nonce)); + } +} diff --git a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt index 21323614c..62748076b 100644 --- a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt @@ -1,17 +1,47 @@ package com.lambda.event.events import com.lambda.event.Event +import com.mojang.authlib.GameProfile import net.minecraft.network.listener.PacketListener import net.minecraft.network.packet.c2s.handshake.ConnectionIntent import net.minecraft.text.Text +import java.security.PublicKey +import java.util.UUID +import javax.crypto.SecretKey -abstract class ConnectionEvent : Event { - class Connect( - val host: String, - port: Int, - listener: PacketListener, - intent: ConnectionIntent, - ) : ConnectionEvent() +sealed class ConnectionEvent : Event { + sealed class Connect { + class Pre( + val address: String, + val port: Int, + val listener: PacketListener, + val intent: ConnectionIntent, + ) : ConnectionEvent() + + class Handshake( + val protocolVersion: Int, + val address: String, + val port: Int, + val intent: ConnectionIntent, + ) : ConnectionEvent() + + sealed class Login : ConnectionEvent() { + class Hello( + val name: String, + val uuid: UUID, + ) : ConnectionEvent() + + class Key( + val secretKey: SecretKey, + val publicKey: PublicKey, + val nonce: ByteArray, + ) : ConnectionEvent() + } + + class Post( + val profile: GameProfile, + ) : ConnectionEvent() + } class Disconnect(val reason: Text) : ConnectionEvent() -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 5386a18f8..338cad7b9 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -2,12 +2,13 @@ package com.lambda.module.modules.client import com.lambda.Lambda import com.lambda.Lambda.mc -import com.lambda.event.EventFlow import com.lambda.event.EventFlow.ioScope +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown -import com.lambda.threading.runConcurrent import com.lambda.util.Communication.info import com.lambda.util.Communication.toast import com.lambda.util.Nameable @@ -28,6 +29,10 @@ import dev.cbyrne.kdiscordipc.data.activity.* import dev.cbyrne.kdiscordipc.data.user.User import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import net.minecraft.network.NetworkState +import net.minecraft.network.encryption.NetworkEncryptionUtils +import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket +import java.math.BigInteger import java.util.* object DiscordRPC : Module( @@ -57,9 +62,12 @@ object DiscordRPC : Module( private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) private val startup = System.currentTimeMillis() + private var lastInviter: User? = null private var lastInvite: ActivityInviteEventData? = null + private var serverId: String? = null + private enum class LineInfo(val value: () -> String) : Nameable { VERSION({ Lambda.VERSION }), WORLD({ @@ -86,6 +94,19 @@ object DiscordRPC : Module( } } + unsafeListener { + if (it.packet !is LoginHelloS2CPacket) return@unsafeListener + serverId = it.packet.serverId + } + + unsafeListener(Int.MAX_VALUE) { + val hash = BigInteger( + NetworkEncryptionUtils.computeServerId(serverId ?: return@unsafeListener, it.publicKey, it.secretKey) + ).toString(16) + + it.secretKey.destroy() // Destroy the secret key after use + } + onEnableUnsafe { connect() } diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index fe939e5ac..0bb398d12 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -4,7 +4,6 @@ "package": "com.lambda.mixin", "compatibilityLevel": "JAVA_17", "client": [ - "ClientConnectionMixin", "MinecraftClientMixin", "entity.ClientPlayerEntityMixin", "entity.EntityMixin", @@ -13,6 +12,11 @@ "input.KeyBindingMixin", "input.KeyboardMixin", "input.MouseMixin", + "network.ClientConnectionMixin", + "network.ClientLoginNetworkMixin", + "network.HandshakeC2SPacketMixin", + "network.LoginHelloC2SPacketMixin", + "network.LoginKeyC2SPacketMixin", "render.CameraMixin", "render.ChatInputSuggestorMixin", "render.ChatScreenMixin", From b075f0c76169de312dd773b3f623ae4846b0198a Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 30 Mar 2024 13:35:21 -0400 Subject: [PATCH 17/56] Added kdoc --- .../lambda/event/events/ConnectionEvent.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt index 62748076b..fbebb972b 100644 --- a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt @@ -9,8 +9,23 @@ import java.security.PublicKey import java.util.UUID import javax.crypto.SecretKey +/** + * Sealed class representing connection events. + */ sealed class ConnectionEvent : Event { + + /** + * Sealed class representing various stages of connection establishment. + */ sealed class Connect { + + /** + * Event representing a pre-connection attempt. + * @property address The address of the connection attempt. + * @property port The port of the connection attempt. + * @property listener The packet listener associated with the connection. + * @property intent The connection intent. + */ class Pre( val address: String, val port: Int, @@ -18,6 +33,13 @@ sealed class ConnectionEvent : Event { val intent: ConnectionIntent, ) : ConnectionEvent() + /** + * Event representing a handshake during connection. + * @property protocolVersion The protocol version of the connection. + * @property address The address of the connection attempt. + * @property port The port of the connection attempt. + * @property intent The connection intent. + */ class Handshake( val protocolVersion: Int, val address: String, @@ -25,12 +47,33 @@ sealed class ConnectionEvent : Event { val intent: ConnectionIntent, ) : ConnectionEvent() + /** + * Sealed class representing login-related connection events. + */ sealed class Login : ConnectionEvent() { + + /** + * Event representing a hello message during login. + * @property name The name associated with the login. + * @property uuid The UUID associated with the login. + */ class Hello( val name: String, val uuid: UUID, ) : ConnectionEvent() + /** + * Event representing the exchange of cryptographic keys during login. + * @property secretKey The secret key exchanged during login. + * @property publicKey The public key exchanged during login. + * @property nonce The nonce associated with the login. + * + * The secret key MUST ABSOLUTELY be destroyed after use to prevent memory leaks and security vulnerabilities. + * This can be done by calling the `destroy()` method on the secret key object. + * We are NOT responsible for any security incidents that may occur due to improper handling of cryptographic keys. + * The worse case scenario is that someone creates a malicious plugin that steals your secret key and uses it to decrypt your traffic. + * It cannot be used for stealing accounts, but you may still want to keep it secret. + */ class Key( val secretKey: SecretKey, val publicKey: PublicKey, @@ -38,10 +81,18 @@ sealed class ConnectionEvent : Event { ) : ConnectionEvent() } + /** + * Event representing post-connection actions. + * @property profile The game profile associated with the connection. + */ class Post( val profile: GameProfile, ) : ConnectionEvent() } + /** + * Event representing a disconnection. + * @property reason The reason for disconnection. + */ class Disconnect(val reason: Text) : ConnectionEvent() } From 57d308041330e9715252fae2312c35b0f3a63269 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 30 Mar 2024 13:44:49 -0400 Subject: [PATCH 18/56] Feature: Minimal http wrapper --- .../main/kotlin/com/lambda/http/Extensions.kt | 15 +++ .../src/main/kotlin/com/lambda/http/Method.kt | 17 ++++ .../main/kotlin/com/lambda/http/Request.kt | 92 +++++++++++++++++++ .../main/kotlin/com/lambda/http/Response.kt | 30 ++++++ 4 files changed, 154 insertions(+) create mode 100644 common/src/main/kotlin/com/lambda/http/Extensions.kt create mode 100644 common/src/main/kotlin/com/lambda/http/Method.kt create mode 100644 common/src/main/kotlin/com/lambda/http/Request.kt create mode 100644 common/src/main/kotlin/com/lambda/http/Response.kt diff --git a/common/src/main/kotlin/com/lambda/http/Extensions.kt b/common/src/main/kotlin/com/lambda/http/Extensions.kt new file mode 100644 index 000000000..f7be6dd0b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/Extensions.kt @@ -0,0 +1,15 @@ +package com.lambda.http + +import java.net.URLEncoder + +/** + * Extension property to convert a map to a URL query string. + */ +val Map.query: String get() { + return this.map { (key, value) -> "$key=${value.urlEncoded}" }.joinToString("&") +} + +/** + * Extension property to URL encode a string. + */ +val Any.urlEncoded: String get() = URLEncoder.encode(toString(), "UTF-8") diff --git a/common/src/main/kotlin/com/lambda/http/Method.kt b/common/src/main/kotlin/com/lambda/http/Method.kt new file mode 100644 index 000000000..c3b4d3e89 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/Method.kt @@ -0,0 +1,17 @@ +package com.lambda.http + +/** + * Enum representing HTTP methods. + * + * @property value The string representation of the HTTP method. + */ +enum class Method(val value: String) { + GET("GET"), + HEAD("HEAD"), + POST("POST"), + PUT("PUT"), + DELETE("DELETE"), + OPTIONS("OPTIONS"), + TRACE("TRACE"), + PATCH("PATCH") +} diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt new file mode 100644 index 000000000..f836484de --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -0,0 +1,92 @@ +package com.lambda.http + +import com.lambda.Lambda +import com.lambda.event.EventFlow +import com.lambda.threading.runConcurrent +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLEncoder + +/** + * Represents an HTTP request handler that allows making various HTTP requests. + * + * @property url The URL to which the request will be made. + * @property method The HTTP method to be used for the request. Default is [Method.GET]. + * @property parameters A map of query parameters to be included in the request. Default is an empty map. + * @property headers A map of headers to be included in the request. Default is an empty map. + * @property config A lambda function to configure the HTTP connection. Default is an empty lambda. + */ +class Request( + private val url: String, + private val method: Method = Method.GET, + private val parameters: Map = mapOf(), + private val headers: Map = mapOf(), + private val config: ((HttpURLConnection) -> Unit) = {}, +) { + + /** + * Executes the HTTP request synchronously. + * + * @param completion A callback function to handle the response asynchronously. + */ + fun doRequest(completion: (Response) -> Unit) { + runCatching { + val url = URL( + if (parameters.isNotEmpty()) "$url?${parameters.query}" + else url + ) + + val connection = url.openConnection() as HttpURLConnection + config.invoke(connection) + connection.requestMethod = method.value + headers.forEach { (key, value) -> connection.setRequestProperty(key, value) } + + if (method == Method.POST || method == Method.PUT) { + connection.doOutput = true + connection.outputStream.use { + it.write(parameters.query.toByteArray()) + } + } + + val response = Response( + connection = connection, + body = connection.inputStream.bufferedReader() + ) + + connection.disconnect() + completion(response) + + response + }.recoverCatching { + val response = Response(exception = it) + completion(response) + } + } + + /** + * Executes an HTTP GET request and parses the response as JSON. + * + * @param T The type of the expected JSON response. + * @param completion A callback function to handle the parsed JSON response. + */ + inline fun getJson(crossinline completion: (T, Response) -> Unit) { + doRequest { + val result = Lambda.gson.fromJson(it.body, T::class.java) + completion(result, it) + } + } + + /** + * Executes an HTTP request asynchronously and parses the response as JSON + * + * @param T The type of the expected JSON response. + * @param completion A callback function to handle the parsed JSON response. + * @return A [Job] representing the asynchronous operation. + */ + inline fun getJsonAsync(crossinline completion: (T?, Response) -> Unit) = + EventFlow.ioScope.launch { + getJson(completion) + } +} diff --git a/common/src/main/kotlin/com/lambda/http/Response.kt b/common/src/main/kotlin/com/lambda/http/Response.kt new file mode 100644 index 000000000..56306ab2c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/Response.kt @@ -0,0 +1,30 @@ +package com.lambda.http + +import java.io.BufferedReader +import java.net.HttpURLConnection + +/** + * Represents an HTTP response. + */ +class Response( + /** + * The HTTP connection associated with the response. + */ + var connection: HttpURLConnection? = null, + + /** + * The buffered reader for reading the response body. + */ + var body: BufferedReader? = null, + + /** + * The exception that occurred during the request, if any. + */ + var exception: Throwable? = null, +) { + /** + * Indicates whether the request was successful (HTTP status code 2xx). + */ + val success: Boolean + get() = connection?.let { return it.responseCode in 200..299 } ?: false +} From ee60145acc62fddbc7694f9c9c9d91b60733b1c5 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 30 Mar 2024 13:45:34 -0400 Subject: [PATCH 19/56] Fixed comment --- common/src/main/kotlin/com/lambda/http/Request.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index f836484de..edf4ae01b 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -66,7 +66,7 @@ class Request( } /** - * Executes an HTTP GET request and parses the response as JSON. + * Executes an HTTP request synchronously and parses the response as JSON. * * @param T The type of the expected JSON response. * @param completion A callback function to handle the parsed JSON response. From 535b891755a8ef3278dfac6cefb189c8bcf01b67 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 30 Mar 2024 16:23:42 -0400 Subject: [PATCH 20/56] Feature: RPC http request --- .../lambda/event/events/ConnectionEvent.kt | 1 - .../main/kotlin/com/lambda/http/Request.kt | 33 +---- .../http/openapi/rpc/v1/endpoints/Login.kt | 22 +++ .../openapi/rpc/v1/models/Authentication.kt | 8 ++ .../http/openapi/rpc/v1/models/DiscordUser.kt | 7 + .../openapi/rpc/v1/models/MinecraftPlayer.kt | 6 + .../http/openapi/rpc/v1/models/Party.kt | 7 + .../http/openapi/rpc/v1/models/Player.kt | 6 + .../module/modules/client/DiscordRPC.kt | 132 ++++++++++-------- 9 files changed, 134 insertions(+), 88 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt create mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt create mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt create mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt create mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt create mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt diff --git a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt index fbebb972b..75f392cb0 100644 --- a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt @@ -71,7 +71,6 @@ sealed class ConnectionEvent : Event { * The secret key MUST ABSOLUTELY be destroyed after use to prevent memory leaks and security vulnerabilities. * This can be done by calling the `destroy()` method on the secret key object. * We are NOT responsible for any security incidents that may occur due to improper handling of cryptographic keys. - * The worse case scenario is that someone creates a malicious plugin that steals your secret key and uses it to decrypt your traffic. * It cannot be used for stealing accounts, but you may still want to keep it secret. */ class Key( diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index edf4ae01b..6c3eb7f28 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -31,7 +31,7 @@ class Request( * * @param completion A callback function to handle the response asynchronously. */ - fun doRequest(completion: (Response) -> Unit) { + fun doRequest(): Response = runCatching { val url = URL( if (parameters.isNotEmpty()) "$url?${parameters.query}" @@ -56,37 +56,16 @@ class Request( ) connection.disconnect() - completion(response) - - response - }.recoverCatching { - val response = Response(exception = it) - completion(response) + return response + }.getOrElse { + return Response(exception = it) } - } /** * Executes an HTTP request synchronously and parses the response as JSON. * * @param T The type of the expected JSON response. - * @param completion A callback function to handle the parsed JSON response. - */ - inline fun getJson(crossinline completion: (T, Response) -> Unit) { - doRequest { - val result = Lambda.gson.fromJson(it.body, T::class.java) - completion(result, it) - } - } - - /** - * Executes an HTTP request asynchronously and parses the response as JSON - * - * @param T The type of the expected JSON response. - * @param completion A callback function to handle the parsed JSON response. - * @return A [Job] representing the asynchronous operation. */ - inline fun getJsonAsync(crossinline completion: (T?, Response) -> Unit) = - EventFlow.ioScope.launch { - getJson(completion) - } + inline fun json(): T? = + doRequest().body?.let { Lambda.gson.fromJson(it, T::class.java) } } diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt new file mode 100644 index 000000000..26218eb52 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt @@ -0,0 +1,22 @@ +package com.lambda.http.openapi.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.openapi.rpc.v1.models.Authentication + +fun login( + endpoint: String, + version: String, + accessToken: String, + username: String, + hash: String +) = + Request( + "$endpoint/api/$version/party/login", + Method.POST, + mapOf( + "token" to "Bearer $accessToken", + "username" to username, + "hash" to hash + ) + ).json() diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt new file mode 100644 index 000000000..c187cf742 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt @@ -0,0 +1,8 @@ +package com.lambda.http.openapi.rpc.v1.models + +data class Authentication( + val accessToken: String, + val expiresIn: Long, + val tokenType: String, + val message: String, +) diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt new file mode 100644 index 000000000..8f3fdfab4 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt @@ -0,0 +1,7 @@ +package com.lambda.http.openapi.rpc.v1.models + +data class DiscordUser( + val id: Int, + val username: String, + val globalName: String, +) diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt new file mode 100644 index 000000000..ffddba557 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt @@ -0,0 +1,6 @@ +package com.lambda.http.openapi.rpc.v1.models + +data class MinecraftPlayer( + val name: String, + val id: String, +) diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt new file mode 100644 index 000000000..572fb18b8 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt @@ -0,0 +1,7 @@ +package com.lambda.http.openapi.rpc.v1.models + +data class Party( + val id: String, + val creation: String, + val players: List, +) diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt new file mode 100644 index 000000000..bf3d0d303 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt @@ -0,0 +1,6 @@ +package com.lambda.http.openapi.rpc.v1.models + +data class Player ( + val player: MinecraftPlayer, + val discord: DiscordUser, +) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 338cad7b9..f0f2d98d2 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -6,9 +6,15 @@ import com.lambda.event.EventFlow.ioScope import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.openapi.rpc.v1.endpoints.login +import com.lambda.http.openapi.rpc.v1.models.Authentication +import com.lambda.http.openapi.rpc.v1.models.Party import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown +import com.lambda.threading.runConcurrent import com.lambda.util.Communication.info import com.lambda.util.Communication.toast import com.lambda.util.Nameable @@ -24,12 +30,13 @@ import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent import dev.cbyrne.kdiscordipc.core.event.impl.ErrorEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent +import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.AuthenticatePacket import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.SetActivityPacket import dev.cbyrne.kdiscordipc.data.activity.* import dev.cbyrne.kdiscordipc.data.user.User +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import net.minecraft.network.NetworkState import net.minecraft.network.encryption.NetworkEncryptionUtils import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket import java.math.BigInteger @@ -39,35 +46,41 @@ object DiscordRPC : Module( name = "DiscordRPC", description = "Discord Rich Presence configuration", defaultTags = setOf(ModuleTag.CLIENT), - //enabledByDefault = true, // ToDo: Enable before release ) { - private var joinSecret by setting("Join Secret", UUID.randomUUID().toString()) // TODO: Server to generate this - private var partyId by setting("Party ID", UUID.randomUUID().toString()) - private var partySize by setting("Party Size", 1, 1..16, 1) - private var partyMax by setting("Party Max Size", 16, 2..16, 1) - - private val confirmCoordinates by setting("Show Coordinates", false) - private val confirmServer by setting("Expose server", false, description = "Allow to show what server you are on and allow to join parties.") - private val enableParty by setting("Enable Party", true, description = "Will allow you to create and join parties but cannot have buttons.") - private val showTime by setting("Show Time", true, description = "Show how long you have been playing for.") + private var rpcServer by setting("RPC Server", "http://127.0.0.1:8080") + private var apiVersion by setting("API Version", ApiVersion.V1) private val line1Left by setting("Line 1 Left", LineInfo.WORLD) private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) private val line2Right by setting("Line 2 Right", LineInfo.FPS) - // Max update delay is 5 per 20 seconds -> every 4 seconds + private val confirmCoordinates by setting("Show Coordinates", false) + private val confirmServer by setting("Expose server", false, description = "Allow to show what server you are on and allow to join parties.") + private val enableParty by setting("Enable Party", true, description = "Will allow you to create and join parties but cannot have buttons.") + private val showTime by setting("Show Time", true, description = "Show how long you have been playing for.") + private val delay by setting("Update Delay", 4, 4..60, 1, unit = "s") private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) private val startup = System.currentTimeMillis() + private var discordAuth: AuthenticatePacket.Data? = null + private var rpcAuth: Authentication? = null + private var currentParty: Party? = null + private var lastInviter: User? = null private var lastInvite: ActivityInviteEventData? = null private var serverId: String? = null + /** + * Check if the player can create parties + */ + private val allowed: Boolean + get() = rpcAuth != null && discordAuth != null && rpc.connected + private enum class LineInfo(val value: () -> String) : Nameable { VERSION({ Lambda.VERSION }), WORLD({ @@ -84,79 +97,79 @@ object DiscordRPC : Module( FPS({ "${mc.currentFps} FPS" }), } - init { - ioScope.launch { - rpc.register() - - while (true) { - if (rpc.connected) update() - delay(delay * 1000L) - } - } + private enum class ApiVersion(val value: String) { + // We can use @Deprecated("Not supported") to remove the old API version in the future + V1("v1"), + } + init { unsafeListener { if (it.packet !is LoginHelloS2CPacket) return@unsafeListener serverId = it.packet.serverId } + // Will not work in single player unsafeListener(Int.MAX_VALUE) { val hash = BigInteger( NetworkEncryptionUtils.computeServerId(serverId ?: return@unsafeListener, it.publicKey, it.secretKey) ).toString(16) + serverId = null it.secretKey.destroy() // Destroy the secret key after use + + runConcurrent { + discordAuth = rpc.applicationManager.authenticate() // We only have access to the basic user info, no email, no password + rpcAuth = login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) + } } onEnableUnsafe { - connect() - } + ioScope.launch { + rpc.register() + connect() - onDisableUnsafe { - disconnect() + while (true) { + if (rpc.connected) update() else cancel() + delay(delay * 1000L) + } + } } - onShutdown { - disconnect() - } + onDisableUnsafe(::disconnect) + + onShutdown(::disconnect) } - private fun connect() { - ioScope.launch { - if (!rpc.connected) { - Lambda.LOG.info("Connecting to Discord RPC.") - rpc.connect() - } + private suspend fun connect() { + if (!rpc.connected) { + Lambda.LOG.info("Connecting to Discord RPC.") + rpc.connect() } } private fun disconnect() { - ioScope.launch { - if (rpc.connected) { - Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") - rpc.disconnect() - } + if (rpc.connected) { + Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") + rpc.disconnect() } } - private fun update() { - ioScope.launch { - rpc.activityManager.setActivity { - details = "${line1Left.value()} ${line1Right.value()}".take(128) - state = "${line2Left.value()} ${line2Right.value()}".take(128) - - largeImage("lambda", Lambda.VERSION) - smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - - if (enableParty && false) { // TODO: Implement parties - // TODO: Only enable if the player is in a server - party(partyId, partySize, partyMax) - secrets(joinSecret) - } else { - button("Download", "https://modrinth.com/") // ToDo: Add real link - } + private suspend fun update() { + rpc.activityManager.setActivity { + details = "${line1Left.value()} ${line1Right.value()}".take(128) + state = "${line2Left.value()} ${line2Right.value()}".take(128) + + largeImage("lambda", Lambda.VERSION) + smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - if (showTime) timestamps(startup) + if (enableParty && allowed) { + /*party(partyId, currentParty?.players?.size ?: 1, 16) + secrets(joinSecret)*/ + } else { + button("Download", "https://modrinth.com/") // ToDo: Add real link } + + if (showTime) timestamps(startup) } } @@ -176,21 +189,20 @@ object DiscordRPC : Module( clickEvent(ClickEvents.runCommand(";rpc accept")) { literal("Click to join ${data.user.username}'s party.") } - }) // TODO: Possibility to block users + }) toast("You have been invited to play by ${lastInviter?.username}") // TODO: Custom toast ? lastInvite = data } on { - joinSecret = data.secret info("Joined ${lastInviter?.username}'s party.") } on { - partyId = data?.party?.id ?: partyId + /*partyId = data?.party?.id ?: partyId partySize = data?.party?.size?.currentSize ?: partySize - partyMax = data?.party?.size?.maxSize ?: partyMax + partyMax = data?.party?.size?.maxSize ?: partyMax*/ } on { From af2c2c17313b078038bb2e99399ba8a0c9fc240a Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 12 Apr 2024 23:28:56 -0400 Subject: [PATCH 21/56] Fix: Forge doesn't support ModifyArgs --- .../mixin/render/LightmapTextureManagerMixin.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java b/common/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java index 3761a0417..f4bfb50cb 100644 --- a/common/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java @@ -7,17 +7,17 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.asm.mixin.injection.invoke.arg.Args; @Mixin(LightmapTextureManager.class) public class LightmapTextureManagerMixin { - @ModifyArgs(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/NativeImage;setColor(III)V")) - private void updateModify(Args args) { + @ModifyArg(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/NativeImage;setColor(III)V"), index = 2) + private int updateModify(int x) { if (Fullbright.INSTANCE.isEnabled() || XRay.INSTANCE.isEnabled()) { - args.set(2, 0xFFFFFFFF); + return 0xFFFFFFFF; } + return x; } @Inject(method = "getDarknessFactor(F)F", at = @At("HEAD"), cancellable = true) From dd52500fbeaa8b4bb1bb5ffadd1cf49e0d008edb Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 12 Apr 2024 23:33:20 -0400 Subject: [PATCH 22/56] Fix: Missing import --- common/src/main/kotlin/com/lambda/config/Configuration.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/kotlin/com/lambda/config/Configuration.kt b/common/src/main/kotlin/com/lambda/config/Configuration.kt index dfa82bf99..785706c15 100644 --- a/common/src/main/kotlin/com/lambda/config/Configuration.kt +++ b/common/src/main/kotlin/com/lambda/config/Configuration.kt @@ -6,6 +6,7 @@ import com.google.gson.JsonParser import com.lambda.Lambda.LOG import com.lambda.Lambda.gson import com.lambda.config.configurations.ModuleConfig +import com.lambda.event.EventFlow.ioScope import com.lambda.event.EventFlow.lambdaScope import com.lambda.event.events.ClientEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener From eab5b12910978fd76a5d40c7f1fafa8ab574c787 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 12 Apr 2024 23:33:44 -0400 Subject: [PATCH 23/56] Disable transitive include --- forge/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index bdaa5bc21..73f66f6fe 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -13,7 +13,6 @@ base.archivesName.set("${base.archivesName.get()}-forge") loom { accessWidenerPath.set(project(":common").loom.accessWidenerPath) - forge { convertAccessWideners = true extraAccessWideners.add(loom.accessWidenerPath.get().asFile.name) @@ -55,7 +54,7 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") - includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") { isTransitive = false } // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") From 8cdb410a0034182bdf2db3bca17a6d107b1e34cc Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 14 Apr 2024 20:27:06 -0400 Subject: [PATCH 24/56] Fix: Quilt --- forge/build.gradle.kts | 7 ++++++- neoforge/build.gradle.kts | 2 ++ quilt/build.gradle.kts | 13 ++++--------- .../main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt | 11 +++++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 837c4bc89..72e5cdc44 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -42,11 +42,13 @@ val shadowBundle: Configuration by configurations.creating { fun DependencyHandlerScope.setupConfigurations() { includeLib.dependencies.forEach { forgeRuntimeLibrary(it) + implementation(it) include(it) } includeMod.dependencies.forEach { forgeRuntimeLibrary(it) + implementation(it) include(it) } @@ -63,7 +65,10 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") - includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") { isTransitive = false } + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") { + exclude(group = "org.jetbrains.kotlin") + exclude(group = "org.jetbrains.kotlinx") + } // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index d4c346db5..4a3184bd3 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -39,11 +39,13 @@ val shadowBundle: Configuration by configurations.creating { fun DependencyHandlerScope.setupConfigurations() { includeLib.dependencies.forEach { + forgeRuntimeLibrary(it) implementation(it) include(it) } includeMod.dependencies.forEach { + forgeRuntimeLibrary(it) implementation(it) include(it) } diff --git a/quilt/build.gradle.kts b/quilt/build.gradle.kts index df119a576..f6b239009 100644 --- a/quilt/build.gradle.kts +++ b/quilt/build.gradle.kts @@ -1,7 +1,7 @@ val quiltVersion = property("quilt_version").toString() val quiltedFabricVersion = property("quilted_fabric_version").toString() val kotlinQuiltVersion = property("kotlin_quilt_version").toString() -val kotlinxCoroutineVersion = property("kotlinx_coroutines_version").toString() +val discordIPCVersion = property("discord_ipc_version").toString() base.archivesName.set("${base.archivesName.get()}-quilt") @@ -52,19 +52,14 @@ dependencies { // Quilt Loader modImplementation("org.quiltmc:quilt-loader:$quiltVersion") - // Quilted Fabric API - modImplementation("org.quiltmc.quilted-fabric-api:quilted-fabric-api:$quiltedFabricVersion") - // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar - includeMod("org.quiltmc.quilt-kotlin-libraries:quilt-kotlin-libraries:$kotlinQuiltVersion") { - // Exclude fabric - exclude("net.fabricmc") - exclude("net.fabricmc.fabric-api") - } + includeMod("org.quiltmc.quilted-fabric-api:quilted-fabric-api:$quiltedFabricVersion") + includeMod("org.quiltmc.quilt-kotlin-libraries:quilt-kotlin-libraries:$kotlinQuiltVersion") // Common (Do not touch) diff --git a/quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt b/quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt new file mode 100644 index 000000000..a016b491d --- /dev/null +++ b/quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt @@ -0,0 +1,11 @@ +package com.lambda.fabric + +import net.fabricmc.loader.api.FabricLoader + +object LoaderInfoImpl { + @JvmStatic + fun getVersion(): String = + FabricLoader.getInstance() + .getModContainer("lambda").orElseThrow() + .metadata.version.friendlyString +} \ No newline at end of file From ffcf4f67426c7194275f0c5d17d3061c90cfc00a Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 27 Apr 2024 18:39:38 -0400 Subject: [PATCH 25/56] Fix: thedarkcolour/KotlinForForge/94 --- forge/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 72e5cdc44..ca1fc2a84 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -65,9 +65,13 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") - includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") { + + // Temporary, only works for production + // See https://github.com/MinecraftForge/MinecraftForge/issues/8878 + shadowBundle("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") { exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlinx") + exclude(group = "org.slf4j") } // Add mods to the mod jar From 0e4a159c8dd70988e9a977b02ff31e2ba11bde19 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sat, 27 Apr 2024 20:03:44 -0400 Subject: [PATCH 26/56] Added all current methods --- .../main/kotlin/com/lambda/http/Request.kt | 2 -- .../http/api/rpc/v1/endpoints/CreateParty.kt | 19 +++++++++++++++ .../http/api/rpc/v1/endpoints/JoinParty.kt | 24 +++++++++++++++++++ .../lambda/http/api/rpc/v1/endpoints/Login.kt | 23 ++++++++++++++++++ .../rpc/v1/models/Authentication.kt | 2 +- .../rpc/v1/models/DiscordUser.kt | 2 +- .../rpc/v1/models/MinecraftPlayer.kt | 2 +- .../{openapi => api}/rpc/v1/models/Party.kt | 2 +- .../{openapi => api}/rpc/v1/models/Player.kt | 2 +- .../http/openapi/rpc/v1/endpoints/Login.kt | 22 ----------------- .../module/modules/client/DiscordRPC.kt | 6 ++--- 11 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt rename common/src/main/kotlin/com/lambda/http/{openapi => api}/rpc/v1/models/Authentication.kt (74%) rename common/src/main/kotlin/com/lambda/http/{openapi => api}/rpc/v1/models/DiscordUser.kt (68%) rename common/src/main/kotlin/com/lambda/http/{openapi => api}/rpc/v1/models/MinecraftPlayer.kt (61%) rename common/src/main/kotlin/com/lambda/http/{openapi => api}/rpc/v1/models/Party.kt (68%) rename common/src/main/kotlin/com/lambda/http/{openapi => api}/rpc/v1/models/Player.kt (65%) delete mode 100644 common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index 6c3eb7f28..ba854d239 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -28,8 +28,6 @@ class Request( /** * Executes the HTTP request synchronously. - * - * @param completion A callback function to handle the response asynchronously. */ fun doRequest(): Response = runCatching { diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt new file mode 100644 index 000000000..1b14abda6 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt @@ -0,0 +1,19 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.models.Party + +fun createParty( + endpoint: String, + version: String, + accessToken: String, +) = + Request( + "$endpoint/api/$version/party/create", + Method.POST, + headers = + mapOf( + "Authorization" to "Bearer $accessToken" + ) + ).json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt new file mode 100644 index 000000000..85a30a6f7 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt @@ -0,0 +1,24 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Request +import com.lambda.http.Method +import com.lambda.http.api.rpc.v1.models.Party + +fun joinParty( + endpoint: String, + version: String, + accessToken: String, + partyId: String, +) = + Request( + "$endpoint/api/$version/party/join", + Method.PUT, + parameters = + mapOf( + "partyId" to partyId + ), + headers = + mapOf( + "Authorization" to "Bearer $accessToken" + ) + ).json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt new file mode 100644 index 000000000..2a50c08d5 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt @@ -0,0 +1,23 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.models.Authentication + +fun login( + endpoint: String, + version: String, + discordToken: String, + username: String, + hash: String +) = + Request( + "$endpoint/api/$version/party/login", + Method.POST, + parameters = + mapOf( + "token" to "Bearer $discordToken", + "username" to username, + "hash" to hash + ) + ).json() diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt similarity index 74% rename from common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt rename to common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt index c187cf742..eb21bb43e 100644 --- a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Authentication.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt @@ -1,4 +1,4 @@ -package com.lambda.http.openapi.rpc.v1.models +package com.lambda.http.api.rpc.v1.models data class Authentication( val accessToken: String, diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt similarity index 68% rename from common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt rename to common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt index 8f3fdfab4..a9b259bb0 100644 --- a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/DiscordUser.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt @@ -1,4 +1,4 @@ -package com.lambda.http.openapi.rpc.v1.models +package com.lambda.http.api.rpc.v1.models data class DiscordUser( val id: Int, diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt similarity index 61% rename from common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt rename to common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt index ffddba557..57888627f 100644 --- a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/MinecraftPlayer.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt @@ -1,4 +1,4 @@ -package com.lambda.http.openapi.rpc.v1.models +package com.lambda.http.api.rpc.v1.models data class MinecraftPlayer( val name: String, diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt similarity index 68% rename from common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt rename to common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt index 572fb18b8..6ba8a8856 100644 --- a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Party.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt @@ -1,4 +1,4 @@ -package com.lambda.http.openapi.rpc.v1.models +package com.lambda.http.api.rpc.v1.models data class Party( val id: String, diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt similarity index 65% rename from common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt rename to common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt index bf3d0d303..ba9a1cd95 100644 --- a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/models/Player.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt @@ -1,4 +1,4 @@ -package com.lambda.http.openapi.rpc.v1.models +package com.lambda.http.api.rpc.v1.models data class Player ( val player: MinecraftPlayer, diff --git a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt deleted file mode 100644 index 26218eb52..000000000 --- a/common/src/main/kotlin/com/lambda/http/openapi/rpc/v1/endpoints/Login.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.lambda.http.openapi.rpc.v1.endpoints - -import com.lambda.http.Method -import com.lambda.http.Request -import com.lambda.http.openapi.rpc.v1.models.Authentication - -fun login( - endpoint: String, - version: String, - accessToken: String, - username: String, - hash: String -) = - Request( - "$endpoint/api/$version/party/login", - Method.POST, - mapOf( - "token" to "Bearer $accessToken", - "username" to username, - "hash" to hash - ) - ).json() diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index f0f2d98d2..565ce5e56 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -8,9 +8,9 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener import com.lambda.http.Method import com.lambda.http.Request -import com.lambda.http.openapi.rpc.v1.endpoints.login -import com.lambda.http.openapi.rpc.v1.models.Authentication -import com.lambda.http.openapi.rpc.v1.models.Party +import com.lambda.http.api.rpc.v1.endpoints.login +import com.lambda.http.api.rpc.v1.models.Authentication +import com.lambda.http.api.rpc.v1.models.Party import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown From 034655297025e33c203299da05167343b1c82975 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:16:19 -0400 Subject: [PATCH 27/56] Refactor!: Up to date with server --- .../http/api/rpc/v1/models/Authentication.kt | 10 +++++++++- .../lambda/http/api/rpc/v1/models/DiscordUser.kt | 7 ------- .../http/api/rpc/v1/models/MinecraftPlayer.kt | 6 ------ .../com/lambda/http/api/rpc/v1/models/Player.kt | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 16 deletions(-) delete mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt delete mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt index eb21bb43e..1f2c01ba8 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt @@ -1,8 +1,16 @@ package com.lambda.http.api.rpc.v1.models +// Authentication represents the response from the authentication endpoint. data class Authentication( + // The access token to use for the API + // example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c val accessToken: String, + + // The duration of the token (in seconds). + // example: 3600 val expiresIn: Long, + + // The type of the token. + // example: Bearer val tokenType: String, - val message: String, ) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt deleted file mode 100644 index a9b259bb0..000000000 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/DiscordUser.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.lambda.http.api.rpc.v1.models - -data class DiscordUser( - val id: Int, - val username: String, - val globalName: String, -) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt deleted file mode 100644 index 57888627f..000000000 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/MinecraftPlayer.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.lambda.http.api.rpc.v1.models - -data class MinecraftPlayer( - val name: String, - val id: String, -) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt index ba9a1cd95..821757bc3 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt @@ -1,6 +1,16 @@ package com.lambda.http.api.rpc.v1.models +// Represents a player in a party. data class Player ( - val player: MinecraftPlayer, - val discord: DiscordUser, + // The player's name. + // example: Notch + val name: String, + + // The player's UUID. + // example: 069a79f4-44e9-4726-a5be-fca90e38aaf5 + val uuid: String, + + // The player's Discord ID. + // example: "385441179069579265" + val discordId: String, ) From a9f875e27667d7ff48c600d0a3dfbae9a68006cd Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:06:07 -0400 Subject: [PATCH 28/56] Working RPC system --- .../mixin/network/LoginKeyC2SPacketMixin.java | 2 + .../lambda/event/events/ConnectionEvent.kt | 5 +- .../main/kotlin/com/lambda/http/Request.kt | 57 +++-- .../http/api/rpc/v1/endpoints/CreateParty.kt | 19 ++ .../http/api/rpc/v1/endpoints/JoinParty.kt | 5 +- .../lambda/http/api/rpc/v1/endpoints/Login.kt | 9 + .../http/api/rpc/v1/endpoints/UpdateParty.kt | 37 +++ .../http/api/rpc/v1/models/Authentication.kt | 1 - .../lambda/http/api/rpc/v1/models/Party.kt | 17 ++ .../lambda/http/api/rpc/v1/models/Player.kt | 1 - .../lambda/http/api/rpc/v1/models/Settings.kt | 16 ++ .../module/modules/client/DiscordRPC.kt | 222 ++++++++++++------ .../util/primitives/extension/PlayerEntity.kt | 11 + .../src/main/resources/lambda.accesswidener | 2 + 14 files changed, 299 insertions(+), 105 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt create mode 100644 common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt diff --git a/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java b/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java index 07bda6368..22247df80 100644 --- a/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java +++ b/common/src/main/java/com/lambda/mixin/network/LoginKeyC2SPacketMixin.java @@ -15,6 +15,8 @@ public class LoginKeyC2SPacketMixin { @Inject(method = "(Ljavax/crypto/SecretKey;Ljava/security/PublicKey;[B)V", at = @At("TAIL")) private void onLoginKeyC2SPacket(SecretKey secretKey, PublicKey publicKey, byte[] nonce, CallbackInfo ci) { + // Please note this won't work if the server is offline mode because the player doesn't + // fetch the server's public key. EventFlow.post(new ConnectionEvent.Connect.Login.Key(secretKey, publicKey, nonce)); } } diff --git a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt index 75f392cb0..5276c268b 100644 --- a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt @@ -68,10 +68,9 @@ sealed class ConnectionEvent : Event { * @property publicKey The public key exchanged during login. * @property nonce The nonce associated with the login. * - * The secret key MUST ABSOLUTELY be destroyed after use to prevent memory leaks and security vulnerabilities. + * The secret key MUST ABSOLUTELY be, if stored, destroyed after use to avoid security vulnerabilities. * This can be done by calling the `destroy()` method on the secret key object. - * We are NOT responsible for any security incidents that may occur due to improper handling of cryptographic keys. - * It cannot be used for stealing accounts, but you may still want to keep it secret. + * We are NOT responsible for any incidents that may occur due to improper handling of cryptographic keys. */ class Key( val secretKey: SecretKey, diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index ba854d239..23d0b3fdf 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -1,13 +1,8 @@ package com.lambda.http import com.lambda.Lambda -import com.lambda.event.EventFlow -import com.lambda.threading.runConcurrent -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import java.net.HttpURLConnection import java.net.URL -import java.net.URLEncoder /** * Represents an HTTP request handler that allows making various HTTP requests. @@ -25,45 +20,47 @@ class Request( private val headers: Map = mapOf(), private val config: ((HttpURLConnection) -> Unit) = {}, ) { + private val canBeEncoded: Boolean + get() = method != Method.POST && method != Method.PUT && method != Method.PATCH /** * Executes the HTTP request synchronously. */ - fun doRequest(): Response = - runCatching { - val url = URL( - if (parameters.isNotEmpty()) "$url?${parameters.query}" - else url - ) + fun doRequest(): Response { + val url = URL( + if (parameters.isNotEmpty() && canBeEncoded) "$url?${parameters.query}" + else url + ) - val connection = url.openConnection() as HttpURLConnection - config.invoke(connection) - connection.requestMethod = method.value - headers.forEach { (key, value) -> connection.setRequestProperty(key, value) } + val connection = url.openConnection() as HttpURLConnection + config.invoke(connection) - if (method == Method.POST || method == Method.PUT) { - connection.doOutput = true - connection.outputStream.use { - it.write(parameters.query.toByteArray()) - } - } + connection.requestMethod = method.value - val response = Response( - connection = connection, - body = connection.inputStream.bufferedReader() - ) + headers.forEach { (key, value) -> connection.setRequestProperty(key, Lambda.gson.toJson(value)) } - connection.disconnect() - return response - }.getOrElse { - return Response(exception = it) + if (!canBeEncoded) { + connection.doOutput = true + connection.outputStream.use { + it.write(Lambda.gson.toJson(parameters).toByteArray()) + } } + connection.connect() + + val response = Response( + connection = connection, + body = connection.inputStream.bufferedReader() + ) + + return response + } + /** * Executes an HTTP request synchronously and parses the response as JSON. * * @param T The type of the expected JSON response. */ - inline fun json(): T? = + inline fun json(): T? = doRequest().body?.let { Lambda.gson.fromJson(it, T::class.java) } } diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt index 1b14abda6..1ebab5f89 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt @@ -8,10 +8,29 @@ fun createParty( endpoint: String, version: String, accessToken: String, + + // The maximum number of players in the party. + // example: 10 + maxPlayers: Int = 10, + + // Whether the party is public or not. + // If false can only be joined by invite. + // example: true + public: Boolean = true, + + // Whether the party can be listed or not. + // example: true + listed: Boolean = true, ) = Request( "$endpoint/api/$version/party/create", Method.POST, + parameters = + mapOf( + "max_players" to maxPlayers, + "public" to public, + "listed" to listed + ), headers = mapOf( "Authorization" to "Bearer $accessToken" diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt index 85a30a6f7..1fe87d035 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt @@ -8,6 +8,9 @@ fun joinParty( endpoint: String, version: String, accessToken: String, + + // The ID of the party. + // example: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" partyId: String, ) = Request( @@ -15,7 +18,7 @@ fun joinParty( Method.PUT, parameters = mapOf( - "partyId" to partyId + "id" to partyId ), headers = mapOf( diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt index 2a50c08d5..2be70c5ee 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt @@ -7,8 +7,17 @@ import com.lambda.http.api.rpc.v1.models.Authentication fun login( endpoint: String, version: String, + + // The player's Discord token. + // example: OTk1MTU1NzcyMzYxMTQ2NDM4.AAAAAA.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB discordToken: String, + + // The player's username. + // example: "Notch" username: String, + + // The player's Mojang session hash. + // example: 069a79f444e94726a5befca90e38aaf5 hash: String ) = Request( diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt new file mode 100644 index 000000000..79587264a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt @@ -0,0 +1,37 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.models.Party + +fun editParty( + endpoint: String, + version: String, + accessToken: String, + + // The maximum number of players in the party. + // example: 10 + maxPlayers: Int = 10, + + // Whether the party is public or not. + // If false can only be joined by invite. + // example: true + public: Boolean = true, + + // Whether the party can be listed or not. + // example: true + listed: Boolean = true, +) = Request( + "$endpoint/api/$version/party/edit", + Method.PATCH, + parameters = + mapOf( + "max_players" to maxPlayers, + "public" to public, + "listed" to listed + ), + headers = + mapOf( + "Authorization" to "Bearer $accessToken" + ) + ).json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt index 1f2c01ba8..9401b55de 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt @@ -1,6 +1,5 @@ package com.lambda.http.api.rpc.v1.models -// Authentication represents the response from the authentication endpoint. data class Authentication( // The access token to use for the API // example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt index 6ba8a8856..f84cdb9f2 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt @@ -1,7 +1,24 @@ package com.lambda.http.api.rpc.v1.models data class Party( + // The ID of the party. + // It is a random string of 30 characters. val id: String, + + // The join secret of the party. + // It is a random string of 100 characters. + val joinSecret: String, + + // The leader of the party + val leader: Player, + + // The creation date of the party. + // example: 2021-10-10T12:00:00Z val creation: String, + + // The list of players in the party. val players: List, + + // The settings of the party + val settings: Settings, ) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt index 821757bc3..cda7f583f 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt @@ -1,6 +1,5 @@ package com.lambda.http.api.rpc.v1.models -// Represents a player in a party. data class Player ( // The player's name. // example: Notch diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt new file mode 100644 index 000000000..c9e638a17 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt @@ -0,0 +1,16 @@ +package com.lambda.http.api.rpc.v1.models + +data class Settings( + // The maximum number of players in the party. + // example: 10 + val maxPlayers: Int, + + // Whether the party is public or not. + // If false can only be joined by invite. + // example: true + val public: Boolean, + + // Whether the party can be listed or not. + // example: true + val listed: Boolean, +) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 565ce5e56..5ec51c8a6 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -1,13 +1,15 @@ package com.lambda.module.modules.client import com.lambda.Lambda +import com.lambda.Lambda.LOG import com.lambda.Lambda.mc import com.lambda.event.EventFlow.ioScope import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener -import com.lambda.http.Method -import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.endpoints.createParty +import com.lambda.http.api.rpc.v1.endpoints.editParty +import com.lambda.http.api.rpc.v1.endpoints.joinParty import com.lambda.http.api.rpc.v1.endpoints.login import com.lambda.http.api.rpc.v1.models.Authentication import com.lambda.http.api.rpc.v1.models.Party @@ -17,8 +19,10 @@ import com.lambda.threading.onShutdown import com.lambda.threading.runConcurrent import com.lambda.util.Communication.info import com.lambda.util.Communication.toast +import com.lambda.util.Communication.warn import com.lambda.util.Nameable import com.lambda.util.StringUtils.capitalize +import com.lambda.util.primitives.extension.isOffline import com.lambda.util.text.ClickEvents import com.lambda.util.text.buildText import com.lambda.util.text.clickEvent @@ -39,47 +43,62 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.minecraft.network.encryption.NetworkEncryptionUtils import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket +import net.minecraft.util.Uuids import java.math.BigInteger -import java.util.* +import java.util.UUID +import java.util.concurrent.atomic.AtomicReference object DiscordRPC : Module( name = "DiscordRPC", description = "Discord Rich Presence configuration", defaultTags = setOf(ModuleTag.CLIENT), ) { - - private var rpcServer by setting("RPC Server", "http://127.0.0.1:8080") - private var apiVersion by setting("API Version", ApiVersion.V1) - - private val line1Left by setting("Line 1 Left", LineInfo.WORLD) - private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) - private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) - private val line2Right by setting("Line 2 Right", LineInfo.FPS) - - private val confirmCoordinates by setting("Show Coordinates", false) - private val confirmServer by setting("Expose server", false, description = "Allow to show what server you are on and allow to join parties.") - private val enableParty by setting("Enable Party", true, description = "Will allow you to create and join parties but cannot have buttons.") - private val showTime by setting("Show Time", true, description = "Show how long you have been playing for.") - - private val delay by setting("Update Delay", 4, 4..60, 1, unit = "s") + private val page by setting("Page", Page.General) + + /* General settings */ + private val line1Left by setting("Line 1 Left", LineInfo.WORLD) { page == Page.General } + private val line1Right by setting("Line 1 Right", LineInfo.USERNAME) { page == Page.General } + private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) { page == Page.General } + private val line2Right by setting("Line 2 Right", LineInfo.FPS) { page == Page.General } + + private val confirmCoordinates by setting("Show Coordinates", false) { page == Page.General } + private val confirmServer by setting("Expose server", false, description = "Allow to show what server you are on and allow to join parties.") { page == Page.General } + private val showTime by setting("Show Time", true, description = "Show how long you have been playing for.") { page == Page.General } + + /* Technical settings */ + private var rpcServer by setting("RPC Server", "http://127.0.0.1:8080") { page == Page.Settings } // TODO: Change this in production + private var apiVersion by setting("API Version", ApiVersion.V1) { page == Page.Settings } + private val delay by setting("Update Delay", 4, 4..60, 1, unit = "ms", visibility = { page == Page.Settings }) + + /* Party settings */ + private val enableParty by setting("Enable Party", true, description = "Allows you to create parties.") { page == Page.Party } + private val createByDefault by setting("Create Party by Default", false, description = "Automatically create a party when you join a server.") { page == Page.Party && enableParty } + private val maxPlayers = setting("Max Players", 10, 2..20, visibility = { page == Page.Party }) + private val public = setting("Public Party", true, description = "Allow anyone to join your party.") { page == Page.Party } + private val listed = setting("Listed Party", true, description = "Allow your party to be listed for other players.") { page == Page.Party && public.value } private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) private val startup = System.currentTimeMillis() + private val cracked = mc.gameProfile.isOffline private var discordAuth: AuthenticatePacket.Data? = null private var rpcAuth: Authentication? = null - private var currentParty: Party? = null + private var currentParty: AtomicReference = AtomicReference(null) - private var lastInviter: User? = null private var lastInvite: ActivityInviteEventData? = null + private var connectionTime: Long = 0 private var serverId: String? = null /** - * Check if the player can create parties + * If the player can interact with the party system. */ private val allowed: Boolean - get() = rpcAuth != null && discordAuth != null && rpc.connected + get() = rpcAuth != null && discordAuth != null && !cracked && enableParty + + private enum class Page { + General, Settings, Party + } private enum class LineInfo(val value: () -> String) : Nameable { VERSION({ Lambda.VERSION }), @@ -98,73 +117,133 @@ object DiscordRPC : Module( } private enum class ApiVersion(val value: String) { - // We can use @Deprecated("Not supported") to remove the old API version in the future + // We can use @Deprecated("Not supported") to remove old API versions in the future V1("v1"), } init { + // I don't like this, can we provide a listener directly? + maxPlayers.listener { _, _ -> + if (allowed) edit() + } + + public.listener { _, _ -> + if (allowed) edit() + } + + listed.listener { _, _ -> + if (allowed) edit() + } + unsafeListener { if (it.packet !is LoginHelloS2CPacket) return@unsafeListener + connectionTime = System.currentTimeMillis() serverId = it.packet.serverId } - // Will not work in single player - unsafeListener(Int.MAX_VALUE) { - val hash = BigInteger( - NetworkEncryptionUtils.computeServerId(serverId ?: return@unsafeListener, it.publicKey, it.secretKey) - ).toString(16) - - serverId = null - it.secretKey.destroy() // Destroy the secret key after use - + // Will not work in single player or cracked servers + unsafeListener { event -> runConcurrent { - discordAuth = rpc.applicationManager.authenticate() // We only have access to the basic user info, no email, no password - rpcAuth = login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) + connect(event) } } onEnableUnsafe { - ioScope.launch { - rpc.register() + runConcurrent { connect() - - while (true) { - if (rpc.connected) update() else cancel() - delay(delay * 1000L) - } } } - onDisableUnsafe(::disconnect) - - onShutdown(::disconnect) + onDisableUnsafe { disconnect() } + onShutdown { disconnect() } } - private suspend fun connect() { + private suspend fun connect(event: ConnectionEvent.Connect.Login.Key? = null) { if (!rpc.connected) { - Lambda.LOG.info("Connecting to Discord RPC.") + rpc.register() rpc.connect() } + + if (!cracked && + (rpcAuth == null || + discordAuth == null) && + event != null) + { + if (System.currentTimeMillis() - connectionTime > 300000) { + warn("The authentication hash has expired, please reconnect to the server.") + return + } + + val hash = BigInteger(NetworkEncryptionUtils.computeServerId(serverId ?: return, event.publicKey, event.secretKey)).toString(16) + + // Prompt the user to authorize + discordAuth = rpc.applicationManager.authenticate() + rpcAuth = login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) + + if (rpcAuth != null) { + info("Successfully authenticated with the RPC server.") + if (createByDefault) create() + } else { + warn("Failed to authenticate with the RPC server.") + } + } else warn("You are using an offline account, please use a premium account to access all the RPC features.") + + loop@ while (true) { + if (rpc.connected) update() else break@loop + delay(delay * 1000L) + } } private fun disconnect() { if (rpc.connected) { - Lambda.LOG.info("Gracefully disconnecting from Discord RPC.") + LOG.info("Gracefully disconnecting from Discord RPC.") rpc.disconnect() } } + // We won't need to specify non-null variables in kotlin 2.0 + private fun join(id: String) { + if (!allowed) return + + ioScope.launch { + joinParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, id) + .also { currentParty.lazySet(it) } + } + } + + private fun create() { + if (!allowed) return + + ioScope.launch { + createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers.value, public.value, listed.value) + .also { currentParty.lazySet(it) } + } + } + + private fun edit() { + if (!allowed) return + + ioScope.launch { + currentParty.acquire?.let { + editParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers.value, public.value, listed.value) + .also { currentParty.lazySet(it) } + } + } + } + private suspend fun update() { rpc.activityManager.setActivity { - details = "${line1Left.value()} ${line1Right.value()}".take(128) - state = "${line2Left.value()} ${line2Right.value()}".take(128) + details = "${line1Left.value()} | ${line1Right.value()}".take(128) + state = "${line2Left.value()} | ${line2Right.value()}".take(128) largeImage("lambda", Lambda.VERSION) smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - if (enableParty && allowed) { - /*party(partyId, currentParty?.players?.size ?: 1, 16) - secrets(joinSecret)*/ + val party = currentParty.acquire + + if (allowed && party != null) { + party(party.id, party.players.size, party.settings.maxPlayers) + secrets(party.joinSecret) } else { button("Download", "https://modrinth.com/") // ToDo: Add real link } @@ -175,38 +254,43 @@ object DiscordRPC : Module( private suspend fun KDiscordIPC.register() { on { - Lambda.LOG.info("Discord RPC connected to ${data.user.username}.") - - subscribe(DiscordEvent.ActivityJoinRequest) - subscribe(DiscordEvent.ActivityJoin) - subscribe(DiscordEvent.ActivityInvite) + LOG.info("Discord RPC connected to ${data.user.username}.") + + if (!cracked) { + // Party features + subscribe(DiscordEvent.ActivityJoinRequest) + subscribe(DiscordEvent.ActivityJoin) + subscribe(DiscordEvent.ActivityInvite) + subscribe(DiscordEvent.LobbyUpdate) + subscribe(DiscordEvent.LobbyDelete) + subscribe(DiscordEvent.LobbyMemberConnect) + subscribe(DiscordEvent.LobbyMemberDisconnect) + subscribe(DiscordEvent.LobbyMemberUpdate) + + // QOL features + subscribe(DiscordEvent.SpeakingStart) + subscribe(DiscordEvent.SpeakingStop) + } } on { - lastInviter = data.user + lastInvite = data info(buildText { - clickEvent(ClickEvents.runCommand(";rpc accept")) { + clickEvent(ClickEvents.runCommand(";rpc accept")) { // TODO: Custom click events literal("Click to join ${data.user.username}'s party.") } }) - toast("You have been invited to play by ${lastInviter?.username}") // TODO: Custom toast ? - lastInvite = data + toast("You have been invited to play by ${lastInvite?.user?.username}") } on { - info("Joined ${lastInviter?.username}'s party.") - } - - on { - /*partyId = data?.party?.id ?: partyId - partySize = data?.party?.size?.currentSize ?: partySize - partyMax = data?.party?.size?.maxSize ?: partyMax*/ + info("Joined ${lastInvite?.user?.username}'s party.") } on { - Lambda.LOG.error("Discord RPC error: ${data.message}") + LOG.error("Discord RPC error: ${data.message}") } } } diff --git a/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt b/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt new file mode 100644 index 000000000..6106fe2c9 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt @@ -0,0 +1,11 @@ +package com.lambda.util.primitives.extension + +import com.mojang.authlib.GameProfile +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.util.Uuids + +val PlayerEntity.isOffline + get() = gameProfile.isOffline + +val GameProfile.isOffline + get() = Uuids.getOfflinePlayerUuid(name) == id diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index 2c5745564..ecb5ba8bc 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -41,6 +41,8 @@ accessible field net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPa accessible field net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket type Lnet/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractTypeHandler; accessible class net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractTypeHandler accessible class net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractAtHandler +accessible field net/minecraft/network/packet/c2s/login/LoginKeyC2SPacket encryptedSecretKey [B +accessible field net/minecraft/network/packet/c2s/login/LoginKeyC2SPacket nonce [B # Other accessible field net/minecraft/client/world/ClientEntityManager cache Lnet/minecraft/world/entity/SectionedEntityCache; From 087b0c3ff5f9abc11ba510ca036159104557fa1e Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:14:30 -0400 Subject: [PATCH 29/56] Refactor: Pseudo setting consumer --- .../module/modules/client/DiscordRPC.kt | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 5ec51c8a6..adf99038c 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -35,17 +35,12 @@ import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent import dev.cbyrne.kdiscordipc.core.event.impl.ErrorEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.AuthenticatePacket -import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.SetActivityPacket import dev.cbyrne.kdiscordipc.data.activity.* -import dev.cbyrne.kdiscordipc.data.user.User -import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.minecraft.network.encryption.NetworkEncryptionUtils import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket -import net.minecraft.util.Uuids import java.math.BigInteger -import java.util.UUID import java.util.concurrent.atomic.AtomicReference object DiscordRPC : Module( @@ -73,9 +68,9 @@ object DiscordRPC : Module( /* Party settings */ private val enableParty by setting("Enable Party", true, description = "Allows you to create parties.") { page == Page.Party } private val createByDefault by setting("Create Party by Default", false, description = "Automatically create a party when you join a server.") { page == Page.Party && enableParty } - private val maxPlayers = setting("Max Players", 10, 2..20, visibility = { page == Page.Party }) - private val public = setting("Public Party", true, description = "Allow anyone to join your party.") { page == Page.Party } - private val listed = setting("Listed Party", true, description = "Allow your party to be listed for other players.") { page == Page.Party && public.value } + private val maxPlayers by setting("Max Players", 10, 2..20, visibility = { page == Page.Party }).apply { listener { _, _ -> edit() }} + private val public by setting("Public Party", false, description = "Allow anyone to join your party.") { page == Page.Party }.apply { listener { _, _ -> edit() }} + private val listed by setting("Listed Party", false, description = "Allow your party to be listed for other players.") { page == Page.Party && public }.apply { listener { _, _ -> edit() }} private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) private val startup = System.currentTimeMillis() @@ -122,19 +117,6 @@ object DiscordRPC : Module( } init { - // I don't like this, can we provide a listener directly? - maxPlayers.listener { _, _ -> - if (allowed) edit() - } - - public.listener { _, _ -> - if (allowed) edit() - } - - listed.listener { _, _ -> - if (allowed) edit() - } - unsafeListener { if (it.packet !is LoginHelloS2CPacket) return@unsafeListener connectionTime = System.currentTimeMillis() @@ -186,7 +168,9 @@ object DiscordRPC : Module( } else { warn("Failed to authenticate with the RPC server.") } - } else warn("You are using an offline account, please use a premium account to access all the RPC features.") + } else { + warn("You are using an offline account, please use a premium account to access all the RPC features.") + } loop@ while (true) { if (rpc.connected) update() else break@loop @@ -215,7 +199,7 @@ object DiscordRPC : Module( if (!allowed) return ioScope.launch { - createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers.value, public.value, listed.value) + createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public, listed) .also { currentParty.lazySet(it) } } } @@ -225,7 +209,7 @@ object DiscordRPC : Module( ioScope.launch { currentParty.acquire?.let { - editParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers.value, public.value, listed.value) + editParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public, listed) .also { currentParty.lazySet(it) } } } From 56de50cb914650dc85fc8fded062bd2407afe04d Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 28 Apr 2024 21:07:39 -0400 Subject: [PATCH 30/56] Feature: Join a party later with commands --- .../com/lambda/command/commands/RpcCommand.kt | 45 +++++++++++++++++++ .../module/modules/client/DiscordRPC.kt | 13 +++++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt diff --git a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt new file mode 100644 index 000000000..eb06e4c75 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt @@ -0,0 +1,45 @@ +package com.lambda.command.commands + +import com.lambda.brigadier.CommandResult +import com.lambda.brigadier.argument.string +import com.lambda.brigadier.argument.value +import com.lambda.brigadier.argument.word +import com.lambda.brigadier.execute +import com.lambda.brigadier.executeWithResult +import com.lambda.brigadier.required +import com.lambda.command.CommandManager.register +import com.lambda.command.LambdaCommand +import com.lambda.module.modules.client.DiscordRPC + +object RpcCommand : LambdaCommand { + override val name = "rpc" + + // TODO: What the fuck am I even doing here ?? + init { + register(name) { + required(word("action")) { action -> + val actions = listOf("join", "accept") + + suggests { _, builder -> + actions.forEach { + builder.suggest(it) + } + builder.buildFuture() + } + + executeWithResult { + val action = action().value() + if (action !in actions) { + return@executeWithResult CommandResult.failure("Invalid action $action. Did you mean ${actions.joinToString()}?") + } + + when (action) { + //"join" -> DiscordRPC.join() + "accept" -> DiscordRPC.accept() + } + CommandResult.success() + } + } + } + } +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index adf99038c..706ea8078 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -3,6 +3,7 @@ package com.lambda.module.modules.client import com.lambda.Lambda import com.lambda.Lambda.LOG import com.lambda.Lambda.mc +import com.lambda.context.SafeContext import com.lambda.event.EventFlow.ioScope import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent @@ -186,7 +187,7 @@ object DiscordRPC : Module( } // We won't need to specify non-null variables in kotlin 2.0 - private fun join(id: String) { + fun join(id: String) { if (!allowed) return ioScope.launch { @@ -195,6 +196,16 @@ object DiscordRPC : Module( } } + fun accept() { + if (!allowed) return + + ioScope.launch { + lastInvite?.let { + join(it.activity.party.id) + } + } + } + private fun create() { if (!allowed) return From 9a89c7e80a9a944a83130b1a4c190feac5ab5409 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 28 Apr 2024 21:10:27 -0400 Subject: [PATCH 31/56] funny comment --- .../main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 706ea8078..723275c1d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -141,6 +141,9 @@ object DiscordRPC : Module( onShutdown { disconnect() } } + // TODO: Fix the rpc only showing up in the second connection + // We should do a video of my sanity slowing degrading over time + // as I try to implement stuff and fix other stuff private suspend fun connect(event: ConnectionEvent.Connect.Login.Key? = null) { if (!rpc.connected) { rpc.register() From 32a242b9b3f2c311d0e745d7b7a3654c0eef3361 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:32:26 -0400 Subject: [PATCH 32/56] Fix: Token is not authorization --- .../main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt index 2be70c5ee..6bd02fd7f 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt @@ -9,7 +9,7 @@ fun login( version: String, // The player's Discord token. - // example: OTk1MTU1NzcyMzYxMTQ2NDM4.AAAAAA.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + // example: OTk1MTU1NzcyMzYxMTQ2NDM4 discordToken: String, // The player's username. @@ -25,7 +25,7 @@ fun login( Method.POST, parameters = mapOf( - "token" to "Bearer $discordToken", + "token" to discordToken, "username" to username, "hash" to hash ) From 2922ac547d7eb36b88c6477a2d0b24a4a0d5a51d Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:47:37 -0400 Subject: [PATCH 33/56] Create MiscArguments.kt --- .../brigadier/argument/MiscArguments.kt | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 common/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt diff --git a/common/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt b/common/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt new file mode 100644 index 000000000..039affcf8 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2023 The Quilt Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Preserve binary compatibility when moving extensions between files + */ +@file:JvmMultifileClass +@file:JvmName("ArgumentsKt") + +package com.lambda.brigadier.argument + +import com.lambda.brigadier.* +import com.lambda.brigadier.assumeSourceNotUsed +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.arguments.IntegerArgumentType + +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import net.minecraft.command.argument.TimeArgumentType +import net.minecraft.command.argument.UuidArgumentType +import java.util.* + +/** + * Descriptor for a literal argument. + * + * Separate from [DefaultArgumentDescriptor] + * to stand out more in type hints. + */ +object LiteralDescriptor : ArgumentDescriptor> + +/** + * Reads the integer value in ticks from the + * argument in the receiver [ArgumentReader]. + */ +@JvmName("valueTimeArg") +@BrigadierDsl +fun DefaultArgumentReader.value(): Int { + return IntegerArgumentType.getInteger(context, name) +} // TimeArgumentType does not provide an accessor, defaulting to int + +/** + * Reads the [UUID] value from the + * argument in the receiver [ArgumentReader]. + * + * @see UuidArgumentType.getUuid + */ +@JvmName("valueUuidArg") +@BrigadierDsl +fun DefaultArgumentReader.value(): UUID { + return UuidArgumentType.getUuid(context.assumeSourceNotUsed(), name) +} + +/** + * Creates a time argument with [name] as the parameter name. + * + * @see TimeArgumentType.time + */ +@BrigadierDsl +fun time( + name: String, + minimumTicks: Int = 0 +): DefaultArgumentConstructor { + return argument(name, TimeArgumentType.time(minimumTicks)) +} + +/** + * Creates a UUID argument with [name] as the parameter name. + */ +@BrigadierDsl +fun uuid( + name: String +): RequiredArgumentConstructor< + S, + DefaultArgumentDescriptor< + UuidArgumentType> + > { + return argument(name, UuidArgumentType.uuid()) +} + +/** + * Creates a literal argument with [name] as the literal. + * + * Like other arguments, literal arguments create accessors, + * which can be checked for presence of [optional] literals. + * However, [ArgumentReader]s produced from those accessors + * serve no purpose due to the argument not having a value. + */ +@BrigadierDsl +fun literal( + name: String +): ArgumentConstructor< + S, + LiteralArgumentBuilder, + LiteralDescriptor + > { + return ArgumentConstructor(LiteralArgumentBuilder.literal(name), name, LiteralDescriptor) +} From 776f3c604ea613e76837f61e7a90280964770247 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:55:52 -0400 Subject: [PATCH 34/56] Fixed rpc command builder --- .../com/lambda/command/commands/RpcCommand.kt | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt index eb06e4c75..cdca20c75 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt @@ -1,12 +1,10 @@ package com.lambda.command.commands -import com.lambda.brigadier.CommandResult +import com.lambda.brigadier.* +import com.lambda.brigadier.argument.literal import com.lambda.brigadier.argument.string import com.lambda.brigadier.argument.value import com.lambda.brigadier.argument.word -import com.lambda.brigadier.execute -import com.lambda.brigadier.executeWithResult -import com.lambda.brigadier.required import com.lambda.command.CommandManager.register import com.lambda.command.LambdaCommand import com.lambda.module.modules.client.DiscordRPC @@ -14,30 +12,19 @@ import com.lambda.module.modules.client.DiscordRPC object RpcCommand : LambdaCommand { override val name = "rpc" - // TODO: What the fuck am I even doing here ?? init { register(name) { - required(word("action")) { action -> - val actions = listOf("join", "accept") - - suggests { _, builder -> - actions.forEach { - builder.suggest(it) + required(literal("join")) { + required(word("id")) { id -> + execute { + DiscordRPC.join(id().value()) } - builder.buildFuture() } + } - executeWithResult { - val action = action().value() - if (action !in actions) { - return@executeWithResult CommandResult.failure("Invalid action $action. Did you mean ${actions.joinToString()}?") - } - - when (action) { - //"join" -> DiscordRPC.join() - "accept" -> DiscordRPC.accept() - } - CommandResult.success() + required(literal("accept")) { + execute { + DiscordRPC.accept() } } } From 816190e0e6e51800b94b1793c294a5a9c5130fab Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 12:03:42 -0400 Subject: [PATCH 35/56] Merge conflicts --- neoforge/build.gradle.kts | 3 +++ .../main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt | 11 ----------- 2 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 3c8c7d82c..465a06131 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -1,5 +1,6 @@ val neoVersion = property("neo_version").toString() val kotlinForgeVersion = property("kotlin_forge_version").toString() +val discordIPCVersion = property("discord_ipc_version").toString() base.archivesName.set("${base.archivesName.get()}-neoforge") @@ -56,6 +57,8 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") + includeLib("dev.babbaj:nether-pathfinder:1.5") + includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge-neoforge:$kotlinForgeVersion") diff --git a/quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt b/quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt deleted file mode 100644 index a016b491d..000000000 --- a/quilt/src/main/kotlin/com/lambda/fabric/LoaderInfoImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lambda.fabric - -import net.fabricmc.loader.api.FabricLoader - -object LoaderInfoImpl { - @JvmStatic - fun getVersion(): String = - FabricLoader.getInstance() - .getModContainer("lambda").orElseThrow() - .metadata.version.friendlyString -} \ No newline at end of file From 1d048d4edcec0837f7123234a7deee3252914f0a Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 12:59:00 -0400 Subject: [PATCH 36/56] Update gradle.properties --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index 5bcd28478..fd9426823 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ minecraftVersion=1.20.4 mixinExtrasVersion=0.3.5 kotlinVersion=1.9.24 kotlinxCoroutinesVersion=1.8.0 +discordIPCVersion=0.2.2 # Fabric https://fabricmc.net/develop/ fabricLoaderVersion=0.15.7 From 6e659a924d8222c06cc5586b933b2f2f11e1615d Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 19:25:56 -0400 Subject: [PATCH 37/56] Fix: RPC Connection --- common/build.gradle.kts | 2 + .../com/lambda/command/commands/RpcCommand.kt | 31 ++-- .../main/kotlin/com/lambda/http/Extensions.kt | 5 +- .../main/kotlin/com/lambda/http/Request.kt | 3 + .../http/api/rpc/v1/endpoints/CreateParty.kt | 5 - .../http/api/rpc/v1/endpoints/UpdateParty.kt | 5 - .../http/api/rpc/v1/models/Authentication.kt | 5 + .../lambda/http/api/rpc/v1/models/Party.kt | 8 ++ .../lambda/http/api/rpc/v1/models/Player.kt | 5 + .../lambda/http/api/rpc/v1/models/Settings.kt | 8 +- .../module/modules/client/DiscordRPC.kt | 134 +++++++++--------- .../util/primitives/extension/PlayerEntity.kt | 2 +- 12 files changed, 111 insertions(+), 102 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index a271651ef..c77b22e1f 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -2,6 +2,7 @@ val modId: String by project val fabricLoaderVersion: String by project val kotlinVersion: String by project val kotlinxCoroutinesVersion: String by project +val discordIPCVersion: String by project architectury { common("fabric", "forge", "neoforge") } @@ -21,6 +22,7 @@ dependencies { // Add dependencies on the required Kotlin modules. implementation("org.reflections:reflections:0.10.2") + implementation("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") diff --git a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt index cdca20c75..5cbcbc193 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt @@ -2,30 +2,29 @@ package com.lambda.command.commands import com.lambda.brigadier.* import com.lambda.brigadier.argument.literal -import com.lambda.brigadier.argument.string import com.lambda.brigadier.argument.value import com.lambda.brigadier.argument.word -import com.lambda.command.CommandManager.register import com.lambda.command.LambdaCommand import com.lambda.module.modules.client.DiscordRPC +import com.lambda.util.primitives.extension.CommandBuilder -object RpcCommand : LambdaCommand { - override val name = "rpc" - - init { - register(name) { - required(literal("join")) { - required(word("id")) { id -> - execute { - DiscordRPC.join(id().value()) - } +object RpcCommand : LambdaCommand( + name = "rpc", + description = "Discord Rich Presence commands.", + usage = "rpc " +) { + override fun CommandBuilder.create() { + required(literal("join")) { + required(word("id")) { id -> + execute { + DiscordRPC.join(id().value()) } } + } - required(literal("accept")) { - execute { - DiscordRPC.accept() - } + required(literal("accept")) { + execute { + DiscordRPC.accept() } } } diff --git a/common/src/main/kotlin/com/lambda/http/Extensions.kt b/common/src/main/kotlin/com/lambda/http/Extensions.kt index f7be6dd0b..df3543e66 100644 --- a/common/src/main/kotlin/com/lambda/http/Extensions.kt +++ b/common/src/main/kotlin/com/lambda/http/Extensions.kt @@ -5,9 +5,8 @@ import java.net.URLEncoder /** * Extension property to convert a map to a URL query string. */ -val Map.query: String get() { - return this.map { (key, value) -> "$key=${value.urlEncoded}" }.joinToString("&") -} +val Map.query: String + get() = map { (key, value) -> "$key=${value.urlEncoded}" }.joinToString("&") /** * Extension property to URL encode a string. diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index 23d0b3fdf..ce486b819 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -39,6 +39,9 @@ class Request( headers.forEach { (key, value) -> connection.setRequestProperty(key, Lambda.gson.toJson(value)) } + // For the moment we are only supporting JSON requests. + connection.setRequestProperty("Content-Type", "application/json") + if (!canBeEncoded) { connection.doOutput = true connection.outputStream.use { diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt index 1ebab5f89..d4e207787 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt @@ -17,10 +17,6 @@ fun createParty( // If false can only be joined by invite. // example: true public: Boolean = true, - - // Whether the party can be listed or not. - // example: true - listed: Boolean = true, ) = Request( "$endpoint/api/$version/party/create", @@ -29,7 +25,6 @@ fun createParty( mapOf( "max_players" to maxPlayers, "public" to public, - "listed" to listed ), headers = mapOf( diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt index 79587264a..69019c223 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt @@ -17,10 +17,6 @@ fun editParty( // If false can only be joined by invite. // example: true public: Boolean = true, - - // Whether the party can be listed or not. - // example: true - listed: Boolean = true, ) = Request( "$endpoint/api/$version/party/edit", Method.PATCH, @@ -28,7 +24,6 @@ fun editParty( mapOf( "max_players" to maxPlayers, "public" to public, - "listed" to listed ), headers = mapOf( diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt index 9401b55de..0a22d4254 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Authentication.kt @@ -1,15 +1,20 @@ package com.lambda.http.api.rpc.v1.models +import com.google.gson.annotations.SerializedName + data class Authentication( // The access token to use for the API // example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c + @SerializedName("access_token") val accessToken: String, // The duration of the token (in seconds). // example: 3600 + @SerializedName("expires_in") val expiresIn: Long, // The type of the token. // example: Bearer + @SerializedName("token_type") val tokenType: String, ) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt index f84cdb9f2..0636170fa 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Party.kt @@ -1,24 +1,32 @@ package com.lambda.http.api.rpc.v1.models +import com.google.gson.annotations.SerializedName + data class Party( // The ID of the party. // It is a random string of 30 characters. + @SerializedName("id") val id: String, // The join secret of the party. // It is a random string of 100 characters. + @SerializedName("join_secret") val joinSecret: String, // The leader of the party + @SerializedName("leader") val leader: Player, // The creation date of the party. // example: 2021-10-10T12:00:00Z + @SerializedName("creation") val creation: String, // The list of players in the party. + @SerializedName("players") val players: List, // The settings of the party + @SerializedName("settings") val settings: Settings, ) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt index cda7f583f..acfc17ad6 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Player.kt @@ -1,15 +1,20 @@ package com.lambda.http.api.rpc.v1.models +import com.google.gson.annotations.SerializedName + data class Player ( // The player's name. // example: Notch + @SerializedName("name") val name: String, // The player's UUID. // example: 069a79f4-44e9-4726-a5be-fca90e38aaf5 + @SerializedName("uuid") val uuid: String, // The player's Discord ID. // example: "385441179069579265" + @SerializedName("discord_id") val discordId: String, ) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt index c9e638a17..bb7c53cc7 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/models/Settings.kt @@ -1,16 +1,16 @@ package com.lambda.http.api.rpc.v1.models +import com.google.gson.annotations.SerializedName + data class Settings( // The maximum number of players in the party. // example: 10 + @SerializedName("max_players") val maxPlayers: Int, // Whether the party is public or not. // If false can only be joined by invite. // example: true + @SerializedName("public") val public: Boolean, - - // Whether the party can be listed or not. - // example: true - val listed: Boolean, ) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 723275c1d..30ea8c20e 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -3,7 +3,6 @@ package com.lambda.module.modules.client import com.lambda.Lambda import com.lambda.Lambda.LOG import com.lambda.Lambda.mc -import com.lambda.context.SafeContext import com.lambda.event.EventFlow.ioScope import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent @@ -23,7 +22,6 @@ import com.lambda.util.Communication.toast import com.lambda.util.Communication.warn import com.lambda.util.Nameable import com.lambda.util.StringUtils.capitalize -import com.lambda.util.primitives.extension.isOffline import com.lambda.util.text.ClickEvents import com.lambda.util.text.buildText import com.lambda.util.text.clickEvent @@ -48,6 +46,7 @@ object DiscordRPC : Module( name = "DiscordRPC", description = "Discord Rich Presence configuration", defaultTags = setOf(ModuleTag.CLIENT), + enabledByDefault = true, ) { private val page by setting("Page", Page.General) @@ -68,14 +67,12 @@ object DiscordRPC : Module( /* Party settings */ private val enableParty by setting("Enable Party", true, description = "Allows you to create parties.") { page == Page.Party } - private val createByDefault by setting("Create Party by Default", false, description = "Automatically create a party when you join a server.") { page == Page.Party && enableParty } - private val maxPlayers by setting("Max Players", 10, 2..20, visibility = { page == Page.Party }).apply { listener { _, _ -> edit() }} - private val public by setting("Public Party", false, description = "Allow anyone to join your party.") { page == Page.Party }.apply { listener { _, _ -> edit() }} - private val listed by setting("Listed Party", false, description = "Allow your party to be listed for other players.") { page == Page.Party && public }.apply { listener { _, _ -> edit() }} + private val createByDefault by setting("Create Party by Default", true, description = "Automatically create a party when you join a server.") { page == Page.Party && enableParty } + private val maxPlayers by setting("Max Players", 10, 2..20, visibility = { page == Page.Party }).apply { onValueChange { _, _ -> edit() } } + private val public by setting("Public Party", false, description = "Allow anyone to join your party.") { page == Page.Party }.apply { onValueChange { _, _ -> edit() } } private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) private val startup = System.currentTimeMillis() - private val cracked = mc.gameProfile.isOffline private var discordAuth: AuthenticatePacket.Data? = null private var rpcAuth: Authentication? = null @@ -90,7 +87,7 @@ object DiscordRPC : Module( * If the player can interact with the party system. */ private val allowed: Boolean - get() = rpcAuth != null && discordAuth != null && !cracked && enableParty + get() = rpcAuth != null && discordAuth != null && enableParty private enum class Page { General, Settings, Party @@ -99,17 +96,31 @@ object DiscordRPC : Module( private enum class LineInfo(val value: () -> String) : Nameable { VERSION({ Lambda.VERSION }), WORLD({ - if (mc.currentServerEntry != null) "Multiplayer" - else if (mc.isIntegratedServerRunning) "Singleplayer" - else "Main Menu" + when { + mc.currentServerEntry != null -> "Multiplayer" + mc.isIntegratedServerRunning -> "Singleplayer" + else -> "Main Menu" + } }), USERNAME({ mc.session.username }), HEALTH({ "${mc.player?.health ?: 0} HP" }), HUNGER({ "${mc.player?.hungerManager?.foodLevel ?: 0} Hunger" }), DIMENSION({ mc.world?.dimensionKey?.value?.path?.capitalize() ?: "Unknown" }), - COORDINATES({ if (confirmCoordinates) "Coords: ${mc.player?.blockPos?.toShortString()}" else "[Redacted]" }), - SERVER({ if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" else "[Redacted]" }), - FPS({ "${mc.currentFps} FPS" }), + COORDINATES({ + if (confirmCoordinates) { + "Coords: ${mc.player?.blockPos?.toShortString()}" + } else { + "[Redacted]" + } + }), + SERVER({ + if (confirmServer) { + mc.currentServerEntry?.address ?: "Not Connected" + } else { + "[Redacted]" + } + }), + FPS({ "${mc.currentFps} FPS" }); } private enum class ApiVersion(val value: String) { @@ -141,43 +152,13 @@ object DiscordRPC : Module( onShutdown { disconnect() } } - // TODO: Fix the rpc only showing up in the second connection - // We should do a video of my sanity slowing degrading over time - // as I try to implement stuff and fix other stuff private suspend fun connect(event: ConnectionEvent.Connect.Login.Key? = null) { - if (!rpc.connected) { - rpc.register() - rpc.connect() - } + if (event != null) rpc.register(event) - if (!cracked && - (rpcAuth == null || - discordAuth == null) && - event != null) - { - if (System.currentTimeMillis() - connectionTime > 300000) { - warn("The authentication hash has expired, please reconnect to the server.") - return - } + if (!rpc.connected) runConcurrent { rpc.connect() } - val hash = BigInteger(NetworkEncryptionUtils.computeServerId(serverId ?: return, event.publicKey, event.secretKey)).toString(16) - - // Prompt the user to authorize - discordAuth = rpc.applicationManager.authenticate() - rpcAuth = login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) - - if (rpcAuth != null) { - info("Successfully authenticated with the RPC server.") - if (createByDefault) create() - } else { - warn("Failed to authenticate with the RPC server.") - } - } else { - warn("You are using an offline account, please use a premium account to access all the RPC features.") - } - - loop@ while (true) { - if (rpc.connected) update() else break@loop + while (true) { + if (rpc.connected) update() delay(delay * 1000L) } } @@ -209,11 +190,12 @@ object DiscordRPC : Module( } } - private fun create() { + fun create() { if (!allowed) return ioScope.launch { - createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public, listed) + println(rpcAuth) + createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public) .also { currentParty.lazySet(it) } } } @@ -223,7 +205,7 @@ object DiscordRPC : Module( ioScope.launch { currentParty.acquire?.let { - editParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public, listed) + editParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public) .also { currentParty.lazySet(it) } } } @@ -250,24 +232,40 @@ object DiscordRPC : Module( } } - private suspend fun KDiscordIPC.register() { + private suspend fun KDiscordIPC.register(auth: ConnectionEvent.Connect.Login.Key) { on { - LOG.info("Discord RPC connected to ${data.user.username}.") - - if (!cracked) { - // Party features - subscribe(DiscordEvent.ActivityJoinRequest) - subscribe(DiscordEvent.ActivityJoin) - subscribe(DiscordEvent.ActivityInvite) - subscribe(DiscordEvent.LobbyUpdate) - subscribe(DiscordEvent.LobbyDelete) - subscribe(DiscordEvent.LobbyMemberConnect) - subscribe(DiscordEvent.LobbyMemberDisconnect) - subscribe(DiscordEvent.LobbyMemberUpdate) - - // QOL features - subscribe(DiscordEvent.SpeakingStart) - subscribe(DiscordEvent.SpeakingStop) + // Party features + subscribe(DiscordEvent.ActivityJoinRequest) + subscribe(DiscordEvent.ActivityJoin) + subscribe(DiscordEvent.ActivityInvite) + //subscribe(DiscordEvent.LobbyUpdate) // Invalid Event ? + //subscribe(DiscordEvent.LobbyDelete) // Invalid Event ? + //subscribe(DiscordEvent.LobbyMemberConnect) + //subscribe(DiscordEvent.LobbyMemberDisconnect) + //subscribe(DiscordEvent.LobbyMemberUpdate) + + // QOL features + subscribe(DiscordEvent.SpeakingStart) + subscribe(DiscordEvent.SpeakingStop) + + if (System.currentTimeMillis() - connectionTime > 300000) { + warn("The authentication hash has expired, reconnect to the server.") + return@on + } + + val hash = BigInteger( + NetworkEncryptionUtils.computeServerId(serverId ?: return@on, auth.publicKey, auth.secretKey) + ).toString(16) + + // Prompt the user to authorize + discordAuth = rpc.applicationManager.authenticate() + rpcAuth = login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) + + if (rpcAuth != null) { + info("Successfully authenticated with the RPC server.") + if (createByDefault) create() + } else { + warn("Failed to authenticate with the RPC server.") } } diff --git a/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt b/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt index 6106fe2c9..40918046b 100644 --- a/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt +++ b/common/src/main/kotlin/com/lambda/util/primitives/extension/PlayerEntity.kt @@ -8,4 +8,4 @@ val PlayerEntity.isOffline get() = gameProfile.isOffline val GameProfile.isOffline - get() = Uuids.getOfflinePlayerUuid(name) == id + get() = properties.isEmpty From 5a68b2ee47ac84b1c91585a4279263565d9742ac Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 19:34:40 -0400 Subject: [PATCH 38/56] Fix: IPC not found --- settings.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index ca0590866..1811a0cd4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,7 @@ pluginManagement { maven("https://maven.fabricmc.net/") maven("https://maven.architectury.dev/") maven("https://maven.minecraftforge.net/") + maven("https://jitpack.io") mavenCentral() gradlePluginPortal() } From f0a01e7b8f135f3af372bc982fafa52b00f8fdb6 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 19:46:38 -0400 Subject: [PATCH 39/56] Add exception info on error --- .../main/kotlin/com/lambda/http/Request.kt | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index ce486b819..315f4e082 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -1,6 +1,7 @@ package com.lambda.http import com.lambda.Lambda +import com.lambda.Lambda.LOG import java.net.HttpURLConnection import java.net.URL @@ -51,6 +52,16 @@ class Request( connection.connect() + if (connection.responseCode !in 200..299) { + return Response( + connection = connection, + exception = Throwable( + "HTTP request failed with status code ${connection.responseCode}\n" + + "Response: ${connection.errorStream.bufferedReader().readText()}" + ) + ) + } + val response = Response( connection = connection, body = connection.inputStream.bufferedReader() @@ -64,6 +75,14 @@ class Request( * * @param T The type of the expected JSON response. */ - inline fun json(): T? = - doRequest().body?.let { Lambda.gson.fromJson(it, T::class.java) } + inline fun json(): T? { + val response = doRequest() + + response.exception?.let { + LOG.error(it) + return null + } + + return response.body?.let { Lambda.gson.fromJson(it, T::class.java) } + } } From dfc8f976f9b7d51bf8f5f3393057edc21d85e219 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 19:50:31 -0400 Subject: [PATCH 40/56] Better error format --- common/src/main/kotlin/com/lambda/http/Request.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index 315f4e082..7a627336d 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -21,6 +21,8 @@ class Request( private val headers: Map = mapOf(), private val config: ((HttpURLConnection) -> Unit) = {}, ) { + private val exceptionFormat = "HTTP request failed with status code %d\nResponse: %s" + private val canBeEncoded: Boolean get() = method != Method.POST && method != Method.PUT && method != Method.PATCH @@ -56,8 +58,10 @@ class Request( return Response( connection = connection, exception = Throwable( - "HTTP request failed with status code ${connection.responseCode}\n" + - "Response: ${connection.errorStream.bufferedReader().readText()}" + exceptionFormat.format( + connection.responseCode, + connection.errorStream.bufferedReader().readText() + ) ) ) } From 00b17af0c0e8ddd319368f628fc1314cc7bf3a91 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 19 May 2024 20:58:23 -0400 Subject: [PATCH 41/56] Temp fix xray --- common/src/main/kotlin/com/lambda/http/Request.kt | 3 +++ .../kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 1 - .../main/kotlin/com/lambda/module/modules/render/XRay.kt | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index 7a627336d..f6d0b21f6 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -60,6 +60,9 @@ class Request( exception = Throwable( exceptionFormat.format( connection.responseCode, + // If there is an error here, it means the connection + // was abruptly closed and there is no error stream. + // I don't know how to handle this rare edge case. connection.errorStream.bufferedReader().readText() ) ) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 30ea8c20e..846c2b442 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -194,7 +194,6 @@ object DiscordRPC : Module( if (!allowed) return ioScope.launch { - println(rpcAuth) createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public) .also { currentParty.lazySet(it) } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt index 4a1b8b012..0b34597d3 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt @@ -23,14 +23,14 @@ object XRay : Module( Blocks.ANCIENT_DEBRIS ) - private val selection by setting("Block Selection", defaultBlocks, "Block selection that will be shown (whitelist) or hidden (blacklist)") + //private val selection by setting("Block Selection", defaultBlocks, "Block selection that will be shown (whitelist) or hidden (blacklist)") private val mode by setting("Selection Mode", Selection.WHITELIST, "The mode of the block selection") @JvmStatic fun isSelected(blockState: BlockState) = mode.select(blockState) enum class Selection(val select: (BlockState) -> Boolean) { - WHITELIST({ it.block in selection }), - BLACKLIST({ it.block !in selection }) + WHITELIST({ it.block in com.lambda.module.modules.render.XRay.defaultBlocks }), + BLACKLIST({ it.block !in com.lambda.module.modules.render.XRay.defaultBlocks }) } init { From 80b7fa712918e004349c246557f042337d2438f9 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 26 May 2024 17:20:24 -0400 Subject: [PATCH 42/56] Better errors --- .../src/main/kotlin/com/lambda/http/Extensions.kt | 15 +++++++++++++++ common/src/main/kotlin/com/lambda/http/Request.kt | 10 +++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/http/Extensions.kt b/common/src/main/kotlin/com/lambda/http/Extensions.kt index df3543e66..ab2898961 100644 --- a/common/src/main/kotlin/com/lambda/http/Extensions.kt +++ b/common/src/main/kotlin/com/lambda/http/Extensions.kt @@ -1,5 +1,6 @@ package com.lambda.http +import com.lambda.Lambda import java.net.URLEncoder /** @@ -12,3 +13,17 @@ val Map.query: String * Extension property to URL encode a string. */ val Any.urlEncoded: String get() = URLEncoder.encode(toString(), "UTF-8") + +/** + * Extension function to convert a map to a JSON string. + */ +fun Map.toJson(): String = Lambda.gson.toJson(this) + +/** + * Try-catch block wrapped with a default value. + */ +fun tryOrDefault(default: T, block: () -> T) = try { + block() +} catch (e: Exception) { + default +} diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index f6d0b21f6..f59ff9f5a 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -48,7 +48,7 @@ class Request( if (!canBeEncoded) { connection.doOutput = true connection.outputStream.use { - it.write(Lambda.gson.toJson(parameters).toByteArray()) + it.write(parameters.toJson().toByteArray()) } } @@ -60,10 +60,10 @@ class Request( exception = Throwable( exceptionFormat.format( connection.responseCode, - // If there is an error here, it means the connection - // was abruptly closed and there is no error stream. - // I don't know how to handle this rare edge case. - connection.errorStream.bufferedReader().readText() + tryOrDefault( + connection.errorStream.bufferedReader().readText() + ) { "A critical error causes the remote client to abruptly close the connection.\n" + + "No action is required on your side." } ) ) ) From 8fbe15272e16ca179b09f5d561f383eccd4d5c95 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Sun, 26 May 2024 17:34:29 -0400 Subject: [PATCH 43/56] Update DiscordRPC.kt --- .../main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 846c2b442..d5c1ae5bd 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -170,7 +170,6 @@ object DiscordRPC : Module( } } - // We won't need to specify non-null variables in kotlin 2.0 fun join(id: String) { if (!allowed) return From ba3073df824440024f4cd3d327531a5fcba3faf5 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:49:55 -0400 Subject: [PATCH 44/56] Refactor: API update --- .gitignore | 4 + .../com/lambda/command/commands/RpcCommand.kt | 2 +- .../main/kotlin/com/lambda/http/Extensions.kt | 7 +- .../main/kotlin/com/lambda/http/Request.kt | 67 +++++------ .../main/kotlin/com/lambda/http/Response.kt | 15 ++- .../http/api/rpc/v1/endpoints/DeleteParty.kt | 20 ++++ .../http/api/rpc/v1/endpoints/GetParty.kt | 19 +++ .../http/api/rpc/v1/endpoints/LeaveParty.kt | 20 ++++ .../lambda/http/api/rpc/v1/endpoints/Login.kt | 2 +- .../module/modules/client/DiscordRPC.kt | 111 ++++++++---------- 10 files changed, 156 insertions(+), 111 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt create mode 100644 common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt diff --git a/.gitignore b/.gitignore index 46d92844f..5b2d3bfe2 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ logs/ ### Architectury ### .architectury-transformer/ + +### Kotlin ### +*.kotlin_module +.kotlin/ diff --git a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt index 5cbcbc193..40c84ea08 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt @@ -24,7 +24,7 @@ object RpcCommand : LambdaCommand( required(literal("accept")) { execute { - DiscordRPC.accept() + DiscordRPC.join() } } } diff --git a/common/src/main/kotlin/com/lambda/http/Extensions.kt b/common/src/main/kotlin/com/lambda/http/Extensions.kt index ab2898961..a347b5381 100644 --- a/common/src/main/kotlin/com/lambda/http/Extensions.kt +++ b/common/src/main/kotlin/com/lambda/http/Extensions.kt @@ -22,8 +22,13 @@ fun Map.toJson(): String = Lambda.gson.toJson(this) /** * Try-catch block wrapped with a default value. */ -fun tryOrDefault(default: T, block: () -> T) = try { +fun tryOrDefault(default: T, block: () -> T): T = try { block() } catch (e: Exception) { default } + +/** + * Try-catch block wrapped with null + */ +fun tryOrNull(block: () -> T): T? = tryOrDefault(null, block) diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index f59ff9f5a..acfddbea5 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -15,21 +15,21 @@ import java.net.URL * @property config A lambda function to configure the HTTP connection. Default is an empty lambda. */ class Request( - private val url: String, - private val method: Method = Method.GET, - private val parameters: Map = mapOf(), - private val headers: Map = mapOf(), - private val config: ((HttpURLConnection) -> Unit) = {}, + val url: String, + val method: Method = Method.GET, + val parameters: Map = mapOf(), + val headers: Map = mapOf(), + val config: ((HttpURLConnection) -> Unit) = {}, ) { - private val exceptionFormat = "HTTP request failed with status code %d\nResponse: %s" + val exceptionFormat = "HTTP request failed with status code %d\nResponse: %s" - private val canBeEncoded: Boolean + val canBeEncoded: Boolean get() = method != Method.POST && method != Method.PUT && method != Method.PATCH /** * Executes the HTTP request synchronously. */ - fun doRequest(): Response { + inline fun json(): Response { val url = URL( if (parameters.isNotEmpty() && canBeEncoded) "$url?${parameters.query}" else url @@ -45,51 +45,44 @@ class Request( // For the moment we are only supporting JSON requests. connection.setRequestProperty("Content-Type", "application/json") - if (!canBeEncoded) { - connection.doOutput = true - connection.outputStream.use { - it.write(parameters.toJson().toByteArray()) + runCatching { + if (!canBeEncoded) { + connection.doOutput = true + connection.outputStream.use { + it.write(parameters.toJson().toByteArray()) + } + } else { + connection.connect() } + }.onFailure { + return Response( + connection = connection, + data = null, + error = it + ) } - connection.connect() - if (connection.responseCode !in 200..299) { return Response( connection = connection, - exception = Throwable( + data = null, + error = Throwable( exceptionFormat.format( connection.responseCode, tryOrDefault( + "A critical error causes the remote client to abruptly close the connection.\n" + + "No action is required on your side." + ) { connection.errorStream.bufferedReader().readText() - ) { "A critical error causes the remote client to abruptly close the connection.\n" + - "No action is required on your side." } + } ) ) ) } - val response = Response( + return Response( connection = connection, - body = connection.inputStream.bufferedReader() + data = tryOrNull { Lambda.gson.fromJson(connection.inputStream.bufferedReader().readText(), Success::class.java) }, ) - - return response - } - - /** - * Executes an HTTP request synchronously and parses the response as JSON. - * - * @param T The type of the expected JSON response. - */ - inline fun json(): T? { - val response = doRequest() - - response.exception?.let { - LOG.error(it) - return null - } - - return response.body?.let { Lambda.gson.fromJson(it, T::class.java) } } } diff --git a/common/src/main/kotlin/com/lambda/http/Response.kt b/common/src/main/kotlin/com/lambda/http/Response.kt index 56306ab2c..9654e6657 100644 --- a/common/src/main/kotlin/com/lambda/http/Response.kt +++ b/common/src/main/kotlin/com/lambda/http/Response.kt @@ -1,26 +1,25 @@ package com.lambda.http -import java.io.BufferedReader import java.net.HttpURLConnection /** * Represents an HTTP response. */ -class Response( +class Response( /** - * The HTTP connection associated with the response. + * The response */ - var connection: HttpURLConnection? = null, + var data: Success? = null, /** - * The buffered reader for reading the response body. + * The error */ - var body: BufferedReader? = null, + var error: Throwable? = null, /** - * The exception that occurred during the request, if any. + * The HTTP connection associated with the response. */ - var exception: Throwable? = null, + var connection: HttpURLConnection? = null, ) { /** * Indicates whether the request was successful (HTTP status code 2xx). diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt new file mode 100644 index 000000000..2d7e0c82a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt @@ -0,0 +1,20 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.models.Party + +fun deleteParty( + endpoint: String, + version: String, + accessToken: String, +) = + Request( + "$endpoint/api/$version/party/delete", + Method.DELETE, + headers = + mapOf( + "Authorization" to "Bearer $accessToken" + ) + ).json() + diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt new file mode 100644 index 000000000..b917db982 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt @@ -0,0 +1,19 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.models.Party + +fun createParty( + endpoint: String, + version: String, + accessToken: String, +) = + Request( + "$endpoint/api/$version/party", + Method.GET, + headers = + mapOf( + "Authorization" to "Bearer $accessToken" + ) + ).json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt new file mode 100644 index 000000000..f057a9a5e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt @@ -0,0 +1,20 @@ +package com.lambda.http.api.rpc.v1.endpoints + +import com.lambda.http.Method +import com.lambda.http.Request +import com.lambda.http.api.rpc.v1.models.Party + +fun leaveParty( + endpoint: String, + version: String, + accessToken: String, +) = + Request( + "$endpoint/api/$version/party/leave", + Method.PUT, + headers = + mapOf( + "Authorization" to "Bearer $accessToken" + ) + ).json() + diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt index 6bd02fd7f..c2c79fd5e 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt @@ -21,7 +21,7 @@ fun login( hash: String ) = Request( - "$endpoint/api/$version/party/login", + "$endpoint/api/$version/login", Method.POST, parameters = mapOf( diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index d5c1ae5bd..881a056b7 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -7,10 +7,7 @@ import com.lambda.event.EventFlow.ioScope import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener -import com.lambda.http.api.rpc.v1.endpoints.createParty -import com.lambda.http.api.rpc.v1.endpoints.editParty -import com.lambda.http.api.rpc.v1.endpoints.joinParty -import com.lambda.http.api.rpc.v1.endpoints.login +import com.lambda.http.api.rpc.v1.endpoints.* import com.lambda.http.api.rpc.v1.models.Authentication import com.lambda.http.api.rpc.v1.models.Party import com.lambda.module.Module @@ -36,7 +33,6 @@ import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.AuthenticatePacket import dev.cbyrne.kdiscordipc.data.activity.* import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import net.minecraft.network.encryption.NetworkEncryptionUtils import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket import java.math.BigInteger @@ -56,8 +52,8 @@ object DiscordRPC : Module( private val line2Left by setting("Line 2 Left", LineInfo.DIMENSION) { page == Page.General } private val line2Right by setting("Line 2 Right", LineInfo.FPS) { page == Page.General } - private val confirmCoordinates by setting("Show Coordinates", false) { page == Page.General } - private val confirmServer by setting("Expose server", false, description = "Allow to show what server you are on and allow to join parties.") { page == Page.General } + private val confirmCoordinates by setting("Show Coordinates", false, description = "Confirm display the player coordinates") { page == Page.General } + private val confirmServer by setting("Expose server", false, description = "Confirm display the server IP") { page == Page.General } private val showTime by setting("Show Time", true, description = "Show how long you have been playing for.") { page == Page.General } /* Technical settings */ @@ -72,7 +68,8 @@ object DiscordRPC : Module( private val public by setting("Public Party", false, description = "Allow anyone to join your party.") { page == Page.Party }.apply { onValueChange { _, _ -> edit() } } private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) - private val startup = System.currentTimeMillis() + private var startup = System.currentTimeMillis() + private val dimensionRegex = Regex("""\b\w+_\w+\b""") private var discordAuth: AuthenticatePacket.Data? = null private var rpcAuth: Authentication? = null @@ -105,20 +102,18 @@ object DiscordRPC : Module( USERNAME({ mc.session.username }), HEALTH({ "${mc.player?.health ?: 0} HP" }), HUNGER({ "${mc.player?.hungerManager?.foodLevel ?: 0} Hunger" }), - DIMENSION({ mc.world?.dimensionKey?.value?.path?.capitalize() ?: "Unknown" }), + DIMENSION({ + mc.world?.registryKey?.value?.path?.replace(dimensionRegex) { + it.value.split("_").joinToString(" ") { it.capitalize() } + } ?: "Unknown" + }), COORDINATES({ - if (confirmCoordinates) { - "Coords: ${mc.player?.blockPos?.toShortString()}" - } else { - "[Redacted]" - } + if (confirmCoordinates) "Coords: ${mc.player?.blockPos?.toShortString()}" + else "[Redacted]" }), SERVER({ - if (confirmServer) { - mc.currentServerEntry?.address ?: "Not Connected" - } else { - "[Redacted]" - } + if (confirmServer) mc.currentServerEntry?.address ?: "Not Connected" + else "[Redacted]" }), FPS({ "${mc.currentFps} FPS" }); } @@ -135,7 +130,8 @@ object DiscordRPC : Module( serverId = it.packet.serverId } - // Will not work in single player or cracked servers + // Will not work if the player doesn't have a valid key pair + // from Mojang's authentication server. unsafeListener { event -> runConcurrent { connect(event) @@ -153,8 +149,9 @@ object DiscordRPC : Module( } private suspend fun connect(event: ConnectionEvent.Connect.Login.Key? = null) { - if (event != null) rpc.register(event) + startup = System.currentTimeMillis() + if (event != null) rpc.register(event) if (!rpc.connected) runConcurrent { rpc.connect() } while (true) { @@ -167,49 +164,35 @@ object DiscordRPC : Module( if (rpc.connected) { LOG.info("Gracefully disconnecting from Discord RPC.") rpc.disconnect() + leaveParty(rpcServer, apiVersion.value, rpcAuth?.accessToken ?: return) } } - fun join(id: String) { + fun join(id: String = lastInvite?.activity?.party?.id ?: "") { if (!allowed) return - ioScope.launch { - joinParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, id) - .also { currentParty.lazySet(it) } - } - } - - fun accept() { - if (!allowed) return - - ioScope.launch { - lastInvite?.let { - join(it.activity.party.id) + joinParty(rpcServer, apiVersion.value, rpcAuth?.accessToken ?: return, id) + .also { response -> + if (response.error != null) warn("Failed to join the party: ${response.error}") + currentParty.lazySet(response.data) } - } - } - - fun create() { - if (!allowed) return - - ioScope.launch { - createParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public) - .also { currentParty.lazySet(it) } - } } private fun edit() { if (!allowed) return - ioScope.launch { - currentParty.acquire?.let { - editParty(rpcServer, apiVersion.value, rpcAuth!!.accessToken, maxPlayers, public) - .also { currentParty.lazySet(it) } - } + currentParty.acquire?.let { + editParty(rpcServer, apiVersion.value, rpcAuth?.accessToken ?: return, maxPlayers, public) + .also { response -> + if (response.error != null) warn("Failed to edit the party: ${response.error}") + currentParty.lazySet(response.data) + } } } private suspend fun update() { + val party = currentParty.acquire + rpc.activityManager.setActivity { details = "${line1Left.value()} | ${line1Right.value()}".take(128) state = "${line2Left.value()} | ${line2Right.value()}".take(128) @@ -217,13 +200,11 @@ object DiscordRPC : Module( largeImage("lambda", Lambda.VERSION) smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - val party = currentParty.acquire - if (allowed && party != null) { party(party.id, party.players.size, party.settings.maxPlayers) secrets(party.joinSecret) } else { - button("Download", "https://modrinth.com/") // ToDo: Add real link + button("Download", "https://github.com/lambda-client/lambda") } if (showTime) timestamps(startup) @@ -236,15 +217,15 @@ object DiscordRPC : Module( subscribe(DiscordEvent.ActivityJoinRequest) subscribe(DiscordEvent.ActivityJoin) subscribe(DiscordEvent.ActivityInvite) - //subscribe(DiscordEvent.LobbyUpdate) // Invalid Event ? - //subscribe(DiscordEvent.LobbyDelete) // Invalid Event ? + //subscribe(DiscordEvent.LobbyUpdate) + //subscribe(DiscordEvent.LobbyDelete) //subscribe(DiscordEvent.LobbyMemberConnect) //subscribe(DiscordEvent.LobbyMemberDisconnect) //subscribe(DiscordEvent.LobbyMemberUpdate) // QOL features - subscribe(DiscordEvent.SpeakingStart) - subscribe(DiscordEvent.SpeakingStop) + //subscribe(DiscordEvent.SpeakingStart) + //subscribe(DiscordEvent.SpeakingStop) if (System.currentTimeMillis() - connectionTime > 300000) { warn("The authentication hash has expired, reconnect to the server.") @@ -257,14 +238,18 @@ object DiscordRPC : Module( // Prompt the user to authorize discordAuth = rpc.applicationManager.authenticate() - rpcAuth = login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) - if (rpcAuth != null) { - info("Successfully authenticated with the RPC server.") - if (createByDefault) create() - } else { - warn("Failed to authenticate with the RPC server.") - } + login(rpcServer, apiVersion.value, discordAuth?.accessToken ?: "", mc.session.username, hash) + .also { response -> + if (response.error != null) warn("Failed to authenticate with the RPC server: ${response.error}") + rpcAuth = response.data + } + + if (createByDefault) createParty(rpcServer, apiVersion.value, rpcAuth?.accessToken ?: return@on, maxPlayers, public) + .also { response -> + if (response.error != null) warn("Failed to create a party: ${response.error}") + currentParty.lazySet(response.data) + } } on { From 90b600e49ead566a8e92009018213a7e1d08e275 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:59:29 -0400 Subject: [PATCH 45/56] Refactor: Use our fork --- build.gradle.kts | 8 ++--- common/build.gradle.kts | 2 +- .../module/modules/client/DiscordRPC.kt | 32 ++++--------------- fabric/build.gradle.kts | 13 +++++++- forge/build.gradle.kts | 6 ++-- gradle.properties | 1 + neoforge/build.gradle.kts | 12 +++++-- 7 files changed, 39 insertions(+), 35 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 148e45a27..1d966875b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,11 +62,11 @@ allprojects { base.archivesName = modId repositories { - maven("https://api.modrinth.com/maven") - maven("https://jitpack.io") - maven("https://maven.shedaniel.me/") { name = "Architectury" } + maven("https://maven.shedaniel.me/") // Architectury maven("https://maven.terraformersmc.com/releases/") - maven("https://babbaj.github.io/maven/") + maven("https://babbaj.github.io/maven/") // Baritone + maven("https://jitpack.io") // KDiscordIPC + mavenCentral() // Allow the use of local libraries flatDir { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 49fd9280f..f1b1ec80a 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { // Add dependencies on the required Kotlin modules. implementation("org.reflections:reflections:0.10.2") - implementation("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") + implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 881a056b7..6eeaa0f13 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -14,20 +14,13 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.onShutdown import com.lambda.threading.runConcurrent -import com.lambda.util.Communication.info -import com.lambda.util.Communication.toast import com.lambda.util.Communication.warn import com.lambda.util.Nameable import com.lambda.util.StringUtils.capitalize -import com.lambda.util.text.ClickEvents -import com.lambda.util.text.buildText -import com.lambda.util.text.clickEvent -import com.lambda.util.text.literal import dev.cbyrne.kdiscordipc.KDiscordIPC import dev.cbyrne.kdiscordipc.core.event.DiscordEvent -import dev.cbyrne.kdiscordipc.core.event.data.ActivityInviteEventData -import dev.cbyrne.kdiscordipc.core.event.impl.ActivityInviteEvent import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinEvent +import dev.cbyrne.kdiscordipc.core.event.impl.ActivityJoinRequestEvent import dev.cbyrne.kdiscordipc.core.event.impl.ErrorEvent import dev.cbyrne.kdiscordipc.core.event.impl.ReadyEvent import dev.cbyrne.kdiscordipc.core.packet.inbound.impl.AuthenticatePacket @@ -75,8 +68,6 @@ object DiscordRPC : Module( private var rpcAuth: Authentication? = null private var currentParty: AtomicReference = AtomicReference(null) - private var lastInvite: ActivityInviteEventData? = null - private var connectionTime: Long = 0 private var serverId: String? = null @@ -168,7 +159,7 @@ object DiscordRPC : Module( } } - fun join(id: String = lastInvite?.activity?.party?.id ?: "") { + fun join(id: String = rpc.activityManager.activity?.party?.id ?: "") { if (!allowed) return joinParty(rpcServer, apiVersion.value, rpcAuth?.accessToken ?: return, id) @@ -216,7 +207,6 @@ object DiscordRPC : Module( // Party features subscribe(DiscordEvent.ActivityJoinRequest) subscribe(DiscordEvent.ActivityJoin) - subscribe(DiscordEvent.ActivityInvite) //subscribe(DiscordEvent.LobbyUpdate) //subscribe(DiscordEvent.LobbyDelete) //subscribe(DiscordEvent.LobbyMemberConnect) @@ -252,21 +242,13 @@ object DiscordRPC : Module( } } - on { - lastInvite = data - - info(buildText { - clickEvent(ClickEvents.runCommand(";rpc accept")) { // TODO: Custom click events - literal("Click to join ${data.user.username}'s party.") - } - }) - - toast("You have been invited to play by ${lastInvite?.user?.username}") + // Event when someone would like to join your party + on { + // TODO: Implement a GUI for this } - on { - info("Joined ${lastInvite?.user?.username}'s party.") - } + // Event when someone joins your party + on {} on { LOG.error("Discord RPC error: ${data.message}") diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index a8e33651b..7f8c253a6 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -21,6 +21,17 @@ loom { enableTransitiveAccessWideners = true } +repositories { + // You can add more repositories here if you plan + // on using environment-specific dependencies. + // If you want to add a plugin-specific repository, + // you can add it to the `settings.gradle.kts` file + // in the base of the project and gradle will do the + // rest for you. + // If you want to add more global repositories, you can + // add them to the root build.gradle.kts file. +} + val common: Configuration by configurations.creating { configurations.compileClasspath.get().extendsFrom(this) configurations.runtimeClasspath.get().extendsFrom(this) @@ -56,7 +67,7 @@ dependencies { includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") includeLib("dev.babbaj:nether-pathfinder:1.5") - includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") + includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index f87f929f9..5beb8e0f2 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -35,10 +35,12 @@ loom { repositories { // You can add more repositories here if you plan // on using environment-specific dependencies. - // If you simply want to add a global repository, + // If you want to add a plugin-specific repository, // you can add it to the `settings.gradle.kts` file // in the base of the project and gradle will do the // rest for you. + // If you want to add more global repositories, you can + // add them to the root build.gradle.kts file. maven("https://thedarkcolour.github.io/KotlinForForge/") } @@ -81,7 +83,7 @@ dependencies { // Temporary, only works for production // See https://github.com/MinecraftForge/MinecraftForge/issues/8878 - shadowBundle("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") { + shadowBundle("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") { exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlinx") exclude(group = "org.slf4j") diff --git a/gradle.properties b/gradle.properties index 319b6b9df..e582d8bc9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,7 @@ mixinExtrasVersion=0.3.6 kotlinVersion=2.0.0 kotlinxCoroutinesVersion=1.9.0-RC javaVersion=17 +discordIPCVersion=15b3373013 # Fabric https://fabricmc.net/develop/ fabricLoaderVersion=0.15.11 diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index b7b100595..1b359c0da 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -20,8 +20,16 @@ loom { } repositories { - maven("https://maven.neoforged.net/releases/") + // You can add more repositories here if you plan + // on using environment-specific dependencies. + // If you want to add a plugin-specific repository, + // you can add it to the `settings.gradle.kts` file + // in the base of the project and gradle will do the + // rest for you. + // If you want to add more global repositories, you can + // add them to the root build.gradle.kts file. maven("https://thedarkcolour.github.io/KotlinForForge/") + maven("https://maven.neoforged.net/releases/") } val common: Configuration by configurations.creating { @@ -59,7 +67,7 @@ dependencies { includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") includeLib("dev.babbaj:nether-pathfinder:1.5") - includeLib("com.github.caoimhebyrne:KDiscordIPC:$discordIPCVersion") + includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge-neoforge:$kotlinForgeVersion") From d5a11cbdc45387daf9e2de2d4811b5a07e5879a2 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:03:24 -0400 Subject: [PATCH 46/56] Added test logging --- .../kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 6eeaa0f13..a94a9251e 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -244,11 +244,15 @@ object DiscordRPC : Module( // Event when someone would like to join your party on { + LOG.info("Received a party join request from ${data.userId}.") + rpc.activityManager.acceptJoinRequest(data.userId) // TODO: Implement a GUI for this } // Event when someone joins your party - on {} + on { + LOG.info("TEst invite join") + } on { LOG.error("Discord RPC error: ${data.message}") From 4962c3ff99f24105d47bcea736812baeea31c047 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:04:31 -0400 Subject: [PATCH 47/56] Update lambda.mixins.common.json --- common/src/main/resources/lambda.mixins.common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index 4a8f53c42..b7baba85d 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -4,7 +4,6 @@ "package": "com.lambda.mixin", "compatibilityLevel": "JAVA_17", "client": [ - "ClientConnectionMixin", "MinecraftClientMixin", "baritone.MixinBaritonePlayerContext", "baritone.MixinLookBehavior", @@ -17,6 +16,7 @@ "input.KeyboardMixin", "items.BarrierBlockMixin", "items.TridentMixin", + "network.ClientConnectionMixin", "render.BlockRenderManagerMixin", "render.CameraMixin", "render.ChatInputSuggestorMixin", From f8adab2c76daa02892555c0ac36be957741daea8 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:14:22 -0400 Subject: [PATCH 48/56] Temp force variable --- .../kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index a94a9251e..205346a62 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -52,7 +52,7 @@ object DiscordRPC : Module( /* Technical settings */ private var rpcServer by setting("RPC Server", "http://127.0.0.1:8080") { page == Page.Settings } // TODO: Change this in production private var apiVersion by setting("API Version", ApiVersion.V1) { page == Page.Settings } - private val delay by setting("Update Delay", 4, 4..60, 1, unit = "ms", visibility = { page == Page.Settings }) + private val delay by setting("Update Delay", 5000, 5000..10000, 1, unit = "ms", visibility = { page == Page.Settings }) /* Party settings */ private val enableParty by setting("Enable Party", true, description = "Allows you to create parties.") { page == Page.Party } @@ -75,7 +75,7 @@ object DiscordRPC : Module( * If the player can interact with the party system. */ private val allowed: Boolean - get() = rpcAuth != null && discordAuth != null && enableParty + get() = true //rpcAuth != null && discordAuth != null && enableParty private enum class Page { General, Settings, Party From 6975fa9e7439da81230c42e0ea024f5394b3d595 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:16:15 -0400 Subject: [PATCH 49/56] Temp: Local party --- .../com/lambda/module/modules/client/DiscordRPC.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 205346a62..485f1a265 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.delay import net.minecraft.network.encryption.NetworkEncryptionUtils import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket import java.math.BigInteger +import java.util.* import java.util.concurrent.atomic.AtomicReference object DiscordRPC : Module( @@ -75,7 +76,7 @@ object DiscordRPC : Module( * If the player can interact with the party system. */ private val allowed: Boolean - get() = true //rpcAuth != null && discordAuth != null && enableParty + get() = rpcAuth != null && discordAuth != null && enableParty private enum class Page { General, Settings, Party @@ -191,12 +192,15 @@ object DiscordRPC : Module( largeImage("lambda", Lambda.VERSION) smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - if (allowed && party != null) { + /*if (allowed && party != null) { party(party.id, party.players.size, party.settings.maxPlayers) secrets(party.joinSecret) } else { button("Download", "https://github.com/lambda-client/lambda") - } + }*/ + + party(UUID.randomUUID().toString(), 1, 2) + secrets(UUID.randomUUID().toString()) if (showTime) timestamps(startup) } From 074304ca8afbda9ea7ab409d76f7e1f4e1ca5a66 Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:18:17 -0400 Subject: [PATCH 50/56] Update lambda.mixins.common.json --- common/src/main/resources/lambda.mixins.common.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index b7baba85d..40719b398 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -17,6 +17,10 @@ "items.BarrierBlockMixin", "items.TridentMixin", "network.ClientConnectionMixin", + "network.ClientLoginNetworkMixin", + "network.HandshakeC2SPacketMixin", + "network.LoginHelloC2SPacketMixin", + "network.LoginKeyC2SPacketMixin", "render.BlockRenderManagerMixin", "render.CameraMixin", "render.ChatInputSuggestorMixin", From c0a2b8e471b995f7cb26c01448977cfe6204d96b Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:45:19 -0400 Subject: [PATCH 51/56] Fixed the update delay --- .../com/lambda/module/modules/client/DiscordRPC.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 485f1a265..943d78d15 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.delay import net.minecraft.network.encryption.NetworkEncryptionUtils import net.minecraft.network.packet.s2c.login.LoginHelloS2CPacket import java.math.BigInteger -import java.util.* import java.util.concurrent.atomic.AtomicReference object DiscordRPC : Module( @@ -148,7 +147,7 @@ object DiscordRPC : Module( while (true) { if (rpc.connected) update() - delay(delay * 1000L) + delay(delay.toLong()) } } @@ -192,21 +191,19 @@ object DiscordRPC : Module( largeImage("lambda", Lambda.VERSION) smallImage("https://mc-heads.net/avatar/${mc.gameProfile.id}/nohelm", mc.gameProfile.name) - /*if (allowed && party != null) { + if (allowed && party != null) { party(party.id, party.players.size, party.settings.maxPlayers) secrets(party.joinSecret) } else { button("Download", "https://github.com/lambda-client/lambda") - }*/ - - party(UUID.randomUUID().toString(), 1, 2) - secrets(UUID.randomUUID().toString()) + } if (showTime) timestamps(startup) } } private suspend fun KDiscordIPC.register(auth: ConnectionEvent.Connect.Login.Key) { + // TODO: Check if the rpc is already ready on { // Party features subscribe(DiscordEvent.ActivityJoinRequest) From b10d7f6a32a8b216075735c7c20d27b762f2fc83 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:50:44 -0400 Subject: [PATCH 52/56] Update Player.kt --- common/src/main/kotlin/com/lambda/util/extension/Player.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/util/extension/Player.kt b/common/src/main/kotlin/com/lambda/util/extension/Player.kt index 40918046b..38db84194 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Player.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Player.kt @@ -1,8 +1,7 @@ -package com.lambda.util.primitives.extension +package com.lambda.util.extension import com.mojang.authlib.GameProfile import net.minecraft.entity.player.PlayerEntity -import net.minecraft.util.Uuids val PlayerEntity.isOffline get() = gameProfile.isOffline From fda87bb45d69d1258eadc3e1e0a82c59709626f2 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:14:39 -0400 Subject: [PATCH 53/56] feat: cached downloads & request builder --- .../src/main/kotlin/com/lambda/http/Method.kt | 20 +++---- .../main/kotlin/com/lambda/http/Request.kt | 49 +++++++++++++-- .../kotlin/com/lambda/http/RequestBuilder.kt | 59 +++++++++++++++++++ .../kotlin/com/lambda/util/FolderRegister.kt | 1 + 4 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/http/RequestBuilder.kt diff --git a/common/src/main/kotlin/com/lambda/http/Method.kt b/common/src/main/kotlin/com/lambda/http/Method.kt index c3b4d3e89..db9d76817 100644 --- a/common/src/main/kotlin/com/lambda/http/Method.kt +++ b/common/src/main/kotlin/com/lambda/http/Method.kt @@ -2,16 +2,14 @@ package com.lambda.http /** * Enum representing HTTP methods. - * - * @property value The string representation of the HTTP method. */ -enum class Method(val value: String) { - GET("GET"), - HEAD("HEAD"), - POST("POST"), - PUT("PUT"), - DELETE("DELETE"), - OPTIONS("OPTIONS"), - TRACE("TRACE"), - PATCH("PATCH") +enum class Method { + GET, + HEAD, + POST, + PUT, + DELETE, + OPTIONS, + TRACE, + PATCH; } diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index acfddbea5..f975fa0ff 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -1,12 +1,16 @@ package com.lambda.http import com.lambda.Lambda -import com.lambda.Lambda.LOG +import com.lambda.util.FolderRegister.cache +import java.io.File import java.net.HttpURLConnection import java.net.URL +import java.time.Instant +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days /** - * Represents an HTTP request handler that allows making various HTTP requests. + * Represents an HTTP request. * * @property url The URL to which the request will be made. * @property method The HTTP method to be used for the request. Default is [Method.GET]. @@ -14,7 +18,7 @@ import java.net.URL * @property headers A map of headers to be included in the request. Default is an empty map. * @property config A lambda function to configure the HTTP connection. Default is an empty lambda. */ -class Request( +data class Request( val url: String, val method: Method = Method.GET, val parameters: Map = mapOf(), @@ -26,6 +30,43 @@ class Request( val canBeEncoded: Boolean get() = method != Method.POST && method != Method.PUT && method != Method.PATCH + /** + * Downloads the resource at the specified path and caches it for future use. + * + * @param path The path to the resource. + * @param maxAge The maximum age of the cached resource. Default is 4 days. + */ + fun maybeDownload(path: String, maxAge: Duration = 4.days): ByteArray { + val file = File("${cache}/${path.substringAfterLast("/").hashCode()}") + + if (file.exists() && Instant.now().toEpochMilli() - file.lastModified() < maxAge.inWholeMilliseconds) + return file.readBytes() + + file.writeText("") // Clear the file before writing to it. + + val url = URL( + if (parameters.isNotEmpty() && canBeEncoded) "$url?${parameters.query}" + else url + ) + + val connection = url.openConnection() as HttpURLConnection + config.invoke(connection) + + connection.requestMethod = method.name + + headers.forEach { (key, value) -> connection.setRequestProperty(key, value) } + + connection.connect() + + connection.inputStream.use { input -> + file.outputStream().use { output -> + input.copyTo(output) + } + } + + return file.readBytes() + } + /** * Executes the HTTP request synchronously. */ @@ -38,7 +79,7 @@ class Request( val connection = url.openConnection() as HttpURLConnection config.invoke(connection) - connection.requestMethod = method.value + connection.requestMethod = method.name headers.forEach { (key, value) -> connection.setRequestProperty(key, Lambda.gson.toJson(value)) } diff --git a/common/src/main/kotlin/com/lambda/http/RequestBuilder.kt b/common/src/main/kotlin/com/lambda/http/RequestBuilder.kt new file mode 100644 index 000000000..d380a4847 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/http/RequestBuilder.kt @@ -0,0 +1,59 @@ +package com.lambda.http + +import java.net.HttpURLConnection + +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +@DslMarker +annotation class RequestDsl + +@RequestDsl +class RequestBuilder( + private val url: String, +) { + private var method: Method = Method.GET + private val parameters: MutableMap = mutableMapOf() + private val headers: MutableMap = mutableMapOf() + private var config: ((HttpURLConnection) -> Unit) = {} + + /** + * Sets the HTTP method to be used for the request. + */ + fun method(method: Method): RequestBuilder { + this.method = method + return this + } + + /** + * Sets the query parameters to be included in the request. + */ + fun parameters(parameters: Map): RequestBuilder { + this.parameters.putAll(parameters) + return this + } + + /** + * Sets the headers to be included in the request. + */ + fun headers(headers: Map): RequestBuilder { + this.headers.putAll(headers) + return this + } + + /** + * Sets the lambda function to configure the HTTP connection. + */ + fun config(config: (HttpURLConnection) -> Unit): RequestBuilder { + this.config = config + return this + } + + fun build() = Request(url, method, parameters, headers, config) +} + +/** + * Creates an HTTP request. + * + * @param url The URL to which the request will be made. + * @param block A lambda function to configure the request. + */ +inline fun request(url: String, block: (@RequestDsl RequestBuilder).() -> Unit) = RequestBuilder(url).apply(block).build() diff --git a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt index ee6ffc8e6..26af87db9 100644 --- a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt +++ b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt @@ -28,6 +28,7 @@ object FolderRegister { val config: File = File(lambda, "config") val packetLogs: File = File(lambda, "packet-log") val replay: File = File(lambda, "replay") + val cache: File = File(lambda, "cache") fun File.createIfNotExists() { if (!exists()) { From 7e7182e44bc3e9ba995ddff2041e924664e0c1d7 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:18:46 -0400 Subject: [PATCH 54/56] added request builder to endpoints --- .../http/api/rpc/v1/endpoints/CreateParty.kt | 20 +++++++------- .../http/api/rpc/v1/endpoints/DeleteParty.kt | 15 +++++------ .../http/api/rpc/v1/endpoints/GetParty.kt | 16 +++++------ .../http/api/rpc/v1/endpoints/JoinParty.kt | 22 ++++++++------- .../http/api/rpc/v1/endpoints/LeaveParty.kt | 15 +++++------ .../lambda/http/api/rpc/v1/endpoints/Login.kt | 12 +++++---- .../http/api/rpc/v1/endpoints/UpdateParty.kt | 27 ++++++++++--------- 7 files changed, 67 insertions(+), 60 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt index d4e207787..d6e1b15aa 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/CreateParty.kt @@ -3,6 +3,7 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Method import com.lambda.http.Request import com.lambda.http.api.rpc.v1.models.Party +import com.lambda.http.request fun createParty( endpoint: String, @@ -18,16 +19,17 @@ fun createParty( // example: true public: Boolean = true, ) = - Request( - "$endpoint/api/$version/party/create", - Method.POST, - parameters = + request("$endpoint/api/$version/party/create") { + method(Method.POST) + + parameters( mapOf( "max_players" to maxPlayers, "public" to public, - ), - headers = - mapOf( - "Authorization" to "Bearer $accessToken" ) - ).json() + ) + + headers( + mapOf("Authorization" to "Bearer $accessToken") + ) + }.json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt index 2d7e0c82a..6a124ae9d 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/DeleteParty.kt @@ -3,18 +3,17 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Method import com.lambda.http.Request import com.lambda.http.api.rpc.v1.models.Party +import com.lambda.http.request fun deleteParty( endpoint: String, version: String, accessToken: String, ) = - Request( - "$endpoint/api/$version/party/delete", - Method.DELETE, - headers = - mapOf( - "Authorization" to "Bearer $accessToken" - ) - ).json() + request("$endpoint/api/$version/party/delete") { + method(Method.DELETE) + headers( + mapOf("Authorization" to "Bearer $accessToken") + ) + }.json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt index b917db982..eb12d3a1e 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/GetParty.kt @@ -3,17 +3,17 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Method import com.lambda.http.Request import com.lambda.http.api.rpc.v1.models.Party +import com.lambda.http.request fun createParty( endpoint: String, version: String, accessToken: String, ) = - Request( - "$endpoint/api/$version/party", - Method.GET, - headers = - mapOf( - "Authorization" to "Bearer $accessToken" - ) - ).json() + request("$endpoint/api/$version/party") { + method(Method.POST) + + headers( + mapOf("Authorization" to "Bearer $accessToken") + ) + }.json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt index 1fe87d035..e91d207db 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/JoinParty.kt @@ -3,6 +3,7 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Request import com.lambda.http.Method import com.lambda.http.api.rpc.v1.models.Party +import com.lambda.http.request fun joinParty( endpoint: String, @@ -13,15 +14,16 @@ fun joinParty( // example: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" partyId: String, ) = - Request( - "$endpoint/api/$version/party/join", - Method.PUT, - parameters = - mapOf( - "id" to partyId - ), - headers = + request("$endpoint/api/$version/party/join") { + method(Method.PUT) + + parameters( mapOf( - "Authorization" to "Bearer $accessToken" + "id" to partyId, ) - ).json() + ) + + headers( + mapOf("Authorization" to "Bearer $accessToken") + ) + }.json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt index f057a9a5e..6a9a67a75 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/LeaveParty.kt @@ -3,18 +3,17 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Method import com.lambda.http.Request import com.lambda.http.api.rpc.v1.models.Party +import com.lambda.http.request fun leaveParty( endpoint: String, version: String, accessToken: String, ) = - Request( - "$endpoint/api/$version/party/leave", - Method.PUT, - headers = - mapOf( - "Authorization" to "Bearer $accessToken" - ) - ).json() + request("$endpoint/api/$version/party/leave") { + method(Method.PUT) + headers( + mapOf("Authorization" to "Bearer $accessToken") + ) + }.json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt index c2c79fd5e..1912c61d6 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/Login.kt @@ -3,6 +3,7 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Method import com.lambda.http.Request import com.lambda.http.api.rpc.v1.models.Authentication +import com.lambda.http.request fun login( endpoint: String, @@ -20,13 +21,14 @@ fun login( // example: 069a79f444e94726a5befca90e38aaf5 hash: String ) = - Request( - "$endpoint/api/$version/login", - Method.POST, - parameters = + request("$endpoint/api/$version/login") { + method(Method.POST) + + parameters( mapOf( "token" to discordToken, "username" to username, "hash" to hash ) - ).json() + ) + }.json() diff --git a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt index 69019c223..6a8e3d85a 100644 --- a/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt +++ b/common/src/main/kotlin/com/lambda/http/api/rpc/v1/endpoints/UpdateParty.kt @@ -3,6 +3,7 @@ package com.lambda.http.api.rpc.v1.endpoints import com.lambda.http.Method import com.lambda.http.Request import com.lambda.http.api.rpc.v1.models.Party +import com.lambda.http.request fun editParty( endpoint: String, @@ -17,16 +18,18 @@ fun editParty( // If false can only be joined by invite. // example: true public: Boolean = true, -) = Request( - "$endpoint/api/$version/party/edit", - Method.PATCH, - parameters = - mapOf( - "max_players" to maxPlayers, - "public" to public, - ), - headers = - mapOf( - "Authorization" to "Bearer $accessToken" +) = + request("$endpoint/api/$version/party/edit") { + method(Method.PATCH) + + parameters( + mapOf( + "max_players" to maxPlayers, + "public" to public, + ) + ) + + headers( + mapOf("Authorization" to "Bearer $accessToken") ) - ).json() + }.json() From 4943a728cdc9296255c84ade149ed01499a87551 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:25:00 -0400 Subject: [PATCH 55/56] several fixes --- .../src/main/kotlin/com/lambda/command/commands/RpcCommand.kt | 2 +- .../kotlin/com/lambda/module/modules/client/DiscordRPC.kt | 4 ++-- .../kotlin/com/lambda/module/modules/movement/BackTrack.kt | 4 ++-- common/src/main/kotlin/com/lambda/threading/Threading.kt | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt index 40c84ea08..750ec1669 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/RpcCommand.kt @@ -6,7 +6,7 @@ import com.lambda.brigadier.argument.value import com.lambda.brigadier.argument.word import com.lambda.command.LambdaCommand import com.lambda.module.modules.client.DiscordRPC -import com.lambda.util.primitives.extension.CommandBuilder +import com.lambda.util.extension.CommandBuilder object RpcCommand : LambdaCommand( name = "rpc", diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt index 943d78d15..18d9f06f8 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/DiscordRPC.kt @@ -3,7 +3,7 @@ package com.lambda.module.modules.client import com.lambda.Lambda import com.lambda.Lambda.LOG import com.lambda.Lambda.mc -import com.lambda.event.EventFlow.ioScope +import com.lambda.event.EventFlow import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener @@ -60,7 +60,7 @@ object DiscordRPC : Module( private val maxPlayers by setting("Max Players", 10, 2..20, visibility = { page == Page.Party }).apply { onValueChange { _, _ -> edit() } } private val public by setting("Public Party", false, description = "Allow anyone to join your party.") { page == Page.Party }.apply { onValueChange { _, _ -> edit() } } - private val rpc = KDiscordIPC(Lambda.APP_ID, scope = ioScope) + private val rpc = KDiscordIPC(Lambda.APP_ID, scope = EventFlow.lambdaScope) private var startup = System.currentTimeMillis() private val dimensionRegex = Regex("""\b\w+_\w+\b""") diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt index bfe0976b6..99f71b5b7 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt @@ -146,7 +146,7 @@ object BackTrack : Module( event.cancel() } - listener { + listener { receivePool.clear() sendPool.clear() } @@ -188,4 +188,4 @@ object BackTrack : Module( connection.sendPacketSilently(packet) } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/threading/Threading.kt b/common/src/main/kotlin/com/lambda/threading/Threading.kt index 9a059fb41..05e04d093 100644 --- a/common/src/main/kotlin/com/lambda/threading/Threading.kt +++ b/common/src/main/kotlin/com/lambda/threading/Threading.kt @@ -41,7 +41,7 @@ inline fun runConcurrent(crossinline block: suspend () -> Unit) = } inline fun runIO(crossinline block: suspend () -> Unit) = - EventFlow.ioScope.launch { + EventFlow.lambdaScope.launch(Dispatchers.IO) { block() } @@ -131,4 +131,4 @@ inline fun runSafeGameConcurrent(crossinline block: SafeContext.() -> Unit) { * @param block The task to be executed on the game's main thread within a safe context. */ suspend inline fun awaitMainThread(noinline block: SafeContext.() -> T) = - CompletableFuture.supplyAsync({ runSafe { block() } }, mc).await() ?: throw IllegalStateException("Unsafe") \ No newline at end of file + CompletableFuture.supplyAsync({ runSafe { block() } }, mc).await() ?: throw IllegalStateException("Unsafe") From ba1b21bcf93851298c71690e960a1f4bba9747c1 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:25:51 -0400 Subject: [PATCH 56/56] Update ConnectionEvent.kt --- .../src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt index 5276c268b..bcff6e801 100644 --- a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt @@ -13,12 +13,10 @@ import javax.crypto.SecretKey * Sealed class representing connection events. */ sealed class ConnectionEvent : Event { - /** * Sealed class representing various stages of connection establishment. */ sealed class Connect { - /** * Event representing a pre-connection attempt. * @property address The address of the connection attempt. @@ -51,7 +49,6 @@ sealed class ConnectionEvent : Event { * Sealed class representing login-related connection events. */ sealed class Login : ConnectionEvent() { - /** * Event representing a hello message during login. * @property name The name associated with the login.