aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron <51387595+AzureAaron@users.noreply.github.com>2024-06-22 02:47:39 -0400
committerGitHub <noreply@github.com>2024-06-22 02:47:39 -0400
commitd12504ef9992a72de9eb3c3a930c9f3c855c0acc (patch)
tree840ac5bc666da72b26f104b088eb95da5306065e
parentd6b220a8e42a1fc2dbc955779e86d199851b4674 (diff)
parent7e3ed3e5ca248434b61e876df227ec4ea72b46a2 (diff)
downloadSkyblocker-d12504ef9992a72de9eb3c3a930c9f3c855c0acc.tar.gz
Skyblocker-d12504ef9992a72de9eb3c3a930c9f3c855c0acc.tar.bz2
Skyblocker-d12504ef9992a72de9eb3c3a930c9f3c855c0acc.zip
Merge pull request #783 from SkyblockerMod/api-changes
Api changes the Aaron of the Azure color made
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/PetCache.java149
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java44
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java109
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java169
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Http.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java6
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json7
13 files changed, 560 insertions, 62 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index eff88783..d793e73d 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -173,9 +173,10 @@ public class SkyblockerMod implements ClientModInitializer {
VisitorHelper.init();
ItemRarityBackgrounds.init();
MuseumItemCache.init();
+ PetCache.init();
SecretsTracker.init();
+ ApiAuthentication.init();
ApiUtils.init();
- ProfileUtils.init();
Debug.init();
Kuudra.init();
RenderHelper.init();
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
index a7685ffc..f2e3e907 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
@@ -4,6 +4,7 @@ import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.PetCache;
import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver;
import de.hysky.skyblocker.skyblock.experiment.ExperimentSolver;
import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver;
@@ -11,6 +12,7 @@ import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver;
import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
import de.hysky.skyblocker.skyblock.item.ItemProtection;
import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds;
+import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
import de.hysky.skyblocker.skyblock.item.WikiLookup;
import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager;
@@ -36,6 +38,7 @@ import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -248,17 +251,32 @@ public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen
ci.cancel();
return;
}
- if (this.handler instanceof GenericContainerScreenHandler genericContainerScreenHandler && genericContainerScreenHandler.getRows() == 6) {
- VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack());
-
- // Prevent selling to NPC shops
- ItemStack sellStack = this.handler.slots.get(49).getStack();
- if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) {
- if (slotId != 49 && ItemProtection.isItemProtected(stack)) {
- ci.cancel();
- return;
+
+ switch (this.handler) {
+ case GenericContainerScreenHandler genericContainerScreenHandler when genericContainerScreenHandler.getRows() == 6 -> {
+ VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack());
+
+ // Prevent selling to NPC shops
+ ItemStack sellStack = this.handler.slots.get(49).getStack();
+ if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) {
+ if (slotId != 49 && ItemProtection.isItemProtected(stack)) {
+ ci.cancel();
+ return;
+ }
}
}
+
+ case GenericContainerScreenHandler genericContainerScreenHandler when title.equals(MuseumItemCache.DONATION_CONFIRMATION_SCREEN_TITLE) -> {
+ //Museum Item Cache donation tracking
+ MuseumItemCache.handleClick(slot, slotId, genericContainerScreenHandler.slots);
+ }
+
+ case null, default -> {}
+ }
+
+ //Pet Caching
+ if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && title.startsWith("Pets")) {
+ PetCache.handlePetEquip(slot, slotId);
}
if (currentSolver != null) {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
index 2154c4b5..3abbfbcd 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
@@ -1,10 +1,12 @@
package de.hysky.skyblocker.mixins;
-import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
-import de.hysky.skyblocker.SkyblockerMod;
+import com.mojang.serialization.JsonOps;
+
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.injected.SkyblockerStack;
+import de.hysky.skyblocker.skyblock.PetCache.PetInfo;
import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
@@ -165,6 +167,7 @@ public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack
}
// Transformation to API format.
+ //TODO future - remove this and just handle it directly for the NEU id conversion because this whole system is confusing and hard to follow
if (customData.contains("is_shiny")) {
return "ISSHINY_" + customDataString;
}
@@ -178,12 +181,14 @@ public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack
return "ENCHANTMENT_" + enchant.toUpperCase(Locale.ENGLISH) + "_" + enchants.getInt(enchant);
}
}
+
case "PET" -> {
if (customData.contains("petInfo")) {
- JsonObject petInfo = SkyblockerMod.GSON.fromJson(customData.getString("petInfo"), JsonObject.class);
- return "LVL_1_" + petInfo.get("tier").getAsString() + "_" + petInfo.get("type").getAsString();
+ PetInfo petInfo = PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(customData.getString("petInfo"))).getOrThrow();
+ return "LVL_1_" + petInfo.tier() + "_" + petInfo.type();
}
}
+
case "POTION" -> {
String enhanced = customData.contains("enhanced") ? "_ENHANCED" : "";
String extended = customData.contains("extended") ? "_EXTENDED" : "";
@@ -193,6 +198,7 @@ public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack
+ enhanced + extended + splash).toUpperCase(Locale.ENGLISH);
}
}
+
case "RUNE" -> {
if (customData.contains("runes")) {
NbtCompound runes = customData.getCompound("runes");
@@ -201,6 +207,7 @@ public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack
return rune.toUpperCase(Locale.ENGLISH) + "_RUNE_" + runes.getInt(rune);
}
}
+
case "ATTRIBUTE_SHARD" -> {
if (customData.contains("attributes")) {
NbtCompound shards = customData.getCompound("attributes");
@@ -209,6 +216,42 @@ public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack
return customDataString + "-" + shard.toUpperCase(Locale.ENGLISH) + "_" + shards.getInt(shard);
}
}
+
+ case "NEW_YEAR_CAKE" -> {
+ return customDataString + "_" + customData.getInt("new_years_cake");
+ }
+
+ case "PARTY_HAT_CRAB", "PARTY_HAT_CRAB_ANIMATED", "BALLOON_HAT_2024" -> {
+ return customDataString + "_" + customData.getString("party_hat_color").toUpperCase(Locale.ENGLISH);
+ }
+
+ case "PARTY_HAT_SLOTH" -> {
+ return customDataString + "_" + customData.getString("party_hat_emoji").toUpperCase(Locale.ENGLISH);
+ }
+
+ case "CRIMSON_HELMET", "CRIMSON_CHESTPLATE", "CRIMSON_LEGGINGS", "CRIMSON_BOOTS" -> {
+ NbtCompound attributes = customData.getCompound("attributes");
+
+ if (attributes.contains("magic_find") && attributes.contains("veteran")) {
+ return customDataString + "-MAGIC_FIND-VETERAN";
+ }
+ }
+
+ case "AURORA_HELMET", "AURORA_CHESTPLATE", "AURORA_LEGGINGS", "AURORA_BOOTS" -> {
+ NbtCompound attributes = customData.getCompound("attributes");
+
+ if (attributes.contains("mana_pool") && attributes.contains("mana_regeneration")) {
+ return customDataString + "-MANA_POOL-MANA_REGENERATION";
+ }
+ }
+
+ case "TERROR_HELMET", "TERROR_CHESTPLATE", "TERROR_LEGGINGS", "TERROR_BOOTS" -> {
+ NbtCompound attributes = customData.getCompound("attributes");
+
+ if (attributes.contains("lifeline") && attributes.contains("mana_pool")) {
+ return customDataString + "-LIFELINE-MANA_POOL";
+ }
+ }
}
return customDataString;
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
new file mode 100644
index 00000000..d8cd6e48
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
@@ -0,0 +1,149 @@
+package de.hysky.skyblocker.skyblock;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.concurrent.CompletableFuture;
+import java.util.Optional;
+
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.Utils;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.screen.slot.Slot;
+
+/**
+ * Doesn't work with auto pet right now because thats complicated.
+ *
+ * Want support? Ask the Admins for a Mod API event or open your pets menu.
+ */
+public class PetCache {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("pet_cache.json");
+ private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>> CACHED_PETS = new Object2ObjectOpenHashMap<>();
+
+ /**
+ * Used in case the server lags to prevent the screen tick check from overwriting the clicked pet logic
+ */
+ private static boolean shouldLook4Pets;
+
+ public static void init() {
+ load();
+
+ ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> {
+ if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen) {
+ if (genericContainerScreen.getTitle().getString().startsWith("Pets")) {
+ shouldLook4Pets = true;
+
+ ScreenEvents.afterTick(screen).register(screen1 -> {
+ if (shouldLook4Pets) {
+ for (Slot slot : genericContainerScreen.getScreenHandler().slots) {
+ ItemStack stack = slot.getStack();
+
+ if (!stack.isEmpty() && ItemUtils.getLoreLineIf(stack, line -> line.equals("Click to despawn!")) != null) {
+ shouldLook4Pets = false;
+ parsePet(stack, false);
+
+ break;
+ }
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
+ private static void load() {
+ CompletableFuture.runAsync(() -> {
+ try (BufferedReader reader = Files.newBufferedReader(FILE)) {
+ CACHED_PETS.putAll(PetInfo.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow());
+ } catch (NoSuchFileException ignored) {
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Pet Cache] Failed to load saved pet!", e);
+ }
+ });
+ }
+
+ private static void save() {
+ CompletableFuture.runAsync(() -> {
+ try (BufferedWriter writer = Files.newBufferedWriter(FILE)) {
+ SkyblockerMod.GSON.toJson(PetInfo.SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, CACHED_PETS).getOrThrow(), writer);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Pet Cache] Failed to save pet data to the cache!", e);
+ }
+ });
+ }
+
+ public static void handlePetEquip(Slot slot, int slotId) {
+ //Ignore inventory clicks
+ if (slotId >= 0 && slotId <= 53) {
+ ItemStack stack = slot.getStack();
+
+ if (!stack.isEmpty()) parsePet(stack, true);
+ }
+ }
+
+ private static void parsePet(ItemStack stack, boolean clicked) {
+ String id = ItemUtils.getItemId(stack);
+ String profileId = Utils.getProfileId();
+
+ if (id.equals("PET") && !profileId.isEmpty()) {
+ NbtCompound customData = ItemUtils.getCustomData(stack);
+
+ //Should never fail, all pets must have this but you never know with Hypixel
+ try {
+ PetInfo petInfo = PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(customData.getString("petInfo"))).getOrThrow();
+ shouldLook4Pets = false;
+
+ Object2ObjectOpenHashMap<String, PetInfo> playerData = CACHED_PETS.computeIfAbsent(Utils.getUndashedUuid(), _uuid -> new Object2ObjectOpenHashMap<>());
+
+ //Handle deselecting pets
+ if (clicked && getCurrentPet() != null && getCurrentPet().uuid().orElse("").equals(petInfo.uuid().orElse(""))) {
+ playerData.remove(profileId);
+ } else {
+ playerData.put(profileId, petInfo);
+ }
+
+ save();
+ } catch (Exception e) {
+ LOGGER.error(LogUtils.FATAL_MARKER, "[Skyblocker Pet Cache] Failed to parse pet's pet info!", e);
+ }
+ }
+ }
+
+ @Nullable
+ public static PetInfo getCurrentPet() {
+ String uuid = Utils.getUndashedUuid();
+ String profileId = Utils.getProfileId();
+
+ return CACHED_PETS.containsKey(uuid) && CACHED_PETS.get(uuid).containsKey(profileId) ? CACHED_PETS.get(uuid).get(profileId) : null;
+ }
+
+ public record PetInfo(String type, double exp, String tier, Optional<String> uuid) {
+ public static final Codec<PetInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.STRING.fieldOf("type").forGetter(PetInfo::type),
+ Codec.DOUBLE.fieldOf("exp").forGetter(PetInfo::exp),
+ Codec.STRING.fieldOf("tier").forGetter(PetInfo::tier),
+ Codec.STRING.optionalFieldOf("uuid").forGetter(PetInfo::uuid))
+ .apply(instance, PetInfo::new));
+ private static final Codec<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING,
+ Codec.unboundedMap(Codec.STRING, CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)
+ ).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java
index 96c21d22..93d29714 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java
@@ -1,9 +1,9 @@
package de.hysky.skyblocker.skyblock.item;
-import com.google.gson.JsonElement;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.PetCache;
+import de.hysky.skyblocker.skyblock.PetCache.PetInfo;
import de.hysky.skyblocker.utils.ItemUtils;
-import de.hysky.skyblocker.utils.ProfileUtils;
import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
import net.minecraft.block.BlockState;
@@ -36,8 +36,8 @@ public class ItemCooldowns {
561700, 611700, 666700, 726700, 791700, 861700, 936700, 1016700, 1101700, 1191700,
1286700, 1386700, 1496700, 1616700, 1746700, 1886700
};
- public static int monkeyLevel = 1;
- public static double monkeyExp = 0;
+ private static int monkeyLevel = 1;
+ private static double monkeyExp = 0;
public static void init() {
ClientPlayerBlockBreakEvents.AFTER.register(ItemCooldowns::afterBlockBreak);
@@ -45,30 +45,24 @@ public class ItemCooldowns {
}
public static void updateCooldown() {
- ProfileUtils.updateProfile().thenAccept(player -> {
- for (JsonElement pet : player.getAsJsonObject("pets_data").getAsJsonArray("pets")) {
- if (!pet.getAsJsonObject().get("type").getAsString().equals("MONKEY")) continue;
- if (!pet.getAsJsonObject().get("active").getAsString().equals("true")) continue;
- if (pet.getAsJsonObject().get("tier").getAsString().equals("LEGENDARY")) {
- monkeyExp = Double.parseDouble(pet.getAsJsonObject().get("exp").getAsString());
- monkeyLevel = 0;
- for (int xpLevel : EXPERIENCE_LEVELS) {
- if (monkeyExp < xpLevel) {
- break;
- } else {
- monkeyExp -= xpLevel;
- monkeyLevel++;
- }
- }
+ PetInfo pet = PetCache.getCurrentPet();
+
+ if (pet != null && pet.tier().equals("LEGENDARY")) {
+ monkeyExp = pet.exp();
+
+ monkeyLevel = 0;
+ for (int xpLevel : EXPERIENCE_LEVELS) {
+ if (monkeyExp < xpLevel) {
+ break;
+ } else {
+ monkeyExp -= xpLevel;
+ monkeyLevel++;
}
}
- }).exceptionally(e -> {
- ProfileUtils.LOGGER.error("[Skyblocker Item Cooldown] Failed to get Player Pet Data, is the API Down/Limited?", e);
- return null;
- });
+ }
}
- private static int getCooldown() {
+ private static int getCooldown4Foraging() {
int baseCooldown = 2000;
int monkeyPetCooldownReduction = baseCooldown * monkeyLevel / 200;
return baseCooldown - monkeyPetCooldownReduction;
@@ -82,7 +76,7 @@ public class ItemCooldowns {
if (usedItemId.equals(JUNGLE_AXE_ID) || usedItemId.equals(TREECAPITATOR_ID)) {
updateCooldown();
if (!isOnCooldown(JUNGLE_AXE_ID) || !isOnCooldown(TREECAPITATOR_ID)) {
- ITEM_COOLDOWNS.put(usedItemId, new CooldownEntry(getCooldown()));
+ ITEM_COOLDOWNS.put(usedItemId, new CooldownEntry(getCooldown4Foraging()));
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
index c78724ca..50982d29 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
@@ -1,22 +1,36 @@
package de.hysky.skyblocker.skyblock.item;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.mojang.util.UndashedUuid;
import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Http;
import de.hysky.skyblocker.utils.Http.ApiResponse;
+import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.CommandRegistryAccess;
+import net.minecraft.item.ItemStack;
import net.minecraft.nbt.*;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.collection.DefaultedList;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,11 +50,29 @@ public class MuseumItemCache {
private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json");
private static final Map<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>();
private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}";
+ public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation";
+ private static final int CONFIRM_DONATION_BUTTON_SLOT = 20;
private static CompletableFuture<Void> loaded;
public static void init() {
ClientLifecycleEvents.CLIENT_STARTED.register(MuseumItemCache::load);
+ ClientCommandRegistrationCallback.EVENT.register(MuseumItemCache::registerCommands);
+ }
+
+ private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
+ dispatcher.register(literal(SkyblockerMod.NAMESPACE)
+ .then(literal("museum")
+ .then(literal("resync")
+ .executes(context -> {
+ if (tryResync(context.getSource())) {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.attemptingResync")));
+ } else {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.cannotResync")));
+ }
+
+ return Command.SINGLE_SUCCESS;
+ }))));
}
private static void load(MinecraftClient client) {
@@ -67,7 +99,35 @@ public class MuseumItemCache {
});
}
+ public static void handleClick(Slot slot, int slotId, DefaultedList<Slot> slots) {
+ if (slotId == CONFIRM_DONATION_BUTTON_SLOT) {
+ //Slots 0 to 17 can have items, well not all but thats the general range
+ for (int i = 0; i < 17; i++) {
+ ItemStack stack = slots.get(i).getStack();
+
+ if (!stack.isEmpty()) {
+ String itemId = ItemUtils.getItemId(stack);
+ String profileId = Utils.getProfileId();
+
+ if (!itemId.isEmpty() && !profileId.isEmpty()) {
+ String uuid = Utils.getUndashedUuid();
+ //Be safe about access to avoid NPEs
+ Map<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>());
+ playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY);
+
+ playerData.get(profileId).collectedItemIds().add(itemId);
+ save();
+ }
+ }
+ }
+ }
+ }
+
private static void updateData4ProfileMember(String uuid, String profileId) {
+ updateData4ProfileMember(uuid, profileId, null);
+ }
+
+ private static void updateData4ProfileMember(String uuid, String profileId, FabricClientCommandSource source) {
CompletableFuture.runAsync(() -> {
try (ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId)) {
//The request was successful
@@ -103,58 +163,85 @@ public class MuseumItemCache {
MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds));
save();
+ if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncSuccess")));
+
LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId);
} else {
//If the player's Museum API is disabled
putEmpty(uuid, profileId);
+ if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
+
LOGGER.warn(ERROR_LOG_TEMPLATE + " because the Museum API is disabled!", profileId);
}
} else {
//If the request returns a non 200 status code
putEmpty(uuid, profileId);
+ if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
+
LOGGER.error(ERROR_LOG_TEMPLATE + " because a non 200 status code was encountered! Status Code: {}", profileId, response.statusCode());
}
} catch (Exception e) {
//If an exception was somehow thrown
putEmpty(uuid, profileId);
+ if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
+
LOGGER.error(ERROR_LOG_TEMPLATE, profileId, e);
}
});
}
private static void putEmpty(String uuid, String profileId) {
- MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), ObjectOpenHashSet.of()));
+ //Only put new data if they didn't have any before
+ if (!MUSEUM_ITEM_CACHE.get(uuid).containsKey(profileId)) {
+ MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), ObjectOpenHashSet.of()));
+ }
+
save();
}
+ private static boolean tryResync(FabricClientCommandSource source) {
+ String uuid = Utils.getUndashedUuid();
+ String profileId = Utils.getProfileId();
+
+ //Only allow resyncing if the data is actually present yet, otherwise the player needs to swap servers for the tick method to be called
+ if (loaded.isDone() && !profileId.isEmpty() && MUSEUM_ITEM_CACHE.containsKey(uuid) && MUSEUM_ITEM_CACHE.get(uuid).containsKey(profileId) && MUSEUM_ITEM_CACHE.get(uuid).get(profileId).canResync()) {
+ updateData4ProfileMember(uuid, profileId, source);
+
+ return true;
+ }
+
+ return false;
+ }
+
/**
- * The cache is ticked upon switching skyblock servers
+ * The cache is ticked upon switching Skyblock servers. Only loads from the API if the profile wasn't cached yet.
*/
public static void tick(String profileId) {
- if (loaded.isDone()) {
- String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+ String uuid = Utils.getUndashedUuid();
+
+ if (loaded.isDone() && (!MUSEUM_ITEM_CACHE.containsKey(uuid) || !MUSEUM_ITEM_CACHE.getOrDefault(uuid, new Object2ObjectOpenHashMap<>()).containsKey(profileId))) {
Map<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>());
playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY);
- if (playerData.get(profileId).stale()) updateData4ProfileMember(uuid, profileId);
+ updateData4ProfileMember(uuid, profileId);
}
}
public static boolean hasItemInMuseum(String id) {
- String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+ String uuid = Utils.getUndashedUuid();
ObjectOpenHashSet<String> collectedItemIds = (!MUSEUM_ITEM_CACHE.containsKey(uuid) || Utils.getProfileId().isBlank() || !MUSEUM_ITEM_CACHE.get(uuid).containsKey(Utils.getProfileId())) ? null : MUSEUM_ITEM_CACHE.get(uuid).get(Utils.getProfileId()).collectedItemIds();
return collectedItemIds != null && collectedItemIds.contains(id);
}
- private record ProfileMuseumData(long lastUpdated, ObjectOpenHashSet<String> collectedItemIds) {
+ private record ProfileMuseumData(long lastResync, ObjectOpenHashSet<String> collectedItemIds) {
private static final ProfileMuseumData EMPTY = new ProfileMuseumData(0L, null);
- private static final long MAX_AGE = 86_400_000;
+ private static final long TIME_BETWEEN_RESYNCING_ALLOWED = 600_000L;
private static final Codec<ProfileMuseumData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
- Codec.LONG.fieldOf("lastUpdated").forGetter(ProfileMuseumData::lastUpdated),
+ Codec.LONG.fieldOf("lastResync").forGetter(ProfileMuseumData::lastResync),
Codec.STRING.listOf()
.xmap(ObjectOpenHashSet::new, ObjectArrayList::new)
.fieldOf("collectedItemIds")
@@ -165,8 +252,8 @@ public class MuseumItemCache {
.xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)
).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new);
- private boolean stale() {
- return System.currentTimeMillis() > lastUpdated + MAX_AGE;
+ private boolean canResync() {
+ return this.lastResync + TIME_BETWEEN_RESYNCING_ALLOWED < System.currentTimeMillis();
}
}
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
index 2f5408a1..cc3d2099 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
@@ -52,6 +52,11 @@ public class ItemTooltip {
case "POTION" -> apiId = "";
case "ATTRIBUTE_SHARD" ->
apiId = id + "+" + apiId.replace("SHARD-", "").replaceAll("_(?!.*_)", ";");
+ case "NEW_YEAR_CAKE" -> apiId = id + "+" + apiId.replace("NEW_YEAR_CAKE_", "");
+ case "PARTY_HAT_CRAB_ANIMATED" -> apiId = "PARTY_HAT_CRAB_" + apiId.replace("PARTY_HAT_CRAB_ANIMATED_", "") + "_ANIMATED";
+ case "CRIMSON_HELMET", "CRIMSON_CHESTPLATE", "CRIMSON_LEGGINGS", "CRIMSON_BOOTS",
+ "AURORA_HELMET", "AURORA_CHESTPLATE", "AURORA_LEGGINGS", "AURORA_BOOTS",
+ "TERROR_HELMET", "TERROR_CHESTPLATE", "TERROR_LEGGINGS", "TERROR_BOOTS" -> apiId = id;
default -> apiId = apiId.replace(":", "-");
}
return apiId;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java
index 89d41290..10e12ace 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java
@@ -78,7 +78,8 @@ public class ItemListTab extends ItemListWidget.TabContainerWidget {
return true;
} else if (results != null) {
this.searchField.setFocused(false);
- this.results.mouseClicked(mouseX, mouseY, button);
+
+ return this.results.mouseClicked(mouseX, mouseY, button);
}
return false;
diff --git a/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java b/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java
new file mode 100644
index 00000000..fbf814ee
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java
@@ -0,0 +1,169 @@
+package de.hysky.skyblocker.utils;
+
+import java.nio.ByteBuffer;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.util.Base64;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.minecraft.SharedConstants;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.network.encryption.PlayerKeyPair;
+import net.minecraft.text.Text;
+import net.minecraft.util.Uuids;
+import net.minecraft.util.dynamic.Codecs;
+
+/**
+ * This class is responsible for communicating with the API to retrieve a fully custom token used to gain access to more privileged APIs
+ * such as the Hypixel API Proxy. The main point of this is to verify that a person is most likely playing Minecraft, and thus is likely to be
+ * using the mod, and not somebody who is attempting to freeload off of our services.
+ */
+public class ApiAuthentication {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final String MINECRAFT_VERSION = SharedConstants.getGameVersion().getName();
+ private static final String AUTH_URL = "https://hysky.de/api/aaron/authenticate";
+ private static final String CONTENT_TYPE = "application/json";
+ private static final String ALGORITHM = "SHA256withRSA";
+
+ private static TokenInfo tokenInfo = null;
+
+ public static void init() {
+ //Update token after the profileKeys instance is initialized
+ ClientLifecycleEvents.CLIENT_STARTED.register(_client -> updateToken());
+ }
+
+ /**
+ * Refreshes the token by fetching the player's key pair from the Minecraft Services API.
+ *
+ * We use the player's uuid, public key, public key signature, public key expiry date, and randomly signed data by the private key to verify the identity/legitimacy
+ * of the player without the server needing to handle any privileged information.
+ *
+ * Mojang provides a signature for each key pair which is comprised of the player's uuid, public key, and expiry time so we can easily validate if the key pair
+ * was generated by Mojang and is tied to said player. For information about what the randomly signed data is used for and why see {@link #getRandomSignedData(PrivateKey)}
+ */
+ private static void updateToken() {
+ //The fetching runs async in ProfileKeysImpl#getKeyPair
+ CLIENT.getProfileKeys().fetchKeyPair().thenAcceptAsync(playerKeypairOpt -> {
+ if (playerKeypairOpt.isPresent()) {
+ PlayerKeyPair playerKeyPair = playerKeypairOpt.get();
+
+ //The key header and footer can be sent but that doesn't matter to the server
+ String publicKey = Base64.getMimeEncoder().encodeToString(playerKeyPair.publicKey().data().key().getEncoded());
+ byte[] publicKeySignature = playerKeyPair.publicKey().data().keySignature();
+ long expiresAt = playerKeyPair.publicKey().data().expiresAt().toEpochMilli();
+
+ TokenRequest.KeyPairInfo keyPairInfo = new TokenRequest.KeyPairInfo(Objects.requireNonNull(CLIENT.getSession().getUuidOrNull()), publicKey, publicKeySignature, expiresAt);
+ TokenRequest.SignedData signedData = Objects.requireNonNull(getRandomSignedData(playerKeyPair.privateKey()));
+ TokenRequest tokenRequest = new TokenRequest(keyPairInfo, signedData, SkyblockerMod.SKYBLOCKER_MOD.getMetadata().getId(), MINECRAFT_VERSION, SkyblockerMod.VERSION);
+
+ String request = SkyblockerMod.GSON.toJson(TokenRequest.CODEC.encodeStart(JsonOps.INSTANCE, tokenRequest).getOrThrow());
+
+ try {
+ tokenInfo = TokenInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(Http.sendPostRequest(AUTH_URL, request, CONTENT_TYPE))).getOrThrow();
+ int refreshAtTicks = (int) (((tokenInfo.expiresAt() - tokenInfo.issuedAt()) / 1000L) - 300L) * 20; //Refresh 5 minutes before expiry date
+
+ Scheduler.INSTANCE.schedule(ApiAuthentication::updateToken, refreshAtTicks, true);
+ } catch (Exception e) {
+ //Try again in 1 minute
+ logErrorAndScheduleRetry(Text.translatable("skyblocker.api.token.authFailure"), 60 * 20, "[Skyblocker Api Auth] Failed to refresh the api token! Some features might not work :(", e);
+ }
+ } else {
+ //The Minecraft Services API is probably down so we will retry in 5 minutes, either that or your access token has expired (game open for 24h) and you need to restart.
+ logErrorAndScheduleRetry(Text.translatable("skyblocker.api.token.noProfileKeys"), 300 * 20, "[Skyblocker Api Auth] Failed to fetch profile keys! Some features may not work temporarily :( (Has your game been open for more than 24 hours? If so restart.)");
+ }
+ }).exceptionally(throwable -> {
+ //Try again in 1 minute
+ logErrorAndScheduleRetry(Text.translatable("skyblocker.api.token.authFailure"), 60 * 20, "[Skyblocker Api Auth] Encountered an unexpected exception while refreshing the api token!", throwable);
+
+ return null;
+ });
+ }
+
+ /**
+ * Signs a string of random data with the key pair's private key. This is required to know if you are the real holder of the key pair or not. Why? Because your public key,
+ * public key signature, and expiry time are forwarded by the server to all players on the same server as you. This means that a malicious
+ * individual could scrape key pairs and pretend to be someone else when requesting a token from our API. So by signing data with the private key,
+ * the server can use the public key to verify its integrity which proves that the requester is the true holder of the complete key pair and not someone trying to pose as them for malicious purposes.
+ */
+ private static TokenRequest.SignedData getRandomSignedData(PrivateKey privateKey) {
+ try {
+ Signature signature = Signature.getInstance(ALGORITHM);
+ UUID uuid = UUID.randomUUID();
+ ByteBuffer buf = ByteBuffer.allocate(16)
+ .putLong(uuid.getMostSignificantBits())
+ .putLong(uuid.getLeastSignificantBits());
+
+ signature.initSign(privateKey);
+ signature.update(buf.array());
+
+ byte[] signedData = signature.sign();
+
+ return new TokenRequest.SignedData(buf.array(), signedData);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Api Auth] Failed to sign random data!", e);
+ }
+
+ //This should never ever be the case, since we are signing data that is not invalid in any case
+ return null;
+ }
+
+ private static void logErrorAndScheduleRetry(Text warningMessage, int retryAfter, String logMessage, Object... logArgs) {
+ LOGGER.error(logMessage, logArgs);
+ Scheduler.INSTANCE.schedule(ApiAuthentication::updateToken, retryAfter, true);
+
+ if (CLIENT.player != null) CLIENT.player.sendMessage(Constants.PREFIX.get().append(warningMessage));
+ }
+
+ @Nullable
+ public static String getToken() {
+ return tokenInfo != null ? tokenInfo.token() : null;
+ }
+
+ private record TokenRequest(KeyPairInfo keyPairInfo, SignedData signedData, String mod, String minecraftVersion, String modVersion) {
+ private static final Codec<TokenRequest> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ KeyPairInfo.CODEC.fieldOf("keyPair").forGetter(TokenRequest::keyPairInfo),
+ SignedData.CODEC.fieldOf("signedData").forGetter(TokenRequest::signedData),
+ Codec.STRING.fieldOf("mod").forGetter(TokenRequest::mod),
+ Codec.STRING.fieldOf("minecraftVersion").forGetter(TokenRequest::minecraftVersion),
+ Codec.STRING.fieldOf("modVersion").forGetter(TokenRequest::modVersion))
+ .apply(instance, TokenRequest::new));
+
+ private record KeyPairInfo(UUID uuid, String publicKey, byte[] publicKeySignature, long expiresAt) {
+ private static final Codec<KeyPairInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Uuids.STRING_CODEC.fieldOf("uuid").forGetter(KeyPairInfo::uuid),
+ Codec.STRING.fieldOf("publicKey").forGetter(KeyPairInfo::publicKey),
+ Codecs.BASE_64.fieldOf("publicKeySignature").forGetter(KeyPairInfo::publicKeySignature),
+ Codec.LONG.fieldOf("expiresAt").forGetter(KeyPairInfo::expiresAt))
+ .apply(instance, KeyPairInfo::new));
+ }
+
+ private record SignedData(byte[] original, byte[] signed) {
+ private static final Codec<SignedData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codecs.BASE_64.fieldOf("original").forGetter(SignedData::original),
+ Codecs.BASE_64.fieldOf("signed").forGetter(SignedData::signed))
+ .apply(instance, SignedData::new));
+ }
+ }
+
+ private record TokenInfo(String token, long issuedAt, long expiresAt) {
+ private static final Codec<TokenInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.STRING.fieldOf("token").forGetter(TokenInfo::token),
+ Codec.LONG.fieldOf("issuedAt").forGetter(TokenInfo::issuedAt),
+ Codec.LONG.fieldOf("expiresAt").forGetter(TokenInfo::expiresAt))
+ .apply(instance, TokenInfo::new));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java
index 871eac78..99db0316 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Http.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Http.java
@@ -17,6 +17,7 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import de.hysky.skyblocker.SkyblockerMod;
import net.minecraft.SharedConstants;
@@ -33,15 +34,18 @@ public class Http {
.followRedirects(Redirect.NORMAL)
.build();
- private static ApiResponse sendCacheableGetRequest(String url) throws IOException, InterruptedException {
- HttpRequest request = HttpRequest.newBuilder()
+ private static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException {
+ HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.GET()
.header("Accept", "application/json")
.header("Accept-Encoding", "gzip, deflate")
.header("User-Agent", USER_AGENT)
.version(Version.HTTP_2)
- .uri(URI.create(url))
- .build();
+ .uri(URI.create(url));
+
+ if (token != null) requestBuilder.header("Token", token);
+
+ HttpRequest request = requestBuilder.build();
HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
InputStream decodedInputStream = getDecodedInputStream(response);
@@ -69,7 +73,7 @@ public class Http {
}
public static String sendGetRequest(String url) throws IOException, InterruptedException {
- return sendCacheableGetRequest(url).content();
+ return sendCacheableGetRequest(url, null).content();
}
public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException {
@@ -84,8 +88,26 @@ public class Http {
return response.headers();
}
+ public static String sendPostRequest(String url, String requestBody, String contentType) throws IOException, InterruptedException {
+ HttpRequest request = HttpRequest.newBuilder()
+ .POST(BodyPublishers.ofString(requestBody))
+ .header("Accept-Encoding", "gzip, deflate")
+ .header("Content-Type", contentType)
+ .header("User-Agent", USER_AGENT)
+ .version(Version.HTTP_2)
+ .uri(URI.create(url))
+ .build();
+
+ HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
+ InputStream decodedInputStream = getDecodedInputStream(response);
+
+ String responseBody = new String(decodedInputStream.readAllBytes());
+
+ return responseBody;
+ }
+
public static ApiResponse sendName2UuidRequest(String name) throws IOException, InterruptedException {
- return sendCacheableGetRequest(NAME_2_UUID + name);
+ return sendCacheableGetRequest(NAME_2_UUID + name, null);
}
/**
@@ -96,7 +118,7 @@ public class Http {
* @implNote the {@code v2} prefix is automatically added
*/
public static ApiResponse sendHypixelRequest(String endpoint, @NotNull String query) throws IOException, InterruptedException {
- return sendCacheableGetRequest(HYPIXEL_PROXY + endpoint + query);
+ return sendCacheableGetRequest(HYPIXEL_PROXY + endpoint + query, ApiAuthentication.getToken());
}
private static InputStream getDecodedInputStream(HttpResponse<InputStream> response) {
diff --git a/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java b/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java
index 0a1f238a..a786e79f 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java
@@ -18,10 +18,6 @@ public class ProfileUtils {
public static Map<String, ObjectLongPair<JsonObject>> players = new HashMap<>();
- public static void init() {
- updateProfile();
- }
-
public static CompletableFuture<JsonObject> updateProfile() {
return updateProfile(MinecraftClient.getInstance().getSession().getUsername());
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index 925879b8..84b3cb9e 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -2,6 +2,8 @@ package de.hysky.skyblocker.utils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
+import com.mojang.util.UndashedUuid;
+
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor;
import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
@@ -507,4 +509,8 @@ public class Utils {
((MessageHandlerAccessor) client.getMessageHandler()).invokeAddToChatLog(message, Instant.now());
client.getNarratorManager().narrateSystemMessage(message);
}
+
+ public static String getUndashedUuid() {
+ return UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+ }
}
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 133923fb..a73d523d 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -661,6 +661,11 @@
"skyblocker.updateRepository.failed": "§cUpdating the local repository failed. See logs for details.",
"skyblocker.updateRepository.error": "§cEncountered an error while deleting and updating the local repository. Try again in a moment or remove the files manually and restart the game. See logs for details.",
+ "skyblocker.museum.attemptingResync": "Attempting to resync your museum item donations...",
+ "skyblocker.museum.cannotResync": "Cannot resync your museum item donations! Note that you can only do this once every two days.",
+ "skyblocker.museum.resyncSuccess": "Successfully resynced your museum item donations!",
+ "skyblocker.museum.resyncFailure": "Failed to resync your museum item donations!",
+
"skyblocker.dungeons.secrets.physicalEntranceNotFound": "§cDungeon Entrance Room coordinates not found. Please go back to the green Entrance Room.",
"skyblocker.dungeons.secrets.markSecretFound": "§rMarked secret #%d as found.",
"skyblocker.dungeons.secrets.markSecretMissing": "§rMarked secret #%d as missing.",
@@ -680,6 +685,8 @@
"skyblocker.api.cache.HIT": "This data was cached!\nIt's %d seconds old.",
"skyblocker.api.cache.MISS": "This data wasn't cached!",
+ "skyblocker.api.token.authFailure": "Failed to refresh your Skyblocker API token, some features may not work temporarily!",
+ "skyblocker.api.token.noProfileKeys": "Failed to get your profile keys! Some features of the mod may not work temporarily :( (Has your game been open for more than 24 hours?)",
"skyblocker.exotic.crystal": "CRYSTAL",
"skyblocker.exotic.fairy": "FAIRY",