Skip to content

Commit 0d4fe24

Browse files
committed
feat: add /husktowns dump web dump command
1 parent fc4cbb8 commit 0d4fe24

File tree

19 files changed

+310
-73
lines changed

19 files changed

+310
-73
lines changed

bukkit/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies {
55
implementation 'io.papermc:paperlib:1.0.8'
66
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
77
implementation 'net.william278.cloplib:cloplib-bukkit:2.0.3'
8+
implementation 'net.william278.toilet:toilet-bukkit:1.0.12'
89
implementation 'me.lucko:commodore:2.2'
910
implementation 'net.william278:AdvancementAPI:master-SNAPSHOT'
1011
implementation 'org.bstats:bstats-bukkit:3.1.0'
@@ -50,6 +51,7 @@ shadowJar {
5051
relocate 'net.william278.paginedown', 'net.william278.husktowns.libraries.paginedown'
5152
relocate 'net.william278.desertwell', 'net.william278.husktowns.libraries.desertwell'
5253
relocate 'net.william278.cloplib', 'net.william278.husktowns.libraries.cloplib'
54+
relocate 'net.william278.toilet', 'net.william278.husktowns.libraries.toilet'
5355
relocate 'org.json', 'net.william278.husktowns.libraries.json'
5456
relocate 'dev.dejvokep.boostedyaml', 'net.william278.husktowns.libraries.boostedyaml'
5557
relocate 'org.yaml.snakeyaml', 'net.william278.husktowns.libraries.snakeyaml'

bukkit/src/main/java/net/william278/husktowns/BukkitHuskTowns.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
import net.william278.husktowns.util.BukkitTask;
6161
import net.william278.husktowns.util.Validator;
6262
import net.william278.husktowns.visualizer.Visualizer;
63+
import net.william278.toilet.BukkitToilet;
64+
import net.william278.toilet.Toilet;
6365
import org.bstats.bukkit.Metrics;
6466
import org.bstats.charts.SimplePie;
6567
import org.bukkit.Bukkit;
@@ -95,6 +97,7 @@ public class BukkitHuskTowns extends JavaPlugin implements HuskTowns, BukkitTask
9597

9698
private AudienceProvider audiences;
9799
private MorePaperLib paperLib;
100+
private Toilet toilet;
98101
private AsynchronousScheduler asyncScheduler;
99102
private RegionalScheduler regionalScheduler;
100103
private OperationListener operationListener;
@@ -193,6 +196,7 @@ public void onEnable() {
193196
// Initialize PaperLib and Adventure
194197
this.paperLib = new MorePaperLib(this);
195198
this.audiences = BukkitAudiences.create(this);
199+
this.toilet = BukkitToilet.create(getDumpOptions());
196200

197201
// Load advancements
198202
if (this.settings.getGeneral().isDoAdvancements()) {
@@ -265,11 +269,13 @@ public Optional<Broker> getMessageBroker() {
265269
}
266270

267271
@Override
268-
public @NotNull HookManager getHookManager() {
272+
@NotNull
273+
public HookManager getHookManager() {
269274
return hookManager;
270275
}
271276

272277
@Override
278+
@NotNull
273279
public Version getPluginVersion() {
274280
return Version.fromString(getDescription().getVersion());
275281
}

common/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ dependencies {
1515
exclude module: 'slf4j-api'
1616
}
1717

18+
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.12'
19+
1820
compileOnly 'net.kyori:adventure-api:4.19.0'
1921
compileOnly 'net.kyori:adventure-platform-api:4.3.4'
2022
compileOnly 'org.jetbrains:annotations:26.0.2'

common/src/main/java/net/william278/husktowns/HuskTowns.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
import java.util.stream.Collectors;
6666

6767
public interface HuskTowns extends Task.Supplier, ConfigProvider, EventDispatcher, UserProvider,
68-
AdvancementProvider, DataPruner, GsonProvider, OperationHandler, UserListener, MetaProvider {
68+
AdvancementProvider, DataPruner, GsonProvider, OperationHandler, UserListener, MetaProvider, DumpProvider {
6969

7070
@NotNull
7171
Database getDatabase();

common/src/main/java/net/william278/husktowns/command/HuskTownsCommand.java

+29-62
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,24 @@
2222
import de.themoep.minedown.adventure.MineDown;
2323
import net.kyori.adventure.text.Component;
2424
import net.kyori.adventure.text.JoinConfiguration;
25+
import net.kyori.adventure.text.event.ClickEvent;
2526
import net.kyori.adventure.text.format.NamedTextColor;
2627
import net.kyori.adventure.text.format.TextColor;
28+
import net.kyori.adventure.text.format.TextDecoration;
2729
import net.william278.desertwell.about.AboutMenu;
2830
import net.william278.desertwell.util.UpdateChecker;
2931
import net.william278.husktowns.HuskTowns;
3032
import net.william278.husktowns.migrator.LegacyMigrator;
3133
import net.william278.husktowns.migrator.Migrator;
3234
import net.william278.husktowns.user.CommandUser;
33-
import org.apache.commons.text.WordUtils;
35+
import net.william278.husktowns.util.StatusLine;
3436
import org.jetbrains.annotations.NotNull;
3537
import org.jetbrains.annotations.Nullable;
3638

3739
import java.util.ArrayList;
3840
import java.util.Arrays;
3941
import java.util.List;
4042
import java.util.Optional;
41-
import java.util.function.Function;
4243

4344
public final class HuskTownsCommand extends Command {
4445

@@ -50,6 +51,7 @@ public HuskTownsCommand(@NotNull HuskTowns plugin) {
5051
new ReloadCommand(this, plugin),
5152
new UpdateCommand(this, plugin),
5253
new StatusCommand(this, plugin),
54+
new DumpCommand(this, plugin),
5355
new MigrateCommand(this, plugin),
5456
getHelpCommand(),
5557
(ChildCommand) getDefaultExecutor()
@@ -149,6 +151,31 @@ public void execute(@NotNull CommandUser executor, @NotNull String[] args) {
149151
}
150152
}
151153

154+
private static class DumpCommand extends ChildCommand {
155+
156+
protected DumpCommand(@NotNull Command parent, @NotNull HuskTowns plugin) {
157+
super("dump", List.of(), parent, "<confirm>", plugin);
158+
this.setConsoleExecutable(true);
159+
this.setOperatorCommand(true);
160+
}
161+
162+
@Override
163+
public void execute(@NotNull CommandUser executor, @NotNull String[] args) {
164+
if (!parseStringArg(args, 0).map(s -> s.equals("confirm")).orElse(false)) {
165+
plugin.getLocales().getLocale("system_dump_confirm").ifPresent(executor::sendMessage);
166+
return;
167+
}
168+
169+
plugin.getLocales().getLocale("system_dump_started").ifPresent(executor::sendMessage);
170+
plugin.runAsync(() -> {
171+
final String url = plugin.createDump(executor);
172+
plugin.getLocales().getLocale("system_dump_ready").ifPresent(executor::sendMessage);
173+
executor.sendMessage(Component.text(url).clickEvent(ClickEvent.openUrl(url))
174+
.decorate(TextDecoration.UNDERLINED).color(NamedTextColor.GRAY));
175+
});
176+
}
177+
}
178+
152179
private static class MigrateCommand extends ChildCommand implements TabProvider {
153180
private final List<Migrator> migrators = new ArrayList<>();
154181

@@ -236,64 +263,4 @@ public List<String> suggest(@NotNull CommandUser user, @NotNull String[] args) {
236263
}
237264
}
238265

239-
private enum StatusLine {
240-
PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata())
241-
.appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty()
242-
: Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))),
243-
SERVER_VERSION(plugin -> Component.text(plugin.getServerType())),
244-
LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())),
245-
MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())),
246-
JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))),
247-
JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))),
248-
SERVER_NAME(plugin -> Component.text(plugin.getServerName())),
249-
DATABASE_TYPE(plugin -> Component.text(plugin.getSettings().getDatabase().getType().getDisplayName())),
250-
IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())),
251-
USING_REDIS_SENTINEL(plugin -> getBoolean(!plugin.getSettings().getCrossServer().getRedis().getSentinel()
252-
.getMasterName().isBlank())),
253-
USING_REDIS_PASSWORD(plugin -> getBoolean(!plugin.getSettings().getCrossServer().getRedis().getPassword()
254-
.isBlank())),
255-
REDIS_USING_SSL(plugin -> getBoolean(!plugin.getSettings().getCrossServer().getRedis().isUseSsl())),
256-
IS_REDIS_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getCrossServer().getRedis().getHost())),
257-
REGISTERED_CUSTOM_OPERATION_TYPES(plugin -> Component.join(
258-
JoinConfiguration.commas(true),
259-
plugin.getOperationListener().getRegisteredOperationTypes().stream()
260-
.filter(t -> !t.getKey().namespace().equals("cloplib"))
261-
.map(tag -> Component.text(tag.getKey().asString())).toList()
262-
)),
263-
LOADED_HOOKS(plugin -> Component.join(
264-
JoinConfiguration.commas(true),
265-
plugin.getHookManager().getHooks().stream()
266-
.map(hook -> Component.text(hook.getHookInfo().id())).toList()
267-
));
268-
269-
private final Function<HuskTowns, Component> supplier;
270-
271-
StatusLine(@NotNull Function<HuskTowns, Component> supplier) {
272-
this.supplier = supplier;
273-
}
274-
275-
@NotNull
276-
private Component get(@NotNull HuskTowns plugin) {
277-
return Component
278-
.text("•").appendSpace()
279-
.append(Component.text(
280-
WordUtils.capitalizeFully(name().replaceAll("_", " ")),
281-
TextColor.color(0x848484)
282-
))
283-
.append(Component.text(':')).append(Component.space().color(NamedTextColor.WHITE))
284-
.append(supplier.apply(plugin));
285-
}
286-
287-
@NotNull
288-
private static Component getBoolean(boolean value) {
289-
return Component.text(value ? "Yes" : "No", value ? NamedTextColor.GREEN : NamedTextColor.RED);
290-
}
291-
292-
@NotNull
293-
private static Component getLocalhostBoolean(@NotNull String value) {
294-
return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0")
295-
|| value.equals("localhost") || value.equals("::1"));
296-
}
297-
}
298-
299266
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package net.william278.husktowns.util;
2+
3+
import net.william278.cloplib.operation.OperationType;
4+
import net.william278.husktowns.HuskTowns;
5+
import net.william278.husktowns.hook.Hook;
6+
import net.william278.husktowns.hook.PluginHook;
7+
import net.william278.husktowns.user.CommandUser;
8+
import net.william278.husktowns.user.OnlineUser;
9+
import net.william278.toilet.DumpOptions;
10+
import net.william278.toilet.Toilet;
11+
import net.william278.toilet.dump.DumpUser;
12+
import net.william278.toilet.dump.PluginInfo;
13+
import net.william278.toilet.dump.PluginStatus;
14+
import net.william278.toilet.dump.ProjectMeta;
15+
import org.jetbrains.annotations.Blocking;
16+
import org.jetbrains.annotations.NotNull;
17+
18+
import java.awt.*;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
23+
import static net.william278.toilet.DumpOptions.builder;
24+
25+
public interface DumpProvider {
26+
27+
@NotNull String BYTEBIN_URL = "https://bytebin.lucko.me";
28+
@NotNull String VIEWER_URL = "https://william278.net/dump";
29+
30+
@NotNull
31+
Toilet getToilet();
32+
33+
@NotNull
34+
@Blocking
35+
default String createDump(@NotNull CommandUser u) {
36+
return getToilet().dump(getPluginStatus(), u instanceof OnlineUser o
37+
? new DumpUser(o.getName(), o.getUuid()) : null).toString();
38+
}
39+
40+
@NotNull
41+
default DumpOptions getDumpOptions() {
42+
return builder()
43+
.bytebinUrl(BYTEBIN_URL)
44+
.viewerUrl(VIEWER_URL)
45+
.projectMeta(ProjectMeta.builder()
46+
.id("husktowns")
47+
.name("HuskTowns")
48+
.version(getPlugin().getPluginVersion().toString())
49+
.md5("unknown")
50+
.author("William278")
51+
.sourceCode("https://github.com/WiIIiam278/HuskTowns")
52+
.website("https://william278.net/project/husktowns")
53+
.support("https://discord.gg/tVYhJfyDWG")
54+
.build())
55+
.fileInclusionRules(List.of(
56+
DumpOptions.FileInclusionRule.configFile("config.yml", "Config File"),
57+
DumpOptions.FileInclusionRule.configFile("flags.yml", "Flags File"),
58+
DumpOptions.FileInclusionRule.configFile("levels.yml", "Town Levels File"),
59+
DumpOptions.FileInclusionRule.configFile("roles.yml", "Town Roles File"),
60+
DumpOptions.FileInclusionRule.configFile("rules.yml", "Rule Presets File"),
61+
DumpOptions.FileInclusionRule.configFile(getMessagesFile(), "Locales File")
62+
))
63+
.compatibilityRules(List.of(
64+
getCompatibilityWarning("CMI", "CMI may cause compatibility issues with " +
65+
"HuskTowns. If you're using Vault, ensure the CMI-compatible version is in use.")
66+
))
67+
.build();
68+
}
69+
70+
@NotNull
71+
@Blocking
72+
private PluginStatus getPluginStatus() {
73+
return PluginStatus.builder()
74+
.blocks(List.of(getSystemStatus(), getTownStatus(), getHookStatus(), getOperationTypeStatus()))
75+
.build();
76+
}
77+
78+
@NotNull
79+
@Blocking
80+
private PluginStatus.MapStatusBlock getSystemStatus() {
81+
return new PluginStatus.MapStatusBlock(
82+
Map.of(
83+
"Language", StatusLine.LANGUAGE.getValue(getPlugin()),
84+
"Database Type", StatusLine.DATABASE_TYPE.getValue(getPlugin()),
85+
"Database Local", StatusLine.IS_DATABASE_LOCAL.getValue(getPlugin()),
86+
"Cross Server", StatusLine.IS_CROSS_SERVER.getValue(getPlugin()),
87+
"Server Name", StatusLine.SERVER_NAME.getValue(getPlugin()),
88+
"Message Broker", StatusLine.MESSAGE_BROKER_TYPE.getValue(getPlugin()),
89+
"Redis Sentinel", StatusLine.USING_REDIS_SENTINEL.getValue(getPlugin()),
90+
"Redis Password", StatusLine.USING_REDIS_PASSWORD.getValue(getPlugin()),
91+
"Redis SSL", StatusLine.REDIS_USING_SSL.getValue(getPlugin()),
92+
"Redis Local", StatusLine.IS_REDIS_LOCAL.getValue(getPlugin())
93+
),
94+
"Plugin Status", "fa6-solid:wrench"
95+
);
96+
}
97+
98+
@NotNull
99+
@Blocking
100+
private PluginStatus.ChartStatusBlock getTownStatus() {
101+
return new PluginStatus.ChartStatusBlock(
102+
getPlugin().getClaimWorlds().entrySet().stream()
103+
.map((e) -> Map.entry(
104+
new PluginStatus.ChartKey(e.getKey()),
105+
e.getValue().getClaimCount()
106+
))
107+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
108+
PluginStatus.ChartType.PIE, "Town Claims by Worlds", "mdi:square"
109+
);
110+
}
111+
112+
@NotNull
113+
@Blocking
114+
private PluginStatus.ListStatusBlock getHookStatus() {
115+
return new PluginStatus.ListStatusBlock(
116+
getPlugin().getHookManager().getHooks().stream().map(Hook::getHookInfo).map(PluginHook::id).toList(),
117+
"Loaded Hooks", "fa6-solid:plug"
118+
);
119+
}
120+
121+
@NotNull
122+
@Blocking
123+
private PluginStatus.ListStatusBlock getOperationTypeStatus() {
124+
return new PluginStatus.ListStatusBlock(
125+
getPlugin().getOperationListener().getRegisteredOperationTypes().stream()
126+
.map(OperationType::asMinimalString).toList(),
127+
"Operation Types", "ci:flag"
128+
);
129+
}
130+
131+
@NotNull
132+
@SuppressWarnings("SameParameterValue")
133+
private DumpOptions.CompatibilityRule getCompatibilityWarning(@NotNull String plugin, @NotNull String description) {
134+
return DumpOptions.CompatibilityRule.builder()
135+
.labelToApply(new PluginInfo.Label("Warning", "#fcba03", description))
136+
.resourceName(plugin).build();
137+
}
138+
139+
@NotNull
140+
private String getMessagesFile() {
141+
return "messages-%s.yml".formatted(getPlugin().getSettings().getLanguage());
142+
}
143+
144+
private int getColorFor(@NotNull String seed) {
145+
int hash = seed.hashCode();
146+
return new Color((hash >> 16) & 0xFF, (hash >> 8) & 0xFF, hash & 0xFF).getRGB();
147+
}
148+
149+
@NotNull
150+
HuskTowns getPlugin();
151+
152+
}

0 commit comments

Comments
 (0)