From 8e386bcf8d10784fc7784333be9e4470598c71d4 Mon Sep 17 00:00:00 2001
From: Linnea Gräf <nea@nea.moe>
Date: Thu, 30 Jan 2025 20:11:37 +0100
Subject: feat: Add ledger event as a baseclass for events with error reporting

---
 .../ledger/mixin/AccessorContainerDispenser.java   | 12 ++++
 .../nea/ledger/mixin/AccessorContainerHopper.java  | 13 +++++
 .../main/kotlin/moe/nea/ledger/ItemIdProvider.kt   |  4 +-
 mod/src/main/kotlin/moe/nea/ledger/Ledger.kt       |  5 +-
 mod/src/main/kotlin/moe/nea/ledger/QueryCommand.kt |  2 +-
 .../kotlin/moe/nea/ledger/TelemetryProvider.kt     | 66 ---------------------
 .../moe/nea/ledger/events/BeforeGuiAction.kt       | 12 +++-
 .../kotlin/moe/nea/ledger/events/LedgerEvent.kt    | 22 +++++++
 .../moe/nea/ledger/telemetry/GuiContextValue.kt    | 16 +++++
 .../moe/nea/ledger/telemetry/TelemetryProvider.kt  | 68 ++++++++++++++++++++++
 .../main/kotlin/moe/nea/ledger/utils/ScreenUtil.kt | 29 +++++++++
 .../kotlin/moe/nea/ledger/utils/telemetry/Span.kt  |  3 +-
 12 files changed, 180 insertions(+), 72 deletions(-)
 create mode 100644 mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerDispenser.java
 create mode 100644 mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerHopper.java
 delete mode 100644 mod/src/main/kotlin/moe/nea/ledger/TelemetryProvider.kt
 create mode 100644 mod/src/main/kotlin/moe/nea/ledger/events/LedgerEvent.kt
 create mode 100644 mod/src/main/kotlin/moe/nea/ledger/telemetry/GuiContextValue.kt
 create mode 100644 mod/src/main/kotlin/moe/nea/ledger/telemetry/TelemetryProvider.kt
 create mode 100644 mod/src/main/kotlin/moe/nea/ledger/utils/ScreenUtil.kt

diff --git a/mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerDispenser.java b/mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerDispenser.java
new file mode 100644
index 0000000..a3d32c4
--- /dev/null
+++ b/mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerDispenser.java
@@ -0,0 +1,12 @@
+package moe.nea.ledger.mixin;
+
+import net.minecraft.inventory.ContainerDispenser;
+import net.minecraft.inventory.IInventory;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ContainerDispenser.class)
+public interface AccessorContainerDispenser {
+	@Accessor("dispenserInventory")
+	IInventory getDispenserInventory_ledger();
+}
diff --git a/mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerHopper.java b/mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerHopper.java
new file mode 100644
index 0000000..ee29d4f
--- /dev/null
+++ b/mod/src/main/java/moe/nea/ledger/mixin/AccessorContainerHopper.java
@@ -0,0 +1,13 @@
+package moe.nea.ledger.mixin;
+
+import net.minecraft.inventory.ContainerDispenser;
+import net.minecraft.inventory.ContainerHopper;
+import net.minecraft.inventory.IInventory;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ContainerHopper.class)
+public interface AccessorContainerHopper {
+	@Accessor("hopperInventory")
+	IInventory getHopperInventory_ledger();
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/mod/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
index 0bacf32..ff2c691 100644
--- a/mod/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
+++ b/mod/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
@@ -20,12 +20,12 @@ class ItemIdProvider {
 	@SubscribeEvent
 	fun onMouseInput(event: GuiScreenEvent.MouseInputEvent.Pre) {
 		if (Mouse.getEventButton() == -1) return
-		MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui))
+		BeforeGuiAction(event.gui).post()
 	}
 
 	@SubscribeEvent
 	fun onKeyInput(event: GuiScreenEvent.KeyboardInputEvent.Pre) {
-		MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui))
+		BeforeGuiAction(event.gui).post()
 	}
 
 	private val knownNames = mutableMapOf<String, ItemId>()
