Skip to content

Commit 563b450

Browse files
committed
Dependency bump
1 parent 2150fdb commit 563b450

6 files changed

Lines changed: 159 additions & 27 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ dependencies {
2929
implementation("org.projectlombok:lombok:1.18.46")
3030
annotationProcessor("org.projectlombok:lombok:1.18.46")
3131
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
32-
implementation("dev.plex:server:1.7-SNAPSHOT")
32+
implementation("dev.plex:server:2.0-SNAPSHOT")
3333
implementation("org.json:json:20251224")
3434
implementation("org.reflections:reflections:0.10.2")
3535
plexLibrary("org.eclipse.jetty:jetty-server:12.1.9")
3636
plexLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.9")
3737
plexLibrary("org.eclipse.jetty:jetty-proxy:12.1.9")
38-
implementation("de.tr7zw:item-nbt-api:2.15.7")
3938
implementation(platform("com.intellectualsites.bom:bom-newest:1.56")) // Ref: https://github.com/IntellectualSites/bom
4039
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core")
4140
implementation("commons-io:commons-io:2.22.0")

src/main/java/dev/plex/request/impl/PlayerInventoryBroadcaster.java

Lines changed: 141 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@
2525
import java.util.concurrent.ConcurrentHashMap;
2626
import java.util.concurrent.Executors;
2727
import java.util.concurrent.ScheduledExecutorService;
28+
import java.lang.reflect.Method;
2829
import java.util.concurrent.atomic.AtomicInteger;
2930
import java.util.function.Function;
3031

31-
import de.tr7zw.changeme.nbtapi.NBT;
32-
import de.tr7zw.changeme.nbtapi.iface.ReadableItemNBT;
32+
import org.bukkit.plugin.Plugin;
3333
import net.kyori.adventure.text.Component;
34-
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
34+
import net.kyori.adventure.text.KeybindComponent;
35+
import net.kyori.adventure.text.ScoreComponent;
36+
import net.kyori.adventure.text.SelectorComponent;
37+
import net.kyori.adventure.text.TextComponent;
38+
import net.kyori.adventure.text.TranslatableComponent;
3539
import org.bukkit.NamespacedKey;
3640
import org.bukkit.inventory.ItemFlag;
3741
import org.bukkit.persistence.PersistentDataContainer;
@@ -46,6 +50,12 @@ public final class PlayerInventoryBroadcaster
4650
{
4751
private static final PlayerInventoryBroadcaster INSTANCE = new PlayerInventoryBroadcaster();
4852
private static final long REFRESH_TICKS = 20L; // 1 second
53+
private static final int MAX_NAME_CHARS = 256;
54+
private static final int MAX_LORE_LINES = 20;
55+
private static final int MAX_LORE_LINE_CHARS = 256;
56+
private static final int MAX_NBT_CHARS = 4096;
57+
private static final int MAX_PDC_KEYS = 64;
58+
private static final int MAX_PDC_KEY_CHARS = 128;
4959

5060
public static PlayerInventoryBroadcaster get()
5161
{
@@ -87,7 +97,7 @@ public synchronized void start()
8797

8898
try
8999
{
90-
NBT.preloadApi();
100+
NbtApiBridge.preload();
91101
}
92102
catch (Throwable t)
93103
{
@@ -246,6 +256,67 @@ private String buildPayload(Player p)
246256
return new GsonBuilder().serializeNulls().create().toJson(root);
247257
}
248258

259+
private static String limit(String value, int maxChars)
260+
{
261+
if (value == null || value.length() <= maxChars) return value;
262+
return value.substring(0, maxChars) + "… [Truncated " + (value.length() - maxChars) + " characters]";
263+
}
264+
265+
private static void putLimited(Map<String, Object> map, String key, String value, int maxChars)
266+
{
267+
if (value == null || value.isEmpty()) return;
268+
map.put(key, limit(value, maxChars));
269+
if (value.length() > maxChars)
270+
{
271+
map.put(key + "Truncated", true);
272+
map.put(key + "TruncatedChars", value.length() - maxChars);
273+
}
274+
}
275+
276+
private static void putLimited(Map<String, Object> map, String key, Component component, int maxChars)
277+
{
278+
LimitedText text = limitedPlainText(component, maxChars);
279+
if (text.text().isEmpty()) return;
280+
map.put(key, text.truncated()
281+
? text.text() + "… [Truncated " + (text.totalChars() - maxChars) + " characters]"
282+
: text.text());
283+
if (text.truncated())
284+
{
285+
map.put(key + "Truncated", true);
286+
map.put(key + "TruncatedChars", text.totalChars() - maxChars);
287+
}
288+
}
289+
290+
private static LimitedText limitedPlainText(Component component, int maxChars)
291+
{
292+
StringBuilder out = new StringBuilder(Math.min(maxChars, 256));
293+
int total = appendPlain(component, out, maxChars);
294+
return new LimitedText(out.toString(), total, total > maxChars);
295+
}
296+
297+
private static int appendPlain(Component component, StringBuilder out, int maxChars)
298+
{
299+
int total = appendComponentValue(component, out, maxChars);
300+
for (Component child : component.children())
301+
{
302+
total += appendPlain(child, out, maxChars - Math.min(out.length(), maxChars));
303+
}
304+
return total;
305+
}
306+
307+
private static int appendComponentValue(Component component, StringBuilder out, int remaining)
308+
{
309+
String value = null;
310+
if (component instanceof TextComponent text) value = text.content();
311+
else if (component instanceof TranslatableComponent translatable) value = translatable.fallback() != null ? translatable.fallback() : translatable.key();
312+
else if (component instanceof KeybindComponent keybind) value = keybind.keybind();
313+
else if (component instanceof ScoreComponent score) value = score.value() != null ? score.value() : score.name();
314+
else if (component instanceof SelectorComponent selector) value = selector.pattern();
315+
if (value == null || value.isEmpty()) return 0;
316+
if (remaining > 0) out.append(value, 0, Math.min(value.length(), remaining));
317+
return value.length();
318+
}
319+
249320
private static Map<String, Object> serializeItem(ItemStack item)
250321
{
251322
if (item == null || item.getType().isAir()) return null;
@@ -274,20 +345,27 @@ private static Map<String, Object> serializeItem(ItemStack item)
274345
try
275346
{
276347
Component name = meta.displayName();
277-
if (name != null) m.put("name", PlainTextComponentSerializer.plainText().serialize(name));
348+
if (name != null) putLimited(m, "name", name, MAX_NAME_CHARS);
278349
}
279350
catch (Throwable ignored) {}
280351
try
281352
{
282353
List<Component> lore = meta.lore();
283354
if (lore != null && !lore.isEmpty())
284355
{
285-
List<String> out = new ArrayList<>(lore.size());
286-
for (Component c : lore)
356+
int count = Math.min(lore.size(), MAX_LORE_LINES);
357+
List<String> out = new ArrayList<>(count);
358+
boolean truncated = lore.size() > MAX_LORE_LINES;
359+
for (int i = 0; i < count; i++)
287360
{
288-
out.add(PlainTextComponentSerializer.plainText().serialize(c));
361+
LimitedText line = limitedPlainText(lore.get(i), MAX_LORE_LINE_CHARS);
362+
if (line.truncated()) truncated = true;
363+
out.add(line.truncated()
364+
? line.text() + "… [Truncated " + (line.totalChars() - MAX_LORE_LINE_CHARS) + " characters]"
365+
: line.text());
289366
}
290367
m.put("lore", out);
368+
if (truncated) m.put("loreTruncated", true);
291369
}
292370
}
293371
catch (Throwable ignored) {}
@@ -328,26 +406,77 @@ private static Map<String, Object> serializeItem(ItemStack item)
328406
if (!keys.isEmpty())
329407
{
330408
Set<String> out = new TreeSet<>();
331-
for (NamespacedKey k : keys) out.add(k.toString());
409+
boolean truncated = keys.size() > MAX_PDC_KEYS;
410+
int count = 0;
411+
for (NamespacedKey k : keys)
412+
{
413+
if (count++ >= MAX_PDC_KEYS) break;
414+
String key = k.toString();
415+
if (key.length() > MAX_PDC_KEY_CHARS) truncated = true;
416+
out.add(limit(key, MAX_PDC_KEY_CHARS));
417+
}
332418
m.put("pdcKeys", out);
419+
if (truncated) m.put("pdcKeysTruncated", true);
333420
}
334421
}
335422
catch (Throwable ignored) {}
336423

337424
try
338425
{
339-
Function<ReadableItemNBT, String> toSnbt = ReadableItemNBT::toString;
340-
String snbt = NBT.get(item, toSnbt);
426+
String snbt = NbtApiBridge.toSnbt(item);
341427
if (snbt != null && !snbt.isEmpty() && !"{}".equals(snbt))
342428
{
343-
m.put("nbt", snbt);
429+
putLimited(m, "nbt", snbt, MAX_NBT_CHARS);
344430
}
345431
}
346432
catch (Throwable ignored) {}
347433
}
348434
return m;
349435
}
350436

437+
private record LimitedText(String text, int totalChars, boolean truncated) {}
438+
439+
private static final class NbtApiBridge
440+
{
441+
private static volatile Method getMethod;
442+
private static volatile Method preloadMethod;
443+
static void preload() throws Exception
444+
{
445+
Method method = preloadMethod;
446+
if (method == null)
447+
{
448+
Class<?> nbt = nbtClass();
449+
method = nbt.getMethod("preloadApi");
450+
preloadMethod = method;
451+
}
452+
method.invoke(null);
453+
}
454+
455+
static String toSnbt(ItemStack item) throws Exception
456+
{
457+
Method method = getMethod;
458+
if (method == null)
459+
{
460+
Class<?> nbt = nbtClass();
461+
method = nbt.getMethod("get", ItemStack.class, Function.class);
462+
getMethod = method;
463+
}
464+
Function<Object, String> stringify = Object::toString;
465+
Object result = method.invoke(null, item, stringify);
466+
return result instanceof String s ? s : null;
467+
}
468+
469+
private static Class<?> nbtClass() throws ClassNotFoundException
470+
{
471+
Plugin plugin = Bukkit.getPluginManager().getPlugin("NBTAPI");
472+
if (plugin == null || !plugin.isEnabled())
473+
{
474+
throw new ClassNotFoundException("NBTAPI plugin is not enabled");
475+
}
476+
return Class.forName("de.tr7zw.changeme.nbtapi.NBT", true, plugin.getClass().getClassLoader());
477+
}
478+
}
479+
351480
private static final class Subscriber
352481
{
353482
final AsyncContext ctx;

src/main/resources/httpd/assets/dashboard.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@
5555
document.querySelectorAll(selector).forEach(el => {
5656
if (el.textContent !== value) {
5757
el.textContent = value;
58-
el.classList.remove('tick');
59-
void el.offsetWidth;
60-
el.classList.add('tick');
6158
}
6259
});
6360
}

src/main/resources/httpd/assets/player.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,17 @@
268268
}
269269
const safeType = escapeHtml(item.type);
270270
const safeName = item.name ? escapeHtml(item.name) : null;
271+
const nameTruncated = item.nameTruncated && Number.isFinite(Number(item.nameTruncatedChars))
272+
? Number(item.nameTruncatedChars)
273+
: null;
271274
const lines = [];
272275
lines.push(`<div class="flex items-start gap-3">
273276
<div class="ring-card relative size-16 shrink-0 rounded-md bg-muted/40">
274277
${renderItemIcon(item)}
275278
</div>
276279
<div class="min-w-0">
277-
${safeName ? `<p class="truncate text-base font-medium italic">${safeName}</p>` : ''}
280+
${safeName ? `<p class="max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-base font-medium italic">${safeName}</p>` : ''}
281+
${nameTruncated != null ? `<p class="mt-0.5 text-[10px] font-semibold uppercase tracking-wide text-destructive">Name truncated by ${nameTruncated.toLocaleString()} characters</p>` : ''}
278282
<p class="font-mono text-xs text-muted-foreground break-all">${safeType}</p>
279283
<p class="mt-0.5 text-xs text-muted-foreground">Count: ${item.amount}</p>
280284
</div>
@@ -284,7 +288,7 @@
284288
lines.push(`<div>
285289
<p class="text-[10px] uppercase tracking-wide text-muted-foreground">Lore</p>
286290
<ul class="mt-1 space-y-0.5 text-xs italic text-foreground/80">
287-
${item.lore.map(l => `<li>${escapeHtml(l)}</li>`).join('')}
291+
${item.lore.map(l => `<li class="break-all">${escapeHtml(l)}</li>`).join('')}
288292
</ul>
289293
</div>`);
290294
}
@@ -332,6 +336,7 @@
332336
<ul class="mt-1 space-y-0.5 font-mono text-xs text-foreground/80">
333337
${item.pdcKeys.map(k => `<li class="break-all">${escapeHtml(k)}</li>`).join('')}
334338
</ul>
339+
${item.pdcKeysTruncated ? `<p class="mt-1 text-[10px] font-semibold uppercase tracking-wide text-destructive">Plugin NBT keys truncated</p>` : ''}
335340
</div>`);
336341
}
337342

