add owner notification if advisories are found when they log in
also clean up config files again
This commit is contained in:
5 files changed
+142
-25
No files matched your search
@@ -46,13 +46,11 @@ public final class AdvisorModule extends AbstractModule {
|
||||
private final Map<String, Advisory> advisories = new HashMap<>();
|
||||
private final Set<String> dismissedAdvisories = new HashSet<>();
|
||||
|
||||
private final File advisorDataFile;
|
||||
private final YamlConfiguration advisorData;
|
||||
private final File advisorDataFile = new File(plugin.getDataFolder(), "advisor_data.yml");
|
||||
private YamlConfiguration advisorData;
|
||||
|
||||
public AdvisorModule(JavaPlugin plugin, EnumSet<Capability> capabilities) {
|
||||
super(plugin, capabilities);
|
||||
this.advisorDataFile = new File(plugin.getDataFolder(), "advisor_data.yml");
|
||||
this.advisorData = YamlConfiguration.loadConfiguration(this.advisorDataFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,7 +81,7 @@ public final class AdvisorModule extends AbstractModule {
|
||||
dismissedAdvisories.add(id);
|
||||
|
||||
// convert to list
|
||||
advisorData.set("dismissed", new ArrayList<>(dismissedAdvisories));
|
||||
advisorData.set("dismissed-advisories", new ArrayList<>(dismissedAdvisories));
|
||||
|
||||
// save to file
|
||||
try {
|
||||
@@ -105,7 +103,7 @@ public final class AdvisorModule extends AbstractModule {
|
||||
}
|
||||
|
||||
// load them into memory
|
||||
List<String> dismissed = advisorData.getStringList("dismissed");
|
||||
List<String> dismissed = advisorData.getStringList("dismissed-advisories");
|
||||
dismissedAdvisories.addAll(dismissed);
|
||||
}
|
||||
|
||||
@@ -116,6 +114,14 @@ public final class AdvisorModule extends AbstractModule {
|
||||
if (!advisorDataFile.exists())
|
||||
plugin.saveResource("advisor_data.yml", false);
|
||||
|
||||
// load contents of advisor data
|
||||
this.advisorData = YamlConfiguration.loadConfiguration(this.advisorDataFile);
|
||||
|
||||
boolean showFirstLoginMessage = plugin.getConfig().getBoolean("advisor.show-first-login-message", true);
|
||||
|
||||
if (showFirstLoginMessage)
|
||||
registerBehaviour(new OwnerNotifierBehaviour(behaviourContext, this, advisorData, advisorDataFile));
|
||||
|
||||
registerAdvisories();
|
||||
|
||||
|
||||
@@ -123,11 +129,19 @@ public final class AdvisorModule extends AbstractModule {
|
||||
|
||||
/* ====== Command Definitions ======= */
|
||||
|
||||
private Map<String, Advisory> getAdvisories() {
|
||||
public Map<String, Advisory> getAdvisories() {
|
||||
return advisories;
|
||||
}
|
||||
|
||||
private Set<String> getDismissedAdvisories() {
|
||||
public boolean isDismissed(String id) {
|
||||
return dismissedAdvisories.contains(id);
|
||||
}
|
||||
|
||||
public boolean isResolved(String id) {
|
||||
return advisories.get(id).isApplied();
|
||||
}
|
||||
|
||||
public Set<String> getDismissedAdvisories() {
|
||||
return dismissedAdvisories;
|
||||
}
|
||||
|
||||
@@ -205,7 +219,7 @@ public final class AdvisorModule extends AbstractModule {
|
||||
Audience audience = ctx.getSource().getSender();
|
||||
|
||||
audience.sendMessage(
|
||||
text("━".repeat(advisory.getName().length()), NamedTextColor.DARK_GRAY)
|
||||
text("━━━━━━━━━━━━━━━━━━━━━━", NamedTextColor.DARK_GRAY)
|
||||
);
|
||||
audience.sendMessage(
|
||||
text(advisory.getName(), NamedTextColor.GOLD)
|
||||
@@ -240,6 +254,9 @@ public final class AdvisorModule extends AbstractModule {
|
||||
);
|
||||
}
|
||||
if (!actions.equals(Component.empty())) audience.sendMessage(actions);
|
||||
audience.sendMessage(
|
||||
text("━━━━━━━━━━━━━━━━━━━━━━", NamedTextColor.DARK_GRAY)
|
||||
);
|
||||
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
@@ -251,7 +268,10 @@ public final class AdvisorModule extends AbstractModule {
|
||||
Component list = text("The following advisories are available:", NamedTextColor.GOLD)
|
||||
.appendNewline();
|
||||
|
||||
for (Map.Entry<String, Advisory> entry : getAdvisories().entrySet()) {
|
||||
List<Map.Entry<String, Advisory>> entries = new ArrayList<>(getAdvisories().entrySet());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
Map.Entry<String, Advisory> entry = entries.get(i);
|
||||
|
||||
String id = entry.getKey();
|
||||
Advisory advisory = entry.getValue();
|
||||
|
||||
@@ -268,12 +288,11 @@ public final class AdvisorModule extends AbstractModule {
|
||||
text("• " + advisory.getName(), colour)
|
||||
.hoverEvent(HoverEvent.showText(advisory.getDescription()))
|
||||
.clickEvent(ClickEvent.runCommand("/szlaban %s info ".formatted(name) + id))
|
||||
).append(Component.newline());
|
||||
);
|
||||
// don't end with a blank line
|
||||
if (i < entries.size() - 1) list = list.appendNewline();
|
||||
}
|
||||
|
||||
// remove last newline
|
||||
list = list.children().removeLast();
|
||||
|
||||
ctx.getSource().getSender().sendMessage(list);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Piotr Weychan
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*
|
||||
* Additional terms under GPL-3.0 section 7 apply – see LICENCE.ADDITIONAL_TERMS.
|
||||
*/
|
||||
|
||||
package dev.piotr_weychan.szlaban.advisor;
|
||||
|
||||
import dev.piotr_weychan.szlaban.behaviour.BehaviourContext;
|
||||
import dev.piotr_weychan.szlaban.behaviour.ListenerBehaviour;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
final class OwnerNotifierBehaviour extends ListenerBehaviour {
|
||||
private final YamlConfiguration data;
|
||||
private final File dataFile;
|
||||
private final AdvisorModule advisorModule;
|
||||
|
||||
public OwnerNotifierBehaviour(BehaviourContext ctx, AdvisorModule mod, YamlConfiguration data, File dataFile) {
|
||||
super(ctx);
|
||||
this.data = data;
|
||||
this.dataFile = dataFile;
|
||||
this.advisorModule = mod;
|
||||
}
|
||||
|
||||
private void showWelcomeMessage(Audience audience) {
|
||||
long unresolved = advisorModule.getAdvisories().entrySet().stream()
|
||||
.filter(e -> !advisorModule.isResolved(e.getKey()) && !advisorModule.isDismissed(e.getKey()))
|
||||
.count();
|
||||
|
||||
if (unresolved == 0) return;
|
||||
|
||||
audience.sendMessage(
|
||||
text("━━━━━━━━━━━━━━━━━━━━━━", NamedTextColor.DARK_GRAY)
|
||||
);
|
||||
audience.sendMessage(
|
||||
text("⚠ ", NamedTextColor.GOLD)
|
||||
.append(text("Szlaban", NamedTextColor.YELLOW).decorate(TextDecoration.BOLD))
|
||||
.append(text(" has found ", NamedTextColor.GRAY))
|
||||
.append(text(unresolved + " unresolved advisor" + (unresolved == 1 ? "y" : "ies"), NamedTextColor.RED).decorate(TextDecoration.BOLD))
|
||||
.append(text(".", NamedTextColor.GRAY))
|
||||
);
|
||||
audience.sendMessage(
|
||||
text("Run ", NamedTextColor.GRAY)
|
||||
.append(text("/szlaban advisor list", NamedTextColor.YELLOW)
|
||||
.clickEvent(ClickEvent.runCommand("/szlaban advisor list"))
|
||||
.hoverEvent(HoverEvent.showText(text("Click to view advisories"))))
|
||||
.append(text(" to view and resolve them.", NamedTextColor.GRAY))
|
||||
);
|
||||
audience.sendMessage(
|
||||
text("━━━━━━━━━━━━━━━━━━━━━━", NamedTextColor.DARK_GRAY)
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent evt) {
|
||||
Player player = evt.getPlayer();
|
||||
// permission check
|
||||
if (!player.hasPermission("szlaban.advisor")) return;
|
||||
|
||||
// check if player has seen this notice
|
||||
Set<UUID> uuidStrings = data.getStringList("seen-players").stream()
|
||||
.map(UUID::fromString)
|
||||
.collect(Collectors.toSet());
|
||||
if (uuidStrings.contains(player.getUniqueId())) return;
|
||||
|
||||
// no? show them the interactive notice then!
|
||||
ctx.plugin().getServer().getScheduler().scheduleSyncDelayedTask(ctx.plugin(),
|
||||
// show welcome message after 5 seconds (5*20 ticks)
|
||||
() -> showWelcomeMessage(player), 5 * 20L
|
||||
);
|
||||
|
||||
// insert the player's uuid into the set
|
||||
uuidStrings.add(player.getUniqueId());
|
||||
data.set("seen-players", uuidStrings.stream().map(UUID::toString).toList());
|
||||
|
||||
try {
|
||||
data.save(dataFile);
|
||||
} catch (IOException e) {
|
||||
ctx.plugin().getSLF4JLogger().error("Failed to update advisor data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -90,8 +90,10 @@ public final class Advisories {
|
||||
"Disable RCON",
|
||||
text("Disables remote console access to the server over the network."),
|
||||
Component.empty()
|
||||
.append(text("RCON allows full console access over the network. If left enabled, even with a "))
|
||||
.append(text("password, it presents an unnecessary attack surface and can lead to "))
|
||||
.append(text("RCON allows full console access over the network. Even with a password, credentials "))
|
||||
.append(text("can be leaked or brute-forced, granting an attacker "))
|
||||
.append(text("complete control").decorate(TextDecoration.BOLD))
|
||||
.append(text(" over your server. Disable it unless you have a specific need for it."))
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
dismissed: []
|
||||
dismissed-advisories: []
|
||||
seen-players: []
|
||||
@@ -21,14 +21,6 @@ advisor:
|
||||
# Whether to show owners the first-time login message
|
||||
show-first-login-message: true
|
||||
|
||||
# List of advisory IDs that have been ignored
|
||||
ignored-advisories:
|
||||
# - online_mode
|
||||
# - whitelist
|
||||
# - rcon
|
||||
# - chat_signing
|
||||
|
||||
|
||||
# Settings for the firewall module
|
||||
firewall:
|
||||
# What engine to use for firewall management
|
||||
|
||||
Reference in new issue
Block a user