summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/data/bazaar
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/data/bazaar')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/bazaar/BazaarJson.kt36
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt100
2 files changed, 136 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/data/bazaar/BazaarJson.kt b/src/main/java/at/hannibal2/skyhanni/data/bazaar/BazaarJson.kt
new file mode 100644
index 000000000..350f33db2
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/bazaar/BazaarJson.kt
@@ -0,0 +1,36 @@
+package at.hannibal2.skyhanni.data.bazaar
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+class BazaarApiResponseJson(
+ @Expose val success: Boolean,
+ @Expose val cause: String,
+ @Expose val lastUpdated: Long,
+ @Expose val products: Map<String, BazaarProduct>,
+)
+
+data class BazaarProduct(
+ @Expose @SerializedName("product_id") val productId: String,
+ @Expose @SerializedName("quick_status") val quickStatus: BazaarQuickStatus,
+ @Expose @SerializedName("sell_summary") val sellSummary: List<BazaarSummary>,
+ @Expose @SerializedName("buy_summary") val buySummary: List<BazaarSummary>,
+)
+
+class BazaarQuickStatus(
+ @Expose val productId: String,
+ @Expose val sellPrice: Double,
+ @Expose val sellVolume: Long,
+ @Expose val sellMovingWeek: Long,
+ @Expose val sellOrders: Long,
+ @Expose val buyPrice: Double,
+ @Expose val buyVolume: Long,
+ @Expose val buyMovingWeek: Long,
+ @Expose val buyOrders: Long,
+)
+
+data class BazaarSummary(
+ @Expose val amount: Long,
+ @Expose val pricePerUnit: Double,
+ @Expose val orders: Long,
+)
diff --git a/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt b/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt
new file mode 100644
index 000000000..148fb633a
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt
@@ -0,0 +1,100 @@
+package at.hannibal2.skyhanni.data.bazaar
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.config.ConfigManager
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.features.inventory.bazaar.BazaarData
+import at.hannibal2.skyhanni.test.command.ErrorManager
+import at.hannibal2.skyhanni.utils.APIUtil
+import at.hannibal2.skyhanni.utils.ChatUtils
+import at.hannibal2.skyhanni.utils.ItemUtils.itemName
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NEUInternalName
+import at.hannibal2.skyhanni.utils.NEUItems
+import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.fromJson
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
+
+// https://api.hypixel.net/#tag/SkyBlock/paths/~1v2~1skyblock~1bazaar/get
+object HypixelBazaarFetcher {
+ private const val URL = "https://api.hypixel.net/v2/skyblock/bazaar"
+ private const val HIDDEN_FAILED_ATTEMPTS = 3
+
+ var latestProductInformation = mapOf<NEUInternalName, BazaarData>()
+ private var nextFetchTime = SimpleTimeMark.farPast()
+ private var failedAttempts = 0
+ private var nextFetchIsManual = false
+
+ @SubscribeEvent
+ fun onTick(event: LorenzTickEvent) {
+ if (!canFetch()) return
+ SkyHanniMod.coroutineScope.launch {
+ fetchAndProcessBazaarData()
+ }
+ }
+
+ private suspend fun fetchAndProcessBazaarData() {
+ nextFetchTime = SimpleTimeMark.now() + 2.minutes
+ val fetchType = if (nextFetchIsManual) "manual" else "automatic"
+ nextFetchIsManual = false
+ try {
+ val jsonResponse = withContext(Dispatchers.IO) { APIUtil.getJSONResponse(URL) }.asJsonObject
+ val response = ConfigManager.gson.fromJson<BazaarApiResponseJson>(jsonResponse)
+ if (response.success) {
+ latestProductInformation = process(response.products)
+ failedAttempts = 0
+ } else {
+ onError(fetchType, Exception("success=false, cause=${response.cause}"))
+ }
+ } catch (e: Exception) {
+ onError(fetchType, e)
+ }
+ }
+
+ private fun process(products: Map<String, BazaarProduct>) = products.mapNotNull { (key, product) ->
+ val internalName = NEUItems.transHypixelNameToInternalName(key)
+ val sellOfferPrice = product.buySummary.minOfOrNull { it.pricePerUnit } ?: 0.0
+ val insantBuyPrice = product.sellSummary.maxOfOrNull { it.pricePerUnit } ?: 0.0
+ if (internalName.getItemStackOrNull() == null) {
+ // Items that exist in Hypixel's Bazaar API, but not in NEU repo (not visible in in the ingame bazaar).
+ // Should only include Enchants
+ if (LorenzUtils.debug)
+ println("Unknown bazaar product: $key/$internalName")
+ return@mapNotNull null
+ }
+ internalName to BazaarData(internalName.itemName, sellOfferPrice, insantBuyPrice, product)
+ }.toMap()
+
+ private fun onError(fetchType: String, e: Exception) {
+ val userMessage = "Failed fetching bazaar price data from hypixel"
+ failedAttempts++
+ if (failedAttempts <= HIDDEN_FAILED_ATTEMPTS) {
+ nextFetchTime = SimpleTimeMark.now() + 15.seconds
+ ChatUtils.debug("$userMessage. (errorMessage=${e.message}, failedAttepmts=$failedAttempts, $fetchType")
+ e.printStackTrace()
+ } else {
+ nextFetchTime = SimpleTimeMark.now() + 15.minutes
+ ErrorManager.logErrorWithData(
+ e,
+ userMessage,
+ "fetchType" to fetchType,
+ "failedAttepmts" to failedAttempts,
+ )
+ }
+ }
+
+ fun fetchNow() {
+ failedAttempts = 0
+ nextFetchIsManual = true
+ nextFetchTime = SimpleTimeMark.now()
+ ChatUtils.chat("Manually updating the bazaar prices right now..")
+ }
+
+ private fun canFetch() = LorenzUtils.onHypixel && nextFetchTime.isInPast()
+}