mirror of
https://github.com/plexusorg/Module-HTTPD.git
synced 2026-06-04 00:56:54 +00:00
Dependency bump
This commit is contained in:
+1
-2
@@ -29,13 +29,12 @@ dependencies {
|
||||
implementation("org.projectlombok:lombok:1.18.46")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.46")
|
||||
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.reflections:reflections:0.10.2")
|
||||
plexLibrary("org.eclipse.jetty:jetty-server:12.1.9")
|
||||
plexLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet: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
|
||||
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core")
|
||||
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.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import de.tr7zw.changeme.nbtapi.NBT;
|
||||
import de.tr7zw.changeme.nbtapi.iface.ReadableItemNBT;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
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.inventory.ItemFlag;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
@@ -46,6 +50,12 @@ public final class PlayerInventoryBroadcaster
|
||||
{
|
||||
private static final PlayerInventoryBroadcaster INSTANCE = new PlayerInventoryBroadcaster();
|
||||
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()
|
||||
{
|
||||
@@ -87,7 +97,7 @@ public final class PlayerInventoryBroadcaster
|
||||
|
||||
try
|
||||
{
|
||||
NBT.preloadApi();
|
||||
NbtApiBridge.preload();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
@@ -246,6 +256,67 @@ public final class PlayerInventoryBroadcaster
|
||||
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)
|
||||
{
|
||||
if (item == null || item.getType().isAir()) return null;
|
||||
@@ -274,7 +345,7 @@ public final class PlayerInventoryBroadcaster
|
||||
try
|
||||
{
|
||||
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) {}
|
||||
try
|
||||
@@ -282,12 +353,19 @@ public final class PlayerInventoryBroadcaster
|
||||
List<Component> lore = meta.lore();
|
||||
if (lore != null && !lore.isEmpty())
|
||||
{
|
||||
List<String> out = new ArrayList<>(lore.size());
|
||||
for (Component c : lore)
|
||||
int count = Math.min(lore.size(), MAX_LORE_LINES);
|
||||
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);
|
||||
if (truncated) m.put("loreTruncated", true);
|
||||
}
|
||||
}
|
||||
catch (Throwable ignored) {}
|
||||
@@ -328,19 +406,27 @@ public final class PlayerInventoryBroadcaster
|
||||
if (!keys.isEmpty())
|
||||
{
|
||||
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);
|
||||
if (truncated) m.put("pdcKeysTruncated", true);
|
||||
}
|
||||
}
|
||||
catch (Throwable ignored) {}
|
||||
|
||||
try
|
||||
{
|
||||
Function<ReadableItemNBT, String> toSnbt = ReadableItemNBT::toString;
|
||||
String snbt = NBT.get(item, toSnbt);
|
||||
String snbt = NbtApiBridge.toSnbt(item);
|
||||
if (snbt != null && !snbt.isEmpty() && !"{}".equals(snbt))
|
||||
{
|
||||
m.put("nbt", snbt);
|
||||
putLimited(m, "nbt", snbt, MAX_NBT_CHARS);
|
||||
}
|
||||
}
|
||||
catch (Throwable ignored) {}
|
||||
@@ -348,6 +434,49 @@ public final class PlayerInventoryBroadcaster
|
||||
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
|
||||
{
|
||||
final AsyncContext ctx;
|
||||
|
||||
@@ -55,9 +55,6 @@
|
||||
document.querySelectorAll(selector).forEach(el => {
|
||||
if (el.textContent !== value) {
|
||||
el.textContent = value;
|
||||
el.classList.remove('tick');
|
||||
void el.offsetWidth;
|
||||
el.classList.add('tick');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -268,13 +268,17 @@
|
||||
}
|
||||
const safeType = escapeHtml(item.type);
|
||||
const safeName = item.name ? escapeHtml(item.name) : null;
|
||||
const nameTruncated = item.nameTruncated && Number.isFinite(Number(item.nameTruncatedChars))
|
||||
? Number(item.nameTruncatedChars)
|
||||
: null;
|
||||
const lines = [];
|
||||
lines.push(`<div class="flex items-start gap-3">
|
||||
<div class="ring-card relative size-16 shrink-0 rounded-md bg-muted/40">
|
||||
${renderItemIcon(item)}
|
||||
</div>
|
||||
<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="mt-0.5 text-xs text-muted-foreground">Count: ${item.amount}</p>
|
||||
</div>
|
||||
@@ -284,7 +288,7 @@
|
||||
lines.push(`<div>
|
||||
<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">
|
||||
${item.lore.map(l => `<li>${escapeHtml(l)}</li>`).join('')}
|
||||
${item.lore.map(l => `<li class="break-all">${escapeHtml(l)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>`);
|
||||
}
|
||||
@@ -332,6 +336,7 @@
|
||||
<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('')}
|
||||
</ul>
|
||||
${item.pdcKeysTruncated ? `<p class="mt-1 text-[10px] font-semibold uppercase tracking-wide text-destructive">Plugin NBT keys truncated</p>` : ''}
|
||||
</div>`);
|
||||
}
|
||||
|
||||
@@ -344,7 +349,8 @@
|
||||
Copy
|
||||
</button>
|
||||
</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>`);
|
||||
}
|
||||
|
||||
@@ -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 class="min-w-max">${renderInventoryGrid(inv)}</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))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +98,7 @@ HOME
|
||||
<span class="text-sm text-muted-foreground">Uptime</span>
|
||||
<svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-clock"/></svg>
|
||||
</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>
|
||||
</div>
|
||||
</article>
|
||||
@@ -108,18 +108,18 @@ HOME
|
||||
<span class="text-sm text-muted-foreground">World</span>
|
||||
<svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-package"/></svg>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Module-HTTPD
|
||||
version: 1.7
|
||||
description: HTTPD server for Plex
|
||||
main: dev.plex.HTTPDModule
|
||||
main: dev.plex.HTTPDModule
|
||||
apiCompatibility: 1
|
||||
Reference in New Issue
Block a user