diff options
3 files changed, 113 insertions, 12 deletions
diff --git a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java index 53e57e83..0147938c 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java +++ b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java @@ -41,6 +41,7 @@ import java.nio.file.Path; * this class. */ public class SkyblockerMod implements ClientModInitializer { + public static final String VERSION = FabricLoader.getInstance().getModContainer("skyblocker").get().getMetadata().getVersion().getFriendlyString(); public static final String NAMESPACE = "skyblocker"; public static final Path CONFIG_DIR = FabricLoader.getInstance().getConfigDir().resolve(NAMESPACE); public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java index 06a642b6..b2dc900e 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java @@ -3,6 +3,7 @@ package me.xmrvizzy.skyblocker.skyblock.item; import com.google.gson.Gson; import com.google.gson.JsonObject; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Http; import me.xmrvizzy.skyblocker.utils.Utils; import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.minecraft.client.MinecraftClient; @@ -15,10 +16,7 @@ import net.minecraft.util.Formatting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; +import java.net.http.HttpHeaders; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -29,7 +27,6 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.zip.GZIPInputStream; public class PriceInfoTooltip { private static final Logger LOGGER = LoggerFactory.getLogger(PriceInfoTooltip.class.getName()); @@ -44,6 +41,9 @@ public class PriceInfoTooltip { private static boolean nullMsgSend = false; private final static Gson gson = new Gson(); private static final Map<String, String> apiAddresses; + private static long npcHash = 0; + private static long museumHash = 0; + private static long motesHash = 0; public static void onInjectTooltip(ItemStack stack, TooltipContext context, List<Text> lines) { if (!Utils.isOnSkyblock() || client.player == null) return; @@ -399,11 +399,22 @@ public class PriceInfoTooltip { private static JsonObject downloadPrices(String type) { try { String url = apiAddresses.get(type); - URL apiAddress = new URL(url); - InputStream src = apiAddress.openStream(); - InputStreamReader reader = new InputStreamReader(url.contains(".gz") ? new GZIPInputStream(src) : src); - return new Gson().fromJson(reader, JsonObject.class); - } catch (IOException e) { + + if (type.equals("npc") || type.equals("museum") || type.equals("motes")) { + HttpHeaders headers = Http.sendHeadRequest(url); + long combinedHash = Http.getEtag(headers).hashCode() + Http.getLastModified(headers).hashCode(); + + switch (type) { + case "npc": if (npcHash == combinedHash) return npcPricesJson; else npcHash = combinedHash; + case "museum": if (museumHash == combinedHash) return isMuseumJson; else museumHash = combinedHash; + case "motes": if (motesHash == combinedHash) return motesPricesJson; else motesHash = combinedHash; + } + } + + String apiResponse = Http.sendGetRequest(url); + + return new Gson().fromJson(apiResponse, JsonObject.class); + } catch (Exception e) { LOGGER.warn("[Skyblocker] Failed to download " + type + " prices!", e); return null; } @@ -411,8 +422,8 @@ public class PriceInfoTooltip { static { apiAddresses = new HashMap<>(); - apiAddresses.put("1 day avg", "https://moulberry.codes/auction_averages_lbin/1day.json.gz"); - apiAddresses.put("3 day avg", "https://moulberry.codes/auction_averages_lbin/3day.json.gz"); + apiAddresses.put("1 day avg", "https://moulberry.codes/auction_averages_lbin/1day.json"); + apiAddresses.put("3 day avg", "https://moulberry.codes/auction_averages_lbin/3day.json"); apiAddresses.put("bazaar", "https://hysky.de/api/bazaar"); apiAddresses.put("lowest bins", "https://hysky.de/api/auctions/lowestbins"); apiAddresses.put("npc", "https://hysky.de/api/npcprice"); diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Http.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Http.java new file mode 100644 index 00000000..3461189c --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Http.java @@ -0,0 +1,89 @@ +package me.xmrvizzy.skyblocker.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import me.xmrvizzy.skyblocker.SkyblockerMod; +import net.minecraft.SharedConstants; + +/** + * @implNote All http requests are sent using HTTP 2 + */ +public class Http { + private static final String USER_AGENT = "Skyblocker/" + SkyblockerMod.VERSION + " (" + SharedConstants.getGameVersion().getName() + ")"; + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + public static String sendGetRequest(String url) throws IOException, InterruptedException { + HttpRequest request = 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(); + + HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream()); + InputStream decodedInputStream = getDecodedInputStream(response); + String body = new String(decodedInputStream.readAllBytes()); + + return body; + } + + public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .method("HEAD", BodyPublishers.noBody()) + .header("User-Agent", USER_AGENT) + .version(Version.HTTP_2) + .uri(URI.create(url)) + .build(); + + HttpResponse<Void> response = HTTP_CLIENT.send(request, BodyHandlers.discarding()); + return response.headers(); + } + + private static InputStream getDecodedInputStream(HttpResponse<InputStream> response) { + String encoding = getContentEncoding(response); + + try { + switch (encoding) { + case "": + return response.body(); + case "gzip": + return new GZIPInputStream(response.body()); + case "deflate": + return new InflaterInputStream(response.body()); + default: + throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static String getContentEncoding(HttpResponse<InputStream> response) { + return response.headers().firstValue("Content-Encoding").orElse(""); + } + + public static String getEtag(HttpHeaders headers) { + return headers.firstValue("Etag").orElse(""); + } + + public static String getLastModified(HttpHeaders headers) { + return headers.firstValue("Last-Modified").orElse(""); + } +} |