add owner notification if advisories are found when they log in

also clean up config files again
This commit is contained in:
Piotr W. committed 2026-04-25 01:54:42 +01:00
1 parent c4baafc1aa
commit b7b0e0614a
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."))
)
);
+2 -1
View File
@@ -1 +1,2 @@
dismissed: []
dismissed-advisories: []
seen-players: []
-8
View File
@@ -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