diff --git a/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt b/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt
index 9bbc8e1..6d3c592 100644
--- a/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt
+++ b/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt
@@ -42,6 +42,7 @@ import moe.nea.ledger.modules.NpcDetection
 import moe.nea.ledger.modules.PestRepellentDetection
 import moe.nea.ledger.modules.UpdateChecker
 import moe.nea.ledger.modules.VisitorDetection
+import moe.nea.ledger.telemetry.TelemetryProvider
 import moe.nea.ledger.utils.ErrorUtil
 import moe.nea.ledger.utils.MinecraftExecutor
 import moe.nea.ledger.utils.di.DI
@@ -112,7 +113,9 @@ class Ledger {
 			tickQueue.add(runnable)
 		}
 
-		val di = DI()
+		private val di = DI()
+
+		fun leakDI() = di
 	}
 
 	@Mod.EventHandler
diff --git a/mod/src/main/kotlin/moe/nea/ledger/QueryCommand.kt b/mod/src/main/kotlin/moe/nea/ledger/QueryCommand.kt
index abdc13a..80dd54c 100644
--- a/mod/src/main/kotlin/moe/nea/ledger/QueryCommand.kt
+++ b/mod/src/main/kotlin/moe/nea/ledger/QueryCommand.kt
@@ -172,7 +172,7 @@ class QueryCommand : CommandBase() {
 		override val name: String
 			get() = "withitem"
 
-		private val itemIdProvider = Ledger.di.provide<ItemIdProvider>() // TODO: close this escape hatch
+		private val itemIdProvider = Ledger.leakDI().provide<ItemIdProvider>() // TODO: close this escape hatch
 		override fun getFilter(text: String): BooleanExpression {
 			return Clause { column(DBItemEntry.itemId) like text }
 		}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/TelemetryProvider.kt b/mod/src/main/kotlin/moe/nea/ledger/TelemetryProvider.kt
deleted file mode 100644
index d9c7108..0000000
--- a/mod/src/main/kotlin/moe/nea/ledger/TelemetryProvider.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package moe.nea.ledger
-
-import com.google.gson.JsonArray
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
-import moe.nea.ledger.gen.BuildConfig
-import moe.nea.ledger.utils.di.DI
-import moe.nea.ledger.utils.di.DIProvider
-import moe.nea.ledger.utils.telemetry.CommonKeys
-import moe.nea.ledger.utils.telemetry.ContextValue
-import moe.nea.ledger.utils.telemetry.EventRecorder
-import moe.nea.ledger.utils.telemetry.JsonElementContext
-import moe.nea.ledger.utils.telemetry.LoggingEventRecorder
-import moe.nea.ledger.utils.telemetry.Span
-import net.minecraft.client.Minecraft
-import net.minecraft.util.Session
-import net.minecraftforge.fml.common.Loader
-
-object TelemetryProvider {
-	fun injectTo(di: DI) {
-		di.register(
-			EventRecorder::class.java,
-			if (DevUtil.isDevEnv) DIProvider.singeleton(LoggingEventRecorder(Ledger.logger, true))
-			else DIProvider.singeleton(
-				LoggingEventRecorder(Ledger.logger, false)) // TODO: replace with upload to server
-		)
-	}
-
-	val USER = "minecraft_user"
-	val MINECRAFT_VERSION = "minecraft_version"
-	val MODS = "mods"
-
-	class MinecraftUser(val session: Session) : ContextValue {
-		override fun serialize(): JsonElement {
-			val obj = JsonObject()
-			obj.addProperty("uuid", session.playerID)
-			obj.addProperty("name", session.username)
-			return obj
-		}
-	}
-
-	fun setupDefaultSpan() {
-		val sp = Span.current()
-		sp.add(USER, MinecraftUser(Minecraft.getMinecraft().session))
-		sp.add(MINECRAFT_VERSION, ContextValue.compound(
-			"static" to "1.8.9",
-			"rt" to Minecraft.getMinecraft().version,
-		))
-		val mods = JsonArray()
-		Loader.instance().activeModList.map {
-			val obj = JsonObject()
-			obj.addProperty("id", it.modId)
-			obj.addProperty("version", it.version)
-			obj.addProperty("displayVersion", it.displayVersion)
-			obj
-		}.forEach(mods::add)
-		sp.add(MODS, JsonElementContext(mods))
-		sp.add(CommonKeys.VERSION, ContextValue.string(BuildConfig.FULL_VERSION))
-		sp.add(CommonKeys.COMMIT_VERSION, ContextValue.string(BuildConfig.GIT_COMMIT))
-	}
-
-	fun setupFor(di: DI) {
-		injectTo(di)
-		setupDefaultSpan()
-	}
-}
\ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/events/BeforeGuiAction.kt b/mod/src/main/kotlin/moe/nea/ledger/events/BeforeGuiAction.kt
index 098912a..7f6eae9 100644
--- a/mod/src/main/kotlin/moe/nea/ledger/events/BeforeGuiAction.kt
+++ b/mod/src/main/kotlin/moe/nea/ledger/events/BeforeGuiAction.kt
@@ -1,11 +1,21 @@
 package moe.nea.ledger.events
 
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.ledger.telemetry.GuiContextValue
+import moe.nea.ledger.utils.telemetry.ContextValue
 import net.minecraft.client.gui.GuiScreen
 import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.client.gui.inventory.GuiContainer
 import net.minecraft.inventory.ContainerChest
 import net.minecraftforge.fml.common.eventhandler.Event
 
-data class BeforeGuiAction(val gui: GuiScreen) : Event() {
+data class BeforeGuiAction(val gui: GuiScreen) : LedgerEvent() {
 	val chest = gui as? GuiChest
 	val chestSlots = chest?.inventorySlots as ContainerChest?
+	override fun serialize(): JsonElement {
+		return JsonObject().apply {
+			add("gui", GuiContextValue(gui).serialize())
+		}
+	}
 }
diff --git a/mod/src/main/kotlin/moe/nea/ledger/events/LedgerEvent.kt b/mod/src/main/kotlin/moe/nea/ledger/events/LedgerEvent.kt
new file mode 100644
index 0000000..cbb3f81
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/events/LedgerEvent.kt
@@ -0,0 +1,22 @@
+package moe.nea.ledger.events
+
+import moe.nea.ledger.Ledger
+import moe.nea.ledger.utils.ErrorUtil
+import moe.nea.ledger.utils.telemetry.CommonKeys
+import moe.nea.ledger.utils.telemetry.ContextValue
+import net.minecraftforge.common.MinecraftForge
+import net.minecraftforge.fml.common.eventhandler.Event
+
+abstract class LedgerEvent : Event(), ContextValue {
+	fun post() {
+		Ledger.leakDI()
+			.provide<ErrorUtil>()
+			.catch(
+				CommonKeys.EVENT_MESSAGE to ContextValue.string("Error during event execution"),
+				"event_instance" to this,
+				"event_type" to ContextValue.string(javaClass.name)
+			) {
+				MinecraftForge.EVENT_BUS.post(this)
+			}
+	}
+}
\ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/telemetry/GuiContextValue.kt b/mod/src/main/kotlin/moe/nea/ledger/telemetry/GuiContextValue.kt
new file mode 100644
index 0000000..2d7db39
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/telemetry/GuiContextValue.kt
@@ -0,0 +1,16 @@
+package moe.nea.ledger.telemetry
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.ledger.utils.ScreenUtil
+import moe.nea.ledger.utils.telemetry.ContextValue
+import net.minecraft.client.gui.GuiScreen
+
+class GuiContextValue(val gui: GuiScreen) : ContextValue {
+	override fun serialize(): JsonElement {
+		return JsonObject().apply {
+			addProperty("class", gui.javaClass.name)
+			addProperty("name", ScreenUtil.estimateName(gui))
+		}
+	}
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/telemetry/TelemetryProvider.kt b/mod/src/main/kotlin/moe/nea/ledger/telemetry/TelemetryProvider.kt
new file mode 100644
index 0000000..c2fff23
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/telemetry/TelemetryProvider.kt
@@ -0,0 +1,68 @@
+package moe.nea.ledger.telemetry
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.ledger.DevUtil
+import moe.nea.ledger.Ledger
+import moe.nea.ledger.gen.BuildConfig
+import moe.nea.ledger.utils.di.DI
+import moe.nea.ledger.utils.di.DIProvider
+import moe.nea.ledger.utils.telemetry.CommonKeys
+import moe.nea.ledger.utils.telemetry.ContextValue
+import moe.nea.ledger.utils.telemetry.EventRecorder
+import moe.nea.ledger.utils.telemetry.JsonElementContext
+import moe.nea.ledger.utils.telemetry.LoggingEventRecorder
+import moe.nea.ledger.utils.telemetry.Span
+import net.minecraft.client.Minecraft
+import net.minecraft.util.Session
+import net.minecraftforge.fml.common.Loader
+
+object TelemetryProvider {
+	fun injectTo(di: DI) {
+		di.register(
+			EventRecorder::class.java,
+			if (DevUtil.isDevEnv) DIProvider.singeleton(LoggingEventRecorder(Ledger.logger, true))
+			else DIProvider.singeleton(
+				LoggingEventRecorder(Ledger.logger, false)) // TODO: replace with upload to server
+		)
+	}
+
+	val USER = "minecraft_user"
+	val MINECRAFT_VERSION = "minecraft_version"
+	val MODS = "mods"
+
+	class MinecraftUser(val session: Session) : ContextValue {
+		override fun serialize(): JsonElement {
+			val obj = JsonObject()
+			obj.addProperty("uuid", session.playerID)
+			obj.addProperty("name", session.username)
+			return obj
+		}
+	}
+
+	fun setupDefaultSpan() {
+		val sp = Span.rootSpan
+		sp.add(USER, MinecraftUser(Minecraft.getMinecraft().session))
+		sp.add(MINECRAFT_VERSION, ContextValue.compound(
+			"static" to "1.8.9",
+			"rt" to Minecraft.getMinecraft().version,
+		))
+		val mods = JsonArray()
+		Loader.instance().activeModList.map {
+			val obj = JsonObject()
+			obj.addProperty("id", it.modId)
+			obj.addProperty("version", it.version)
+			obj.addProperty("displayVersion", it.displayVersion)
+			obj
+		}.forEach(mods::add)
+		sp.add(MODS, JsonElementContext(mods))
+		sp.add(CommonKeys.VERSION, ContextValue.string(BuildConfig.FULL_VERSION))
+		sp.add(CommonKeys.COMMIT_VERSION, ContextValue.string(BuildConfig.GIT_COMMIT))
+	}
+
+	fun setupFor(di: DI) {
+		injectTo(di)
+		setupDefaultSpan()
+	}
+}
\ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/ScreenUtil.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/ScreenUtil.kt
new file mode 100644
index 0000000..0305126
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/utils/ScreenUtil.kt
@@ -0,0 +1,29 @@
+package moe.nea.ledger.utils
+
+import moe.nea.ledger.mixin.AccessorContainerDispenser
+import moe.nea.ledger.mixin.AccessorContainerHopper
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.client.gui.inventory.GuiContainer
+import net.minecraft.inventory.ContainerChest
+import net.minecraft.inventory.IInventory
+
+object ScreenUtil {
+	fun estimateInventory(screen: GuiScreen?): IInventory? {
+		if (screen !is GuiContainer) {
+			return null
+		}
+		val container = screen.inventorySlots ?: return null
+		if (container is ContainerChest)
+			return container.lowerChestInventory
+		if (container is AccessorContainerDispenser)
+			return container.dispenserInventory_ledger
+		if (container is AccessorContainerHopper)
+			return container.hopperInventory_ledger
+		return null
+
+	}
+
+	fun estimateName(screen: GuiScreen?): String? {
+		return estimateInventory(screen)?.name
+	}
+}
\ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt
index 0d680a9..8b8e284 100644
--- a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt
+++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt
@@ -2,9 +2,10 @@ package moe.nea.ledger.utils.telemetry
 
 class Span(val parent: Span?) : AutoCloseable {
 	companion object {
+		val rootSpan = Span(null)
 		private val _current = object : InheritableThreadLocal<Span>() {
 			override fun initialValue(): Span {
-				return Span(null)
+				return Span(rootSpan)
 			}
 
 			override fun childValue(parentValue: Span?): Span {
-- 
cgit