diff options
author | hannibal2 <24389977+hannibal002@users.noreply.github.com> | 2024-05-02 16:01:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-02 16:01:39 +0200 |
commit | 6c8fc0df08331da7e49796985a1df5f14e6d7ac3 (patch) | |
tree | 3fb6f00ae187e3197d98de37d72168a860ee4d71 | |
parent | 4353efaa3d18a61f49f79273bea63303afcab7e1 (diff) | |
download | skyhanni-6c8fc0df08331da7e49796985a1df5f14e6d7ac3.tar.gz skyhanni-6c8fc0df08331da7e49796985a1df5f14e6d7ac3.tar.bz2 skyhanni-6c8fc0df08331da7e49796985a1df5f14e6d7ac3.zip |
Backend: Bazaar Fetcher (#1597)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Co-authored-by: Cal <cwolfson58@gmail.com>
12 files changed, 197 insertions, 38 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 6a0f0c9dd..dcb610423 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -52,6 +52,7 @@ import at.hannibal2.skyhanni.data.SlayerAPI import at.hannibal2.skyhanni.data.TitleData import at.hannibal2.skyhanni.data.TitleManager import at.hannibal2.skyhanni.data.TrackerManager +import at.hannibal2.skyhanni.data.bazaar.HypixelBazaarFetcher import at.hannibal2.skyhanni.data.hypixel.chat.PlayerChatManager import at.hannibal2.skyhanni.data.hypixel.chat.PlayerNameFormatter import at.hannibal2.skyhanni.data.jsonobjects.local.FriendsJson @@ -545,6 +546,7 @@ class SkyHanniMod { loadModule(ChromaManager) loadModule(ContributorManager) loadModule(TabComplete) + loadModule(HypixelBazaarFetcher) // APIs loadModule(BazaarApi()) diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt index b8a0f2c54..b4a2c336e 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -12,6 +12,7 @@ import at.hannibal2.skyhanni.data.GuiEditManager import at.hannibal2.skyhanni.data.PartyAPI import at.hannibal2.skyhanni.data.SackAPI import at.hannibal2.skyhanni.data.TitleManager +import at.hannibal2.skyhanni.data.bazaar.HypixelBazaarFetcher import at.hannibal2.skyhanni.features.bingo.card.BingoCardDisplay import at.hannibal2.skyhanni.features.bingo.card.nextstephelper.BingoNextStepHelper import at.hannibal2.skyhanni.features.chat.Translator @@ -380,6 +381,10 @@ object Commands { "shupdate", "Updates the mod to the specified update stream." ) { forceUpdate(it) } + registerCommand( + "shUpdateBazaarPrices", + "Forcefully updating the bazaar prices right now." + ) { HypixelBazaarFetcher.fetchNow() } } private fun developersDebugFeatures() { @@ -623,8 +628,9 @@ object Commands { } } - private fun registerCommand(name: String, description: String, function: (Array<String>) -> Unit) { - if (commands.any { it.name.equals(name, ignoreCase = true) }) { + private fun registerCommand(rawName: String, description: String, function: (Array<String>) -> Unit) { + val name = rawName.lowercase() + if (commands.any { it.name == name }) { error("The command '$name is already registered!'") } ClientCommandHandler.instance.registerCommand(SimpleCommand(name, createCommand(function))) diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java index bbacd341a..6be8812b8 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java @@ -66,6 +66,11 @@ public class DebugConfig { public boolean showNpcPrice = false; @Expose + @ConfigOption(name = "Show BZ Price", desc = "Show BZ price in item lore.") + @ConfigEditorBoolean + public boolean showBZPrice = false; + + @Expose @ConfigOption(name = "Show Item UUID", desc = "Show the Unique Identifier of items in the lore.") @ConfigEditorBoolean public boolean showItemUuid = false; 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() +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarApi.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarApi.kt index 5d976a7dd..9df83ec81 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarApi.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarApi.kt @@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.features.inventory.bazaar import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.data.bazaar.HypixelBazaarFetcher import at.hannibal2.skyhanni.events.BazaarOpenedProductEvent import at.hannibal2.skyhanni.events.GuiContainerEvent import at.hannibal2.skyhanni.events.InventoryCloseEvent @@ -14,6 +15,7 @@ import at.hannibal2.skyhanni.utils.HypixelCommands import at.hannibal2.skyhanni.utils.InventoryUtils.getAllItems import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.ItemUtils.itemName import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils @@ -41,20 +43,18 @@ class BazaarApi { var currentlyOpenedProduct: NEUInternalName? = null - fun NEUInternalName.getBazaarData() = if (isBazaarItem()) { - holder.getData(this) - } else null + fun NEUInternalName.getBazaarData(): BazaarData? = HypixelBazaarFetcher.latestProductInformation[this] fun NEUInternalName.getBazaarDataOrError(): BazaarData = getBazaarData() ?: run { ErrorManager.skyHanniError( - "Can not find bazaar data for internal name", + "Can not find bazaar data for $itemName", "internal name" to this ) } fun isBazaarItem(stack: ItemStack): Boolean = stack.getInternalName().isBazaarItem() - fun NEUInternalName.isBazaarItem() = NEUItems.manager.auctionManager.getBazaarInfo(asString()) != null + fun NEUInternalName.isBazaarItem() = getBazaarData() != null fun searchForBazaarItem(displayName: String, amount: Int = -1) { if (!LorenzUtils.inSkyBlock) return diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarData.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarData.kt index ed49359bb..db97d5e6b 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarData.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarData.kt @@ -1,7 +1,14 @@ package at.hannibal2.skyhanni.features.inventory.bazaar +import at.hannibal2.skyhanni.data.bazaar.BazaarProduct + data class BazaarData( val displayName: String, - val sellPrice: Double, - val buyPrice: Double, + val sellOfferPrice: Double, + val instantBuyPrice: Double, + val product: BazaarProduct, + @Deprecated("outdated", ReplaceWith("instantBuyPrice")) + val sellPrice: Double = instantBuyPrice, + @Deprecated("outdated", ReplaceWith("sellOfferPrice")) + val buyPrice: Double = sellOfferPrice, ) diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt index 2cbc2474d..8911e1e2b 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt @@ -6,22 +6,15 @@ import at.hannibal2.skyhanni.data.jsonobjects.other.SkyblockItemsDataJson import at.hannibal2.skyhanni.features.rift.RiftAPI 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.name import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUItems -import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull -import at.hannibal2.skyhanni.utils.NEUItems.getPrice -import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.fromJson import kotlinx.coroutines.launch -import kotlin.concurrent.fixedRateTimer class BazaarDataHolder { companion object { - private val bazaarData = mutableMapOf<NEUInternalName, BazaarData>() private var npcPrices = mapOf<NEUInternalName, Double>() fun getNpcPrice(internalName: NEUInternalName) = npcPrices[internalName] @@ -55,25 +48,6 @@ class BazaarDataHolder { } // TODO use SecondPassedEvent - fixedRateTimer(name = "skyhanni-bazaar-update", period = 10_000L) { - bazaarData.clear() - } } - fun getData(internalName: NEUInternalName) = bazaarData[internalName] ?: createNewData(internalName) - - private fun createNewData(internalName: NEUInternalName): BazaarData? { - val stack = internalName.getItemStackOrNull() - if (stack == null) { - ChatUtils.debug("Bazaar data is null: '$internalName'") - return null - } - val displayName = stack.name.removeColor() - val sellPrice = internalName.getPrice(true) - val buyPrice = internalName.getPrice(false) - - val data = BazaarData(displayName, sellPrice, buyPrice) - bazaarData[internalName] = data - return data - } } diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt index 7e9427996..b81a25599 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt @@ -15,6 +15,7 @@ import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.events.ReceiveParticleEvent import at.hannibal2.skyhanni.features.garden.GardenNextJacobContest import at.hannibal2.skyhanni.features.garden.visitor.GardenVisitorColorNames +import at.hannibal2.skyhanni.features.inventory.bazaar.BazaarApi.Companion.getBazaarData import at.hannibal2.skyhanni.test.GriffinUtils.drawWaypointFilled import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.CollectionUtils.editCopy @@ -460,7 +461,21 @@ class SkyHanniDebugsAndTests { val internalName = event.itemStack.getInternalNameOrNull() ?: return val npcPrice = internalName.getNpcPriceOrNull() ?: return - event.toolTip.add("§7NPC price: §6${npcPrice.addSeparators()}") + event.toolTip.add("§7NPC price: ${npcPrice.addSeparators()}") + } + + @SubscribeEvent + fun onShowBzPrice(event: LorenzToolTipEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!debugConfig.showBZPrice) return + val internalName = event.itemStack.getInternalNameOrNull() ?: return + + val data = internalName.getBazaarData() ?: return + val instantBuyPrice = data.instantBuyPrice + val sellOfferPrice = data.sellOfferPrice + + event.toolTip.add("§7BZ instantBuyPrice: ${instantBuyPrice.addSeparators()}") + event.toolTip.add("§7BZ sellOfferPrice: ${sellOfferPrice.addSeparators()}") } @SubscribeEvent diff --git a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt index bd1077be4..46a0d7b57 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt @@ -43,6 +43,11 @@ object APIUtil { ) .useSystemProperties() + /** + * TODO + * make suspend + * use withContext(Dispatchers.IO) { APIUtil.getJSONResponse(url) }.asJsonObject + */ fun getJSONResponse(urlString: String, silentError: Boolean = false) = getJSONResponseAsElement(urlString, silentError) as JsonObject diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt index 664eb86b9..63c2e4c67 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -85,6 +85,8 @@ object LorenzUtils { return result } + val debug: Boolean = onHypixel && SkyHanniMod.feature.dev.debug.enabled + private var previousApril = false fun SimpleDateFormat.formatCurrentTime(): String = this.format(System.currentTimeMillis()) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt index a5ffc6e14..430edc971 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt @@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.data.jsonobjects.repo.MultiFilterJson import at.hannibal2.skyhanni.events.NeuProfileDataLoadedEvent import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.features.inventory.bazaar.BazaarApi.Companion.getBazaarData import at.hannibal2.skyhanni.features.inventory.bazaar.BazaarDataHolder import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.ItemBlink.checkBlinkItem @@ -149,6 +150,7 @@ object NEUItems { fun getInternalNameOrNull(nbt: NBTTagCompound): NEUInternalName? = ItemResolutionQuery(manager).withItemNBT(nbt).resolveInternalName()?.asInternalName() + fun NEUInternalName.getPrice(useSellingPrice: Boolean = false) = getPriceOrNull(useSellingPrice) ?: -1.0 fun NEUInternalName.getNpcPrice() = getNpcPriceOrNull() ?: -1.0 @@ -167,8 +169,13 @@ object NEUItems { if (this == NEUInternalName.WISP_POTION) { return 20_000.0 } - val result = manager.auctionManager.getBazaarOrBin(asString(), useSellingPrice) - if (result != -1.0) return result + + getBazaarData()?.let { + return if (useSellingPrice) it.sellOfferPrice else it.instantBuyPrice + } + + val result = manager.auctionManager.getLowestBin(asString()) + if (result != -1L) return result.toDouble() if (equals("JACK_O_LANTERN")) { return "PUMPKIN".asInternalName().getPrice(useSellingPrice) + 1 |