Dependency bump

This commit is contained in:
2026-05-19 12:02:03 -04:00
parent 2150fdb237
commit 563b45010d
6 changed files with 159 additions and 27 deletions
+1 -2
View File
@@ -29,13 +29,12 @@ dependencies {
implementation("org.projectlombok:lombok:1.18.46") implementation("org.projectlombok:lombok:1.18.46")
annotationProcessor("org.projectlombok:lombok:1.18.46") annotationProcessor("org.projectlombok:lombok:1.18.46")
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
implementation("dev.plex:server:1.7-SNAPSHOT") implementation("dev.plex:server:2.0-SNAPSHOT")
implementation("org.json:json:20251224") implementation("org.json:json:20251224")
implementation("org.reflections:reflections:0.10.2") implementation("org.reflections:reflections:0.10.2")
plexLibrary("org.eclipse.jetty:jetty-server:12.1.9") plexLibrary("org.eclipse.jetty:jetty-server:12.1.9")
plexLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.9") plexLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.9")
plexLibrary("org.eclipse.jetty:jetty-proxy:12.1.9") plexLibrary("org.eclipse.jetty:jetty-proxy:12.1.9")
implementation("de.tr7zw:item-nbt-api:2.15.7")
implementation(platform("com.intellectualsites.bom:bom-newest:1.56")) // Ref: https://github.com/IntellectualSites/bom implementation(platform("com.intellectualsites.bom:bom-newest:1.56")) // Ref: https://github.com/IntellectualSites/bom
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core") compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core")
implementation("commons-io:commons-io:2.22.0") implementation("commons-io:commons-io:2.22.0")
@@ -25,13 +25,17 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function; import java.util.function.Function;
import de.tr7zw.changeme.nbtapi.NBT; import org.bukkit.plugin.Plugin;
import de.tr7zw.changeme.nbtapi.iface.ReadableItemNBT;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.KeybindComponent;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemFlag;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
@@ -46,6 +50,12 @@ public final class PlayerInventoryBroadcaster
{ {
private static final PlayerInventoryBroadcaster INSTANCE = new PlayerInventoryBroadcaster(); private static final PlayerInventoryBroadcaster INSTANCE = new PlayerInventoryBroadcaster();
private static final long REFRESH_TICKS = 20L; // 1 second private static final long REFRESH_TICKS = 20L; // 1 second
private static final int MAX_NAME_CHARS = 256;
private static final int MAX_LORE_LINES = 20;
private static final int MAX_LORE_LINE_CHARS = 256;
private static final int MAX_NBT_CHARS = 4096;
private static final int MAX_PDC_KEYS = 64;
private static final int MAX_PDC_KEY_CHARS = 128;
public static PlayerInventoryBroadcaster get() public static PlayerInventoryBroadcaster get()
{ {
@@ -87,7 +97,7 @@ public final class PlayerInventoryBroadcaster
try try
{ {
NBT.preloadApi(); NbtApiBridge.preload();
} }
catch (Throwable t) catch (Throwable t)
{ {
@@ -246,6 +256,67 @@ public final class PlayerInventoryBroadcaster
return new GsonBuilder().serializeNulls().create().toJson(root); return new GsonBuilder().serializeNulls().create().toJson(root);
} }
private static String limit(String value, int maxChars)
{
if (value == null || value.length() <= maxChars) return value;
return value.substring(0, maxChars) + "… [Truncated " + (value.length() - maxChars) + " characters]";
}
private static void putLimited(Map<String, Object> map, String key, String value, int maxChars)
{
if (value == null || value.isEmpty()) return;
map.put(key, limit(value, maxChars));
if (value.length() > maxChars)
{
map.put(key + "Truncated", true);
map.put(key + "TruncatedChars", value.length() - maxChars);
}
}
private static void putLimited(Map<String, Object> map, String key, Component component, int maxChars)
{
LimitedText text = limitedPlainText(component, maxChars);
if (text.text().isEmpty()) return;
map.put(key, text.truncated()
? text.text() + "… [Truncated " + (text.totalChars() - maxChars) + " characters]"
: text.text());
if (text.truncated())
{
map.put(key + "Truncated", true);
map.put(key + "TruncatedChars", text.totalChars() - maxChars);
}
}
private static LimitedText limitedPlainText(Component component, int maxChars)
{
StringBuilder out = new StringBuilder(Math.min(maxChars, 256));
int total = appendPlain(component, out, maxChars);
return new LimitedText(out.toString(), total, total > maxChars);
}
private static int appendPlain(Component component, StringBuilder out, int maxChars)
{
int total = appendComponentValue(component, out, maxChars);
for (Component child : component.children())
{
total += appendPlain(child, out, maxChars - Math.min(out.length(), maxChars));
}
return total;
}
private static int appendComponentValue(Component component, StringBuilder out, int remaining)
{
String value = null;
if (component instanceof TextComponent text) value = text.content();
else if (component instanceof TranslatableComponent translatable) value = translatable.fallback() != null ? translatable.fallback() : translatable.key();
else if (component instanceof KeybindComponent keybind) value = keybind.keybind();
else if (component instanceof ScoreComponent score) value = score.value() != null ? score.value() : score.name();
else if (component instanceof SelectorComponent selector) value = selector.pattern();
if (value == null || value.isEmpty()) return 0;
if (remaining > 0) out.append(value, 0, Math.min(value.length(), remaining));
return value.length();
}
private static Map<String, Object> serializeItem(ItemStack item) private static Map<String, Object> serializeItem(ItemStack item)
{ {
if (item == null || item.getType().isAir()) return null; if (item == null || item.getType().isAir()) return null;
@@ -274,7 +345,7 @@ public final class PlayerInventoryBroadcaster
try try
{ {
Component name = meta.displayName(); Component name = meta.displayName();
if (name != null) m.put("name", PlainTextComponentSerializer.plainText().serialize(name)); if (name != null) putLimited(m, "name", name, MAX_NAME_CHARS);
} }
catch (Throwable ignored) {} catch (Throwable ignored) {}
try try
@@ -282,12 +353,19 @@ public final class PlayerInventoryBroadcaster
List<Component> lore = meta.lore(); List<Component> lore = meta.lore();
if (lore != null && !lore.isEmpty()) if (lore != null && !lore.isEmpty())
{ {
List<String> out = new ArrayList<>(lore.size()); int count = Math.min(lore.size(), MAX_LORE_LINES);
for (Component c : lore) List<String> out = new ArrayList<>(count);
boolean truncated = lore.size() > MAX_LORE_LINES;
for (int i = 0; i < count; i++)
{ {
out.add(PlainTextComponentSerializer.plainText().serialize(c)); LimitedText line = limitedPlainText(lore.get(i), MAX_LORE_LINE_CHARS);
if (line.truncated()) truncated = true;
out.add(line.truncated()
? line.text() + "… [Truncated " + (line.totalChars() - MAX_LORE_LINE_CHARS) + " characters]"
: line.text());
} }
m.put("lore", out); m.put("lore", out);
if (truncated) m.put("loreTruncated", true);
} }
} }
catch (Throwable ignored) {} catch (Throwable ignored) {}
@@ -328,19 +406,27 @@ public final class PlayerInventoryBroadcaster
if (!keys.isEmpty()) if (!keys.isEmpty())
{ {
Set<String> out = new TreeSet<>(); Set<String> out = new TreeSet<>();
for (NamespacedKey k : keys) out.add(k.toString()); boolean truncated = keys.size() > MAX_PDC_KEYS;
int count = 0;
for (NamespacedKey k : keys)
{
if (count++ >= MAX_PDC_KEYS) break;
String key = k.toString();
if (key.length() > MAX_PDC_KEY_CHARS) truncated = true;
out.add(limit(key, MAX_PDC_KEY_CHARS));
}
m.put("pdcKeys", out); m.put("pdcKeys", out);
if (truncated) m.put("pdcKeysTruncated", true);
} }
} }
catch (Throwable ignored) {} catch (Throwable ignored) {}
try try
{ {
Function<ReadableItemNBT, String> toSnbt = ReadableItemNBT::toString; String snbt = NbtApiBridge.toSnbt(item);
String snbt = NBT.get(item, toSnbt);
if (snbt != null && !snbt.isEmpty() && !"{}".equals(snbt)) if (snbt != null && !snbt.isEmpty() && !"{}".equals(snbt))
{ {
m.put("nbt", snbt); putLimited(m, "nbt", snbt, MAX_NBT_CHARS);
} }
} }
catch (Throwable ignored) {} catch (Throwable ignored) {}
@@ -348,6 +434,49 @@ public final class PlayerInventoryBroadcaster
return m; return m;
} }
private record LimitedText(String text, int totalChars, boolean truncated) {}
private static final class NbtApiBridge
{
private static volatile Method getMethod;
private static volatile Method preloadMethod;
static void preload() throws Exception
{
Method method = preloadMethod;
if (method == null)
{
Class<?> nbt = nbtClass();
method = nbt.getMethod("preloadApi");
preloadMethod = method;
}
method.invoke(null);
}
static String toSnbt(ItemStack item) throws Exception
{
Method method = getMethod;
if (method == null)
{
Class<?> nbt = nbtClass();
method = nbt.getMethod("get", ItemStack.class, Function.class);
getMethod = method;
}
Function<Object, String> stringify = Object::toString;
Object result = method.invoke(null, item, stringify);
return result instanceof String s ? s : null;
}
private static Class<?> nbtClass() throws ClassNotFoundException
{
Plugin plugin = Bukkit.getPluginManager().getPlugin("NBTAPI");
if (plugin == null || !plugin.isEnabled())
{
throw new ClassNotFoundException("NBTAPI plugin is not enabled");
}
return Class.forName("de.tr7zw.changeme.nbtapi.NBT", true, plugin.getClass().getClassLoader());
}
}
private static final class Subscriber private static final class Subscriber
{ {
final AsyncContext ctx; final AsyncContext ctx;
@@ -55,9 +55,6 @@
document.querySelectorAll(selector).forEach(el => { document.querySelectorAll(selector).forEach(el => {
if (el.textContent !== value) { if (el.textContent !== value) {
el.textContent = value; el.textContent = value;
el.classList.remove('tick');
void el.offsetWidth;
el.classList.add('tick');
} }
}); });
} }
+10 -4
View File
@@ -268,13 +268,17 @@
} }
const safeType = escapeHtml(item.type); const safeType = escapeHtml(item.type);
const safeName = item.name ? escapeHtml(item.name) : null; const safeName = item.name ? escapeHtml(item.name) : null;
const nameTruncated = item.nameTruncated && Number.isFinite(Number(item.nameTruncatedChars))
? Number(item.nameTruncatedChars)
: null;
const lines = []; const lines = [];
lines.push(`<div class="flex items-start gap-3"> lines.push(`<div class="flex items-start gap-3">
<div class="ring-card relative size-16 shrink-0 rounded-md bg-muted/40"> <div class="ring-card relative size-16 shrink-0 rounded-md bg-muted/40">
${renderItemIcon(item)} ${renderItemIcon(item)}
</div> </div>
<div class="min-w-0"> <div class="min-w-0">
${safeName ? `<p class="truncate text-base font-medium italic">${safeName}</p>` : ''} ${safeName ? `<p class="max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-base font-medium italic">${safeName}</p>` : ''}
${nameTruncated != null ? `<p class="mt-0.5 text-[10px] font-semibold uppercase tracking-wide text-destructive">Name truncated by ${nameTruncated.toLocaleString()} characters</p>` : ''}
<p class="font-mono text-xs text-muted-foreground break-all">${safeType}</p> <p class="font-mono text-xs text-muted-foreground break-all">${safeType}</p>
<p class="mt-0.5 text-xs text-muted-foreground">Count: ${item.amount}</p> <p class="mt-0.5 text-xs text-muted-foreground">Count: ${item.amount}</p>
</div> </div>
@@ -284,7 +288,7 @@
lines.push(`<div> lines.push(`<div>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground">Lore</p> <p class="text-[10px] uppercase tracking-wide text-muted-foreground">Lore</p>
<ul class="mt-1 space-y-0.5 text-xs italic text-foreground/80"> <ul class="mt-1 space-y-0.5 text-xs italic text-foreground/80">
${item.lore.map(l => `<li>${escapeHtml(l)}</li>`).join('')} ${item.lore.map(l => `<li class="break-all">${escapeHtml(l)}</li>`).join('')}
</ul> </ul>
</div>`); </div>`);
} }
@@ -332,6 +336,7 @@
<ul class="mt-1 space-y-0.5 font-mono text-xs text-foreground/80"> <ul class="mt-1 space-y-0.5 font-mono text-xs text-foreground/80">
${item.pdcKeys.map(k => `<li class="break-all">${escapeHtml(k)}</li>`).join('')} ${item.pdcKeys.map(k => `<li class="break-all">${escapeHtml(k)}</li>`).join('')}
</ul> </ul>
${item.pdcKeysTruncated ? `<p class="mt-1 text-[10px] font-semibold uppercase tracking-wide text-destructive">Plugin NBT keys truncated</p>` : ''}
</div>`); </div>`);
} }
@@ -344,7 +349,8 @@
Copy Copy
</button> </button>
</div> </div>
<pre data-nbt-text class="mt-1 max-h-48 overflow-auto rounded-md bg-muted/40 p-2 font-mono text-[10px] leading-snug whitespace-pre-wrap break-all">${escapeHtml(item.nbt)}</pre> <pre data-nbt-text class="mt-1 max-h-48 max-w-full overflow-auto rounded-md bg-muted/40 p-2 font-mono text-[10px] leading-snug whitespace-pre-wrap break-all">${escapeHtml(item.nbt)}</pre>
${item.nbtTruncated && Number.isFinite(Number(item.nbtTruncatedChars)) ? `<p class="mt-1 text-[10px] font-semibold uppercase tracking-wide text-destructive">NBT truncated by ${Number(item.nbtTruncatedChars).toLocaleString()} characters</p>` : ''}
</div>`); </div>`);
} }
@@ -375,7 +381,7 @@
<div data-inv-grid class="-mx-2 overflow-x-auto px-2 pb-2 sm:mx-0 sm:px-0"> <div data-inv-grid class="-mx-2 overflow-x-auto px-2 pb-2 sm:mx-0 sm:px-0">
<div class="min-w-max">${renderInventoryGrid(inv)}</div> <div class="min-w-max">${renderInventoryGrid(inv)}</div>
</div> </div>
<div data-inv-detail class="rounded-xl border border-border/40 bg-background/40 p-4"> <div data-inv-detail class="min-w-0 rounded-xl border border-border/40 bg-background/40 p-4">
${renderDetailPanel(getItemBySlotKey(inv, selectedKey))} ${renderDetailPanel(getItemBySlotKey(inv, selectedKey))}
</div> </div>
</div> </div>
+5 -5
View File
@@ -98,7 +98,7 @@ HOME
<span class="text-sm text-muted-foreground">Uptime</span> <span class="text-sm text-muted-foreground">Uptime</span>
<svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-clock"/></svg> <svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-clock"/></svg>
</div> </div>
<div class="my-auto font-mono text-2xl tracking-tight"> <div class="mt-3 flex flex-1 items-center justify-start font-mono text-4xl font-medium tracking-tight md:text-5xl">
<span data-stat="uptime"></span> <span data-stat="uptime"></span>
</div> </div>
</article> </article>
@@ -108,18 +108,18 @@ HOME
<span class="text-sm text-muted-foreground">World</span> <span class="text-sm text-muted-foreground">World</span>
<svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-package"/></svg> <svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-package"/></svg>
</div> </div>
<dl class="my-auto grid grid-cols-3 gap-2 text-sm"> <dl class="mt-3 grid flex-1 grid-cols-3 items-center gap-3 text-center">
<div> <div>
<dt class="text-xs text-muted-foreground">Worlds</dt> <dt class="text-xs text-muted-foreground">Worlds</dt>
<dd data-stat="worlds" class="mt-1 tabular text-lg text-foreground"></dd> <dd data-stat="worlds" class="mt-1 tabular text-4xl font-medium tracking-tight text-foreground md:text-5xl"></dd>
</div> </div>
<div> <div>
<dt class="text-xs text-muted-foreground">Chunks</dt> <dt class="text-xs text-muted-foreground">Chunks</dt>
<dd data-stat="chunks" class="mt-1 tabular text-lg text-foreground"></dd> <dd data-stat="chunks" class="mt-1 tabular text-4xl font-medium tracking-tight text-foreground md:text-5xl"></dd>
</div> </div>
<div> <div>
<dt class="text-xs text-muted-foreground">Entities</dt> <dt class="text-xs text-muted-foreground">Entities</dt>
<dd data-stat="entities" class="mt-1 tabular text-lg text-foreground"></dd> <dd data-stat="entities" class="mt-1 tabular text-4xl font-medium tracking-tight text-foreground md:text-5xl"></dd>
</div> </div>
</dl> </dl>
</article> </article>
+1
View File
@@ -2,3 +2,4 @@ name: Module-HTTPD
version: 1.7 version: 1.7
description: HTTPD server for Plex description: HTTPD server for Plex
main: dev.plex.HTTPDModule main: dev.plex.HTTPDModule
apiCompatibility: 1