From e806423aad3dfe7712769e27383f5118039ce625 Mon Sep 17 00:00:00 2001 From: LatvianModder Date: Sat, 31 Aug 2024 19:35:36 +0300 Subject: [PATCH] Added identity and tags to WS sessions, added more info to highlight packets --- .../component/DataComponentWrapper.java | 37 +++--- .../mods/kubejs/core/BlockStateKJS.java | 9 +- .../mods/kubejs/core/FluidStackKJS.java | 6 +- .../dev/latvian/mods/kubejs/core/ItemKJS.java | 4 + .../mods/kubejs/core/ItemStackKJS.java | 6 +- .../mods/kubejs/core/mixin/ItemMixin.java | 5 + .../item/ItemModificationKubeEvent.java | 4 + .../kubejs/kubedex/KubedexPayloadHandler.java | 74 +++++++++++- .../net/WebServerUpdateJSONPayload.java | 5 +- .../kubejs/net/WebServerUpdateNBTPayload.java | 22 ++-- .../mods/kubejs/recipe/RecipesKubeEvent.java | 20 +-- .../latvian/mods/kubejs/script/ConsoleJS.java | 2 +- .../mods/kubejs/script/ScriptManager.java | 4 +- .../latvian/mods/kubejs/web/KJSWSSession.java | 13 +- .../mods/kubejs/web/LocalWebServer.java | 24 +--- .../latvian/mods/kubejs/web/RelativeURL.java | 29 +++++ .../latvian/mods/kubejs/web/SessionInfo.java | 28 +++++ .../mods/kubejs/web/local/KubeJSWeb.java | 114 ++++++++++++++++-- .../web/local/client/KubeJSClientWeb.java | 15 ++- 19 files changed, 333 insertions(+), 88 deletions(-) create mode 100644 src/main/java/dev/latvian/mods/kubejs/web/RelativeURL.java create mode 100644 src/main/java/dev/latvian/mods/kubejs/web/SessionInfo.java diff --git a/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java b/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java index 17c2c7221..5f7f56bad 100644 --- a/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java +++ b/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java @@ -5,6 +5,7 @@ import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.serialization.Codec; import com.mojang.serialization.DynamicOps; import dev.latvian.mods.kubejs.plugin.KubeJSPlugin; import dev.latvian.mods.kubejs.plugin.KubeJSPlugins; @@ -300,18 +301,21 @@ static StringBuilder mapToString(StringBuilder builder, DynamicOps ops, Dat boolean first = true; for (var comp : map) { + var id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(comp.type()); + var codec = comp.type().codec(); + + if (id == null || codec == null) { + continue; + } + if (first) { first = false; } else { builder.append(','); } - var id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(comp.type()); - var optional = comp.encodeValue(ops).result(); - - if (id != null && !optional.isEmpty()) { - builder.append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()).append('=').append(optional.get()); - } + var value = codec == Codec.BOOL ? comp.value() : codec.encodeStart(ops, Cast.to(comp.value())).getOrThrow(); + builder.append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()).append('=').append(value); } builder.append(']'); @@ -324,21 +328,24 @@ static StringBuilder patchToString(StringBuilder builder, DynamicOps ops, D boolean first = true; for (var comp : patch.entrySet()) { + var id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(comp.getKey()); + var codec = comp.getKey().codec(); + + if (id == null || codec == null) { + continue; + } + if (first) { first = false; } else { builder.append(','); } - var id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(comp.getKey()); - - if (id != null) { - if (comp.getValue().isPresent()) { - var value = comp.getKey().codecOrThrow().encodeStart(ops, Cast.to(comp.getValue().get())).result().get(); - builder.append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()).append('=').append(value); - } else { - builder.append('!').append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()); - } + if (comp.getValue().isPresent()) { + var value = codec == Codec.BOOL ? comp.getValue().get() : codec.encodeStart(ops, Cast.to(comp.getValue().get())).result().get(); + builder.append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()).append('=').append(value); + } else { + builder.append('!').append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java index cee909aeb..6ccd56363 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java @@ -6,7 +6,7 @@ import dev.latvian.mods.kubejs.recipe.match.Replaceable; import dev.latvian.mods.kubejs.script.ScriptType; import dev.latvian.mods.kubejs.util.ID; -import dev.latvian.mods.kubejs.web.LocalWebServer; +import dev.latvian.mods.kubejs.web.RelativeURL; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.util.RemapPrefixForJS; import net.minecraft.core.BlockPos; @@ -21,8 +21,6 @@ import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; -import java.util.Map; - @RemapPrefixForJS("kjs$") public interface BlockStateKJS extends RegistryObjectKJS, Replaceable { @Override @@ -75,8 +73,7 @@ default Object replaceThisWith(Context cx, Object with) { return with instanceof BlockState state ? state : with instanceof Block block ? block.defaultBlockState() : cx.jsToJava(with, BlockWrapper.STATE_TYPE_INFO); } - default String kjs$getWebIconURL(int size) { - var url = "/img/" + size + "/block/" + ID.url(kjs$getIdLocation()); - return LocalWebServer.getURL(url, Map.of()); + default RelativeURL kjs$getWebIconURL(int size) { + return new RelativeURL("/img/" + size + "/block/" + ID.url(kjs$getIdLocation())); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java index d73b13833..4bd263a68 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java @@ -11,7 +11,7 @@ import dev.latvian.mods.kubejs.util.ID; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.kubejs.util.WithCodec; -import dev.latvian.mods.kubejs.web.LocalWebServer; +import dev.latvian.mods.kubejs.web.RelativeURL; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.util.SpecialEquality; import net.minecraft.core.Holder; @@ -145,9 +145,9 @@ default boolean matches(Context cx, FluidIngredient ingredient, boolean exact) { return ingredient.test(kjs$self()); } - default String kjs$getWebIconURL(DynamicOps ops, int size) { + default RelativeURL kjs$getWebIconURL(DynamicOps ops, int size) { var url = "/img/" + size + "/fluid/" + ID.url(kjs$getIdLocation()); var c = DataComponentWrapper.patchToString(new StringBuilder(), ops, DataComponentWrapper.visualPatch(kjs$self().getComponentsPatch())).toString(); - return LocalWebServer.getURL(url, c.equals("[]") ? Map.of() : Map.of("components", c.substring(1, c.length() - 1))); + return new RelativeURL(url, c.equals("[]") ? Map.of() : Map.of("components", c.substring(1, c.length() - 1))); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/ItemKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/ItemKJS.java index ef599261a..1f118e434 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/ItemKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/ItemKJS.java @@ -61,4 +61,8 @@ public interface ItemKJS extends IngredientSupplierKJS, RegistryObjectKJS default ItemStackKey kjs$getTypeItemStackKey() { throw new NoMixinException(); } + + default void kjs$setCanRepair(boolean repairable) { + throw new NoMixinException(); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java index 71dc9e9d2..c8a92f22d 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java @@ -11,7 +11,7 @@ import dev.latvian.mods.kubejs.util.ID; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.kubejs.util.WithCodec; -import dev.latvian.mods.kubejs.web.LocalWebServer; +import dev.latvian.mods.kubejs.web.RelativeURL; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.util.RemapPrefixForJS; import dev.latvian.mods.rhino.util.ReturnsSelf; @@ -279,9 +279,9 @@ default boolean matches(Context cx, ItemLike itemLike, boolean exact) { return kjs$self().getItem() == itemLike.asItem(); } - default String kjs$getWebIconURL(DynamicOps ops, int size) { + default RelativeURL kjs$getWebIconURL(DynamicOps ops, int size) { var url = "/img/" + size + "/item/" + ID.url(kjs$getIdLocation()); var c = DataComponentWrapper.patchToString(new StringBuilder(), ops, DataComponentWrapper.visualPatch(kjs$self().getComponentsPatch())).toString(); - return LocalWebServer.getURL(url, c.equals("[]") ? Map.of() : Map.of("components", c.substring(1, c.length() - 1))); + return new RelativeURL(url, c.equals("[]") ? Map.of() : Map.of("components", c.substring(1, c.length() - 1))); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/mixin/ItemMixin.java b/src/main/java/dev/latvian/mods/kubejs/core/mixin/ItemMixin.java index 0e5145893..8a58f9353 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/mixin/ItemMixin.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/mixin/ItemMixin.java @@ -238,4 +238,9 @@ private void hurtEnemy(ItemStack itemStack, LivingEntity livingEntity, LivingEnt return kjs$typeItemStackKey; } + + @Override + @Accessor("canRepair") + @Mutable + public abstract void kjs$setCanRepair(boolean repairable); } diff --git a/src/main/java/dev/latvian/mods/kubejs/item/ItemModificationKubeEvent.java b/src/main/java/dev/latvian/mods/kubejs/item/ItemModificationKubeEvent.java index 76e804366..973b8184b 100644 --- a/src/main/java/dev/latvian/mods/kubejs/item/ItemModificationKubeEvent.java +++ b/src/main/java/dev/latvian/mods/kubejs/item/ItemModificationKubeEvent.java @@ -67,5 +67,9 @@ public void setTier(Consumer c) { public void setNameKey(String key) { item.kjs$setNameKey(key); } + + public void disableRepair() { + item.kjs$setCanRepair(false); + } } } diff --git a/src/main/java/dev/latvian/mods/kubejs/kubedex/KubedexPayloadHandler.java b/src/main/java/dev/latvian/mods/kubejs/kubedex/KubedexPayloadHandler.java index e746e365c..24181fed4 100644 --- a/src/main/java/dev/latvian/mods/kubejs/kubedex/KubedexPayloadHandler.java +++ b/src/main/java/dev/latvian/mods/kubejs/kubedex/KubedexPayloadHandler.java @@ -1,5 +1,6 @@ package dev.latvian.mods.kubejs.kubedex; +import dev.latvian.mods.kubejs.component.DataComponentWrapper; import dev.latvian.mods.kubejs.net.WebServerUpdateNBTPayload; import dev.latvian.mods.kubejs.util.Cast; import dev.latvian.mods.kubejs.util.OrderedCompoundTag; @@ -9,16 +10,35 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringTag; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.BucketItem; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.material.Fluids; import net.neoforged.neoforge.network.PacketDistributor; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; public class KubedexPayloadHandler { + private static ListTag sortedTagList(Stream> stream) { + return stream + .map(TagKey::location) + .sorted(ResourceLocation::compareNamespaced) + .map(ResourceLocation::toString) + .map(StringTag::valueOf) + .collect(ListTag::new, ListTag::add, ListTag::addAll); + } + public static void block(ServerPlayer player, BlockPos pos) { var registries = player.server.registryAccess(); var blockState = player.level().getBlockState(pos); @@ -61,7 +81,7 @@ public static void block(ServerPlayer player, BlockPos pos) { } } - PacketDistributor.sendToPlayer(player, new WebServerUpdateNBTPayload("highlight/block", Optional.of(payload))); + PacketDistributor.sendToPlayer(player, new WebServerUpdateNBTPayload("highlight/block", "highlight", Optional.of(payload))); } } @@ -87,7 +107,7 @@ public static void entity(ServerPlayer player, int entityId) { payload.put("data", new CompoundTag()); } - PacketDistributor.sendToPlayer(player, new WebServerUpdateNBTPayload("highlight/entity", Optional.of(payload))); + PacketDistributor.sendToPlayer(player, new WebServerUpdateNBTPayload("highlight/entity", "highlight", Optional.of(payload))); } } @@ -113,9 +133,55 @@ public static void itemStacks(ServerPlayer player, Collection stacks) var payload = new ListTag(); for (var stack : stacks) { - payload.add(ItemStack.CODEC.encodeStart(ops, stack).result().get()); + var tag = new OrderedCompoundTag(); + tag.putString("string", stack.kjs$toItemString0(ops)); + tag.put("item", ItemStack.CODEC.encodeStart(ops, stack).result().get()); + tag.put("name", ComponentSerialization.FLAT_CODEC.encodeStart(ops, stack.getHoverName()).getOrThrow()); + tag.putString("icon", stack.kjs$getWebIconURL(ops, 64).toString()); + + var patch = stack.getComponentsPatch(); + + if (!patch.isEmpty()) { + tag.putString("component_string", DataComponentWrapper.patchToString(new StringBuilder(), ops, patch).toString()); + } + + var itemTagList = sortedTagList(stack.getItemHolder().tags()); + + if (!itemTagList.isEmpty()) { + tag.put("tags", itemTagList); + } + + if (stack.getItem() instanceof BlockItem blockItem && blockItem.getBlock() != Blocks.AIR) { + var blockTagList = sortedTagList(blockItem.getBlock().builtInRegistryHolder().tags()); + + if (!blockTagList.isEmpty()) { + tag.put("block_tags", blockTagList); + } + } + + if (stack.getItem() instanceof BucketItem bucket && bucket.content != Fluids.EMPTY) { + var fluidTagList = sortedTagList(bucket.content.builtInRegistryHolder().tags()); + + if (!fluidTagList.isEmpty()) { + tag.put("fluid_tags", fluidTagList); + } + } + + if (stack.getItem() instanceof SpawnEggItem egg) { + var entityType = egg.getType(stack); + + if (entityType != null) { + var entityTagList = sortedTagList(entityType.builtInRegistryHolder().tags()); + + if (!entityTagList.isEmpty()) { + tag.put("entity_tags", entityTagList); + } + } + } + + payload.add(tag); } - PacketDistributor.sendToPlayer(player, new WebServerUpdateNBTPayload("highlight/items", Optional.of(payload))); + PacketDistributor.sendToPlayer(player, new WebServerUpdateNBTPayload("highlight/items", "highlight", Optional.of(payload))); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateJSONPayload.java b/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateJSONPayload.java index 92c3dffb3..7e1d3794e 100644 --- a/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateJSONPayload.java +++ b/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateJSONPayload.java @@ -10,9 +10,10 @@ import net.neoforged.neoforge.network.handling.IPayloadContext; import org.jetbrains.annotations.Nullable; -public record WebServerUpdateJSONPayload(String event, @Nullable JsonElement payload) implements CustomPacketPayload { +public record WebServerUpdateJSONPayload(String event, String requiredTag, @Nullable JsonElement payload) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.STRING_UTF8, WebServerUpdateJSONPayload::event, + ByteBufCodecs.STRING_UTF8, WebServerUpdateJSONPayload::requiredTag, KubeJSCodecs.JSON_ELEMENT_STREAM_CODEC, WebServerUpdateJSONPayload::payload, WebServerUpdateJSONPayload::new ); @@ -23,6 +24,6 @@ public Type type() { } public void handle(IPayloadContext ctx) { - KubeJSWeb.broadcastUpdate("server/" + event, () -> payload); + KubeJSWeb.broadcastUpdate("server/" + event, requiredTag, () -> payload); } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateNBTPayload.java b/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateNBTPayload.java index d8d091541..47c204858 100644 --- a/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateNBTPayload.java +++ b/src/main/java/dev/latvian/mods/kubejs/net/WebServerUpdateNBTPayload.java @@ -5,19 +5,21 @@ import dev.latvian.mods.kubejs.web.local.KubeJSWeb; import io.netty.buffer.ByteBuf; import net.minecraft.nbt.CollectionTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.world.item.ItemStack; import net.neoforged.neoforge.network.handling.IPayloadContext; import java.util.Optional; -public record WebServerUpdateNBTPayload(String event, Optional payload) implements CustomPacketPayload { +public record WebServerUpdateNBTPayload(String event, String requiredTag, Optional payload) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.STRING_UTF8, WebServerUpdateNBTPayload::event, + ByteBufCodecs.STRING_UTF8, WebServerUpdateNBTPayload::requiredTag, ByteBufCodecs.optional(ByteBufCodecs.TAG), WebServerUpdateNBTPayload::payload, WebServerUpdateNBTPayload::new ); @@ -28,15 +30,19 @@ public Type type() { } public void handle(IPayloadContext ctx) { - if (KubeJSWeb.UPDATES.sessions().isEmpty() && event.equals("highlight/items")) { - var ops = ctx.player().level().registryAccess().createSerializationContext(NbtOps.INSTANCE); + int count = KubeJSWeb.broadcastUpdate("server/" + event, requiredTag, () -> payload.map(tag -> NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, tag)).orElse(null)); + if (count == 0 && event.equals("highlight/items")) { for (var e : (CollectionTag) payload.get()) { - var stack = ItemStack.CODEC.decode(NbtOps.INSTANCE, e).result().get().getFirst(); - KubeJS.LOGGER.info("[Highlighted Item] " + stack.kjs$toItemString0(ops)); + var t = (CompoundTag) e; + KubeJS.LOGGER.info("[Highlighted Item] " + t.getString("string")); + + if (t.get("tags") instanceof ListTag l) { + for (var tag : l) { + KubeJS.LOGGER.info("[Highlighted Item] Item Tag: #" + tag.getAsString()); + } + } } - } else { - KubeJSWeb.broadcastUpdate("server/" + event, () -> payload.map(tag -> NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, tag)).orElse(null)); } } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/recipe/RecipesKubeEvent.java b/src/main/java/dev/latvian/mods/kubejs/recipe/RecipesKubeEvent.java index 87a60d380..a8ff4ec5f 100644 --- a/src/main/java/dev/latvian/mods/kubejs/recipe/RecipesKubeEvent.java +++ b/src/main/java/dev/latvian/mods/kubejs/recipe/RecipesKubeEvent.java @@ -466,7 +466,15 @@ private T reduceRecipesAsync(Context cx, RecipeFilter filter, Function consumer) { - recipeStream(cx, filter).forEach(consumer); + if (filter instanceof IDFilter id) { + var r = originalRecipes.get(id.id); + + if (r != null && !r.removed) { + consumer.accept(r); + } + } else { + recipeStream(cx, filter).forEach(consumer); + } } public int countRecipes(Context cx, RecipeFilter filter) { @@ -486,15 +494,7 @@ public Collection findRecipeIds(Context cx, RecipeFilter filte } public void remove(Context cx, RecipeFilter filter) { - if (filter instanceof IDFilter id) { - var r = originalRecipes.get(id.id); - - if (r != null) { - r.remove(); - } - } else { - forEachRecipe(cx, filter, KubeRecipe::remove); - } + forEachRecipe(cx, filter, KubeRecipe::remove); } public void replaceInput(Context cx, RecipeFilter filter, ReplacementMatchInfo match, Object with) { diff --git a/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java b/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java index 815636b9b..c2d09d087 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java @@ -248,7 +248,7 @@ private ConsoleLine log(LogType type, SourceLine sourceLine, @Nullable Throwable writeToFile(type, line.timestamp, line.getText()); if (wsBroadcaster != null) { - KubeJSWeb.broadcastEvent(wsBroadcaster, type.id, line); + KubeJSWeb.broadcastEvent(wsBroadcaster, type.id, "", line); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java b/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java index b0c658df2..99bc62a63 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java @@ -48,7 +48,7 @@ public void reload() { long start = System.currentTimeMillis(); - KubeJSWeb.broadcastUpdate("before_scripts_loaded", () -> { + KubeJSWeb.broadcastUpdate("before_scripts_loaded", "", () -> { var broadcast = new JsonObject(); broadcast.addProperty("type", scriptType.name); broadcast.addProperty("time", start); @@ -208,7 +208,7 @@ private void load(long startAll) { int t1 = t; int i1 = i; - KubeJSWeb.broadcastUpdate("after_scripts_loaded", () -> { + KubeJSWeb.broadcastUpdate("after_scripts_loaded", "", () -> { var broadcast = new JsonObject(); broadcast.addProperty("type", scriptType.name); broadcast.addProperty("total", t1); diff --git a/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java b/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java index c35159466..92f798676 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java @@ -6,13 +6,24 @@ import dev.latvian.mods.kubejs.util.JsonUtils; public class KJSWSSession extends WSSession { + public SessionInfo info = SessionInfo.NONE; + @Override public void onTextMessage(String message) { if (message.startsWith("{") && message.endsWith("}")) { var json = JsonUtils.fromString(message).getAsJsonObject(); if (json.has("type")) { - onEvent(json.get("type").getAsString(), json.has("payload") ? json.get("payload") : JsonNull.INSTANCE); + var type = json.get("type").getAsString(); + var payload = json.has("payload") ? json.get("payload") : JsonNull.INSTANCE; + + if (type.equals("$")) { + if (payload.isJsonObject()) { + info = SessionInfo.fromJson(info, payload.getAsJsonObject()); + } + } else if (!type.isBlank()) { + onEvent(type, payload); + } } } } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java b/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java index 7cce570cb..9a8fb5b65 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java @@ -10,11 +10,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Map; public record LocalWebServer(HTTPServer server, String url, List endpoints) { public record Endpoint(String method, String path) implements Comparable { @@ -31,32 +28,17 @@ public static LocalWebServer instance() { return instance; } - public static String getURL(String path, Map query) { - if (instance == null) { - return ""; - } - - var url = new StringBuilder(instance.url + path); - boolean first = true; - - for (var entry : query.entrySet()) { - url.append(first ? '?' : '&').append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8)).append('=').append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)); - first = false; - } - - return url.toString(); - } - @HideFromJS public static void start(BlockableEventLoop eventLoop) { if (instance == null) { try { var registry = new LocalWebServerRegistry(eventLoop); KubeJSPlugins.forEachPlugin(registry, KubeJSPlugin::registerLocalWebServer); + var publicAddress = WebServerProperties.get().publicAddress; registry.server.setDaemon(true); registry.server.setServerName("KubeJS " + KubeJS.VERSION); - registry.server.setAddress(WebServerProperties.get().publicAddress.isEmpty() ? "127.0.0.1" : "0.0.0.0"); + registry.server.setAddress(publicAddress.isEmpty() ? "127.0.0.1" : "0.0.0.0"); registry.server.setPort(WebServerProperties.get().port); registry.server.setMaxPortShift(10); @@ -64,7 +46,7 @@ public static void start(BlockableEventLoop eventLoop) { KubeJS.LOGGER.info("Started the local web server at " + url); var endpoints = new ArrayList<>(registry.endpoints); endpoints.sort(null); - instance = new LocalWebServer(registry.server, url, List.copyOf(endpoints)); + instance = new LocalWebServer(registry.server, publicAddress.isEmpty() ? url : publicAddress, List.copyOf(endpoints)); } catch (BindFailedException ex) { KubeJS.LOGGER.warn("Failed to start the local web server - all ports occupied"); } catch (Exception ex) { diff --git a/src/main/java/dev/latvian/mods/kubejs/web/RelativeURL.java b/src/main/java/dev/latvian/mods/kubejs/web/RelativeURL.java new file mode 100644 index 000000000..8b50798fb --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/web/RelativeURL.java @@ -0,0 +1,29 @@ +package dev.latvian.mods.kubejs.web; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public record RelativeURL(String path, Map query) { + public RelativeURL(String path) { + this(path, Map.of()); + } + + @Override + public String toString() { + var url = new StringBuilder(path); + boolean first = true; + + for (var entry : query.entrySet()) { + url.append(first ? '?' : '&').append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8)).append('=').append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)); + first = false; + } + + return url.toString(); + } + + public String fullString() { + var instance = LocalWebServer.instance(); + return instance == null ? "" : (instance.url() + this); + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/web/SessionInfo.java b/src/main/java/dev/latvian/mods/kubejs/web/SessionInfo.java new file mode 100644 index 000000000..cef5560d5 --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/web/SessionInfo.java @@ -0,0 +1,28 @@ +package dev.latvian.mods.kubejs.web; + +import com.google.gson.JsonObject; + +import java.util.HashSet; +import java.util.Set; + +public record SessionInfo(String source, Set tags) { + public static final SessionInfo NONE = new SessionInfo("", Set.of()); + + public static SessionInfo fromJson(SessionInfo info, JsonObject json) { + if (json.has("source")) { + info = new SessionInfo(json.get("source").getAsString(), info.tags()); + } + + if (json.has("tags")) { + var t = new HashSet(); + + for (var e : json.get("tags").getAsJsonArray()) { + t.add(e.getAsString()); + } + + info = new SessionInfo(info.source(), Set.copyOf(t)); + } + + return info; + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java b/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java index acbae912a..0ab1d1b0a 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java @@ -5,8 +5,10 @@ import dev.latvian.apps.tinyserver.ServerRegistry; import dev.latvian.apps.tinyserver.http.response.HTTPResponse; import dev.latvian.apps.tinyserver.http.response.HTTPStatus; +import dev.latvian.apps.tinyserver.ws.Frame; import dev.latvian.apps.tinyserver.ws.WSHandler; import dev.latvian.mods.kubejs.KubeJS; +import dev.latvian.mods.kubejs.KubeJSPaths; import dev.latvian.mods.kubejs.plugin.KubeJSPlugins; import dev.latvian.mods.kubejs.script.ScriptType; import dev.latvian.mods.kubejs.util.RegExpKJS; @@ -19,32 +21,63 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.tags.TagKey; import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.server.ServerLifecycleHooks; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; public class KubeJSWeb { public static WSHandler UPDATES = WSHandler.empty(); - public static void broadcastEvent(WSHandler ws, String event, Supplier payload) { - ws.broadcastText(() -> { - var json = new JsonObject(); - json.addProperty("type", event); + private static final Map BROWSE = Map.of( + "assets", KubeJSPaths.ASSETS, + "data", KubeJSPaths.DATA, + "startup_scripts", KubeJSPaths.STARTUP_SCRIPTS, + "client_scripts", KubeJSPaths.CLIENT_SCRIPTS, + "server_scripts", KubeJSPaths.SERVER_SCRIPTS, + "logs", FMLPaths.GAMEDIR.get().resolve("logs") + ); + + public static int broadcastEvent(WSHandler handler, String event, String requiredTag, Supplier payload) { + if (handler.sessions().isEmpty()) { + return 0; + } + + Frame frame = null; + int count = 0; + + for (var s : handler.sessions().values()) { + if (!requiredTag.isEmpty() && s instanceof KJSWSSession ks && !ks.info.tags().contains(requiredTag)) { + continue; + } - var p = payload == null ? null : payload.get(); + if (frame == null) { + var json = new JsonObject(); + json.addProperty("type", event); - if (p != null && !p.isJsonNull()) { - json.add("payload", p); + var p = payload == null ? null : payload.get(); + + if (p != null && !p.isJsonNull()) { + json.add("payload", p); + } + + frame = Frame.text(json.toString()); } - return json.toString(); - }); + s.send(frame); + count++; + } + + return count; } - public static void broadcastUpdate(String type, Supplier payload) { - broadcastEvent(UPDATES, type, payload); + public static int broadcastUpdate(String type, String requiredTag, Supplier payload) { + return broadcastEvent(UPDATES, type, requiredTag, payload); } public static void addScriptTypeEndpoints(ServerRegistry registry, ScriptType s, Runnable reload) { @@ -71,6 +104,10 @@ public static void register(LocalWebServerRegistry registry) { registry.get("/api", KubeJSWeb::getApi); registry.get("/api/mods", KubeJSWeb::getMods); + registry.get("/api/browse", KubeJSWeb::getBrowse); + registry.get("/api/browse/{directory}", KubeJSWeb::getBrowseDir); + registry.get("/api/browse/{directory}/", KubeJSWeb::getBrowseFile); + registry.get("/api/registries", KubeJSWeb::getRegistriesResponse); // List of all registries registry.get("/api/registries/{namespace}/{path}/keys", KubeJSWeb::getRegistryKeysResponse); // List of all IDs in registry registry.get("/api/registries/{namespace}/{path}/match/{regex}", KubeJSWeb::getRegistryMatchResponse); // List of RegEx matched IDs in registry @@ -129,6 +166,61 @@ private static HTTPResponse getMods(KJSHTTPRequest req) { })); } + private static HTTPResponse getBrowse(KJSHTTPRequest req) { + return HTTPResponse.ok().content(JsonContent.array(json -> BROWSE.keySet().forEach(json::add))); + } + + private static HTTPResponse getBrowseDir(KJSHTTPRequest req) { + var dirName = req.variable("directory"); + var dir = BROWSE.get(dirName); + + if (dir == null) { + return HTTPStatus.NOT_FOUND; + } + + return HTTPResponse.ok().content(JsonContent.array(json -> { + try { + if (Files.exists(dir)) { + for (var file : Files.walk(dir).filter(Files::isRegularFile).filter(Files::isReadable).toList()) { + var fileName = file.getFileName().toString(); + + if (fileName.endsWith(".gz") && dirName.equals("logs")) { + continue; + } + + var o = new JsonObject(); + o.addProperty("path", dir.relativize(file).toString().replace('\\', '/')); + o.addProperty("name", fileName); + o.addProperty("modified", Files.getLastModifiedTime(file).toMillis()); + json.add(o); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + })); + } + + private static HTTPResponse getBrowseFile(KJSHTTPRequest req) { + var dir = BROWSE.get(req.variable("directory")); + + if (dir == null) { + return HTTPStatus.NOT_FOUND; + } + + var file = dir.resolve(req.variable("file")); + + if (Files.notExists(file)) { + return HTTPStatus.NOT_FOUND; + } else if (!Files.isRegularFile(file)) { + return HTTPStatus.BAD_REQUEST; + } else if (!Files.isReadable(file) || !file.startsWith(dir)) { + return HTTPStatus.FORBIDDEN; + } else { + return HTTPResponse.ok().content(file); + } + } + private static HTTPResponse getRegistriesResponse(KJSHTTPRequest req) { return HTTPResponse.ok().content(JsonContent.array(json -> { for (var registry : req.registries().access().registries().toList()) { diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java b/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java index 90e4ca14e..95672473d 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java @@ -19,9 +19,11 @@ import dev.latvian.mods.kubejs.web.LocalWebServerRegistry; import dev.latvian.mods.kubejs.web.local.KubeJSWeb; import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.language.I18n; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.NbtOps; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -52,6 +54,9 @@ public class KubeJSClientWeb { public static void register(LocalWebServerRegistry registry) { KubeJSWeb.addScriptTypeEndpoints(registry, ScriptType.CLIENT, KubeJS.getClientScriptManager()::reload); + registry.get("/api/client/translate/{key}", KubeJSClientWeb::getTranslate); + registry.get("/api/client/component-string/{json}", KubeJSClientWeb::getComponentString); + registry.get("/api/client/search/items", KubeJSClientWeb::getSearchItems); registry.get("/api/client/search/blocks", KubeJSClientWeb::getSearchBlocks); registry.get("/api/client/search/fluids", KubeJSClientWeb::getSearchFluids); @@ -89,6 +94,14 @@ private static HTTPResponse getScreenshot(KJSHTTPRequest req) { return HTTPResponse.ok().content(bytes, "image/png"); } + private static HTTPResponse getTranslate(KJSHTTPRequest req) { + return HTTPResponse.ok().text(I18n.get(req.variable("key"))); + } + + private static HTTPResponse getComponentString(KJSHTTPRequest req) { + return HTTPResponse.ok().text(ComponentSerialization.FLAT_CODEC.decode(req.registries().java(), req.variable("json")).getOrThrow().getFirst().getString()); + } + private static HTTPResponse getSearchItems(KJSHTTPRequest req) { return HTTPResponse.ok().content(JsonContent.object(json -> { var jsonOps = Minecraft.getInstance().level == null ? req.registries().json() : Minecraft.getInstance().level.registryAccess().createSerializationContext(JsonOps.INSTANCE); @@ -101,7 +114,7 @@ private static HTTPResponse getSearchItems(KJSHTTPRequest req) { o.addProperty("cache_key", UUIDWrapper.toString(item.cacheKey())); o.addProperty("id", item.value().kjs$getId()); o.addProperty("name", item.stack().getHoverName().getString()); - o.addProperty("icon_path", item.stack().kjs$getWebIconURL(nbtOps, 64).substring(iconPathRoot.length())); + o.addProperty("icon_path", item.stack().kjs$getWebIconURL(nbtOps, 64).fullString().substring(iconPathRoot.length())); var patch = item.components();