@@ -344,7 +349,8 @@
344349
Copy
345350
</button>
346351
</div>
347-
<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>
352+
<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>
353+
${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>` : ''}
348354
</div>`);
349355
}
350356

@@ -375,7 +381,7 @@
375381
<div data-inv-grid class="-mx-2 overflow-x-auto px-2 pb-2 sm:mx-0 sm:px-0">
376382
<div class="min-w-max">${renderInventoryGrid(inv)}</div>
377383
</div>
378-
<div data-inv-detail class="rounded-xl border border-border/40 bg-background/40 p-4">
384+
<div data-inv-detail class="min-w-0 rounded-xl border border-border/40 bg-background/40 p-4">
379385
${renderDetailPanel(getItemBySlotKey(inv, selectedKey))}
380386
</div>
381387
</div>

src/main/resources/httpd/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ <h1 class="text-3xl font-medium tracking-tight md:text-4xl">Overview</h1>
9898
<span class="text-sm text-muted-foreground">Uptime</span>
9999
<svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-clock"/></svg>
100100
</div>
101-
<div class="my-auto font-mono text-2xl tracking-tight">
101+
<div class="mt-3 flex flex-1 items-center justify-start font-mono text-4xl font-medium tracking-tight md:text-5xl">
102102
<span data-stat="uptime"></span>
103103
</div>
104104
</article>
@@ -108,18 +108,18 @@ <h1 class="text-3xl font-medium tracking-tight md:text-4xl">Overview</h1>
108108
<span class="text-sm text-muted-foreground">World</span>
109109
<svg class="size-4 text-muted-foreground" aria-hidden="true"><use href="#i-package"/></svg>
110110
</div>
111-
<dl class="my-auto grid grid-cols-3 gap-2 text-sm">
111+
<dl class="mt-3 grid flex-1 grid-cols-3 items-center gap-3 text-center">
112112
<div>
113113
<dt class="text-xs text-muted-foreground">Worlds</dt>
114-
<dd data-stat="worlds" class="mt-1 tabular text-lg text-foreground"></dd>
114+
<dd data-stat="worlds" class="mt-1 tabular text-4xl font-medium tracking-tight text-foreground md:text-5xl"></dd>
115115
</div>
116116
<div>
117117
<dt class="text-xs text-muted-foreground">Chunks</dt>
118-
<dd data-stat="chunks" class="mt-1 tabular text-lg text-foreground"></dd>
118+
<dd data-stat="chunks" class="mt-1 tabular text-4xl font-medium tracking-tight text-foreground md:text-5xl"></dd>
119119
</div>
120120
<div>
121121
<dt class="text-xs text-muted-foreground">Entities</dt>
122-
<dd data-stat="entities" class="mt-1 tabular text-lg text-foreground"></dd>
122+
<dd data-stat="entities" class="mt-1 tabular text-4xl font-medium tracking-tight text-foreground md:text-5xl"></dd>
123123
</div>
124124
</dl>
125125
</article>

src/main/resources/module.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
name: Module-HTTPD
22
version: 1.7
33
description: HTTPD server for Plex
4-
main: dev.plex.HTTPDModule
4+
main: dev.plex.HTTPDModule
5+
apiCompatibility: 1

0 commit comments

Comments
 (0)