diff --git a/consent-plugin/pom.xml b/consent-plugin/pom.xml index 341b4d7..038ff0d 100644 --- a/consent-plugin/pom.xml +++ b/consent-plugin/pom.xml @@ -27,11 +27,11 @@ - + io.papermc.paper paper-api - 1.21-R0.1-SNAPSHOT + 1.17.1-R0.1-SNAPSHOT provided diff --git a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentConfig.java b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentConfig.java index a521da1..c23dc11 100644 --- a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentConfig.java +++ b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentConfig.java @@ -164,4 +164,13 @@ public class ConsentConfig { "Policy: {policy_url}" ); } + + /** + * When {@code true}, kick messages are forwarded to the Velocity / BungeeCord + * proxy via the {@code BungeeCord} plugin-messaging channel so the player is + * removed from the entire network rather than just the current server. + */ + public boolean isVelocityKick() { + return cfg.getBoolean("consent.velocity-kick", false); + } } diff --git a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentPlugin.java b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentPlugin.java index 1873e37..25734a2 100644 --- a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentPlugin.java +++ b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentPlugin.java @@ -4,7 +4,12 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import de.simolzimol.mclogger.consent.commands.ConsentCommand; @@ -59,6 +64,9 @@ public class ConsentPlugin extends JavaPlugin { return; } + // Register plugin-messaging channel for Velocity / BungeeCord network kick + getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + // Register event listeners ConsentListener listener = new ConsentListener(this); getServer().getPluginManager().registerEvents(listener, this); @@ -107,4 +115,31 @@ public class ConsentPlugin extends JavaPlugin { public Set getPendingConsent() { return pendingConsent; } + + /** + * Kicks a player from the current server, or from the entire + * Velocity / BungeeCord network when {@code velocity-kick: true} is set + * in the config. Falls back to a normal kick if the proxy message fails. + */ + public void networkKick(Player player, Component reason) { + if (consentConfig.isVelocityKick()) { + try { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("KickPlayer"); + out.writeUTF(player.getName()); + out.writeUTF(LegacyComponentSerializer.legacySection().serialize(reason)); + player.sendPluginMessage(this, "BungeeCord", out.toByteArray()); + } catch (Exception e) { + getLogger().warning("[MCConsent] Velocity/BungeeCord network kick failed: " + e.getMessage()); + player.kick(reason); + return; + } + // Fallback: if the proxy never kicks, remove the player after 1 s + getServer().getScheduler().runTaskLater(this, () -> { + if (player.isOnline()) player.kick(reason); + }, 20L); + } else { + player.kick(reason); + } + } } diff --git a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/commands/ConsentCommand.java b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/commands/ConsentCommand.java index 3ebcc81..a903b62 100644 --- a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/commands/ConsentCommand.java +++ b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/commands/ConsentCommand.java @@ -113,7 +113,7 @@ public class ConsentCommand implements CommandExecutor, TabCompleter { plugin.getPendingConsent().remove(player.getUniqueId()); String msg = MessageUtil.replace(cfg.getMsgDeclined(), player, cfg); - player.kick(MM.deserialize(msg)); + plugin.networkKick(player, MM.deserialize(msg)); } private void handleStatus(CommandSender sender, ConsentConfig cfg) { diff --git a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/listeners/ConsentListener.java b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/listeners/ConsentListener.java index e517eba..7188b55 100644 --- a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/listeners/ConsentListener.java +++ b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/listeners/ConsentListener.java @@ -1,20 +1,29 @@ package de.simolzimol.mclogger.consent.listeners; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; import de.simolzimol.mclogger.consent.ConsentConfig; import de.simolzimol.mclogger.consent.ConsentConfig.EnforcementMode; @@ -93,9 +102,12 @@ public class ConsentListener implements Listener { EnforcementMode mode = cfg.getEnforcementMode(); - // Send the join prompt - String prompt = MessageUtil.replace(cfg.getMsgJoinPrompt(), player, cfg); - player.sendMessage(MiniMessage.miniMessage().deserialize(prompt)); + // Open the policy book 1 tick after join (openBook can't be called during join) + String promptMini = MessageUtil.replace(cfg.getMsgJoinPrompt(), player, cfg); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + if (!player.isOnline()) return; + openPolicyBook(player, promptMini); + }, 1L); if (mode == EnforcementMode.HOLD) { plugin.getPendingConsent().add(player.getUniqueId()); @@ -177,6 +189,28 @@ public class ConsentListener implements Listener { event.getPlayer().sendMessage(MiniMessage.miniMessage().deserialize(msg)); } + // ───────────────────────────────────────────────────────── + // HOLD-mode: block block-break / place / interact + // ───────────────────────────────────────────────────────── + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + if (!isHoldPending(event.getPlayer())) return; + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + if (!isHoldPending(event.getPlayer())) return; + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInteract(PlayerInteractEvent event) { + if (!isHoldPending(event.getPlayer())) return; + event.setCancelled(true); + } + // ───────────────────────────────────────────────────────── // Internal helpers // ───────────────────────────────────────────────────────── @@ -193,6 +227,40 @@ public class ConsentListener implements Listener { return cfg.getExemptWorlds().contains(worldName); } + /** + * Opens a written-book GUI with the policy prompt so the player can read + * the full text including a clickable URL, without cluttering chat. + * A short plain-chat hint is also sent so players know why the book opened. + */ + private void openPolicyBook(Player player, String promptMini) { + MiniMessage mm = MiniMessage.miniMessage(); + + ItemStack book = new ItemStack(Material.WRITTEN_BOOK); + BookMeta meta = (BookMeta) Objects.requireNonNull(book.getItemMeta()); + meta.title(mm.deserialize("Privacy Policy")); + meta.author(Component.text("MCLogger")); + + // First page: the configured join-prompt text (supports MiniMessage incl. click URLs) + Component page1 = mm.deserialize(promptMini); + meta.pages(List.of(page1)); + + book.setItemMeta(meta); + player.openBook(book); + + // Also send a brief chat message so the book title isn't the only indicator + String hint = MessageUtil.replace( + plugin.getConsentConfig().getMsgJoinPrompt(), player, plugin.getConsentConfig()); + player.sendMessage(mm.deserialize(hint)); + } + + /** + * Kicks a player from the server or – if velocity-kick is enabled – from + * the entire network. Delegates to {@link ConsentPlugin#networkKick}. + */ + void doKick(Player player, Component reason) { + plugin.networkKick(player, reason); + } + /** * Sends a MiniMessage-formatted message to the player with a cooldown to * avoid flooding the chat (e.g. when the player spams movement). @@ -241,7 +309,7 @@ public class ConsentListener implements Listener { plugin.getPendingConsent().remove(uuid); String kickMsg = MessageUtil.replace( plugin.getConsentConfig().getMsgGraceKick(), p, plugin.getConsentConfig()); - p.kick(MiniMessage.miniMessage().deserialize(kickMsg)); + doKick(p, MiniMessage.miniMessage().deserialize(kickMsg)); }, (long) graceSec * 20); } diff --git a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/util/MessageUtil.java b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/util/MessageUtil.java index 3cf7a2a..de66807 100644 --- a/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/util/MessageUtil.java +++ b/consent-plugin/src/main/java/de/simolzimol/mclogger/consent/util/MessageUtil.java @@ -16,7 +16,9 @@ public final class MessageUtil { *

Supported placeholders: *

    *
  • {@code {player}} – player's display name
  • - *
  • {@code {policy_url}} – configured policy URL
  • + *
  • {@code {url}} – configured policy URL
  • + *
  • {@code {policy_url}} – configured policy URL (alias for {url})
  • + *
  • {@code {command}} – consent command name (e.g. "consent")
  • *
  • {@code {version}} – policy version string
  • *
*/ @@ -24,7 +26,9 @@ public final class MessageUtil { if (template == null) return ""; return template .replace("{player}", player.getName()) + .replace("{url}", cfg.getPolicyUrl()) .replace("{policy_url}", cfg.getPolicyUrl()) + .replace("{command}", cfg.getCommand()) .replace("{version}", cfg.getPolicyVersion()); } } diff --git a/consent-plugin/src/main/resources/config.yml b/consent-plugin/src/main/resources/config.yml index d029191..0b965c8 100644 --- a/consent-plugin/src/main/resources/config.yml +++ b/consent-plugin/src/main/resources/config.yml @@ -1,7 +1,7 @@ # ============================================================ # MCConsent – Paper Plugin Configuration # Author: SimolZimol -# Docs: https://github.com/SimolZimol/MCLogger +# Website: https://log.devanturas.net # ============================================================ # ── Global switch ──────────────────────────────────────────── @@ -55,6 +55,14 @@ consent: # in plugin.yml with aliases /privacy and /pp). command: "consent" + # ── Velocity / BungeeCord network kick ────────────────────── + # Set to true if this server runs behind a Velocity or BungeeCord + # proxy. When enabled, kick messages are forwarded via the + # BungeeCord plugin-messaging channel so the player is removed + # from the entire network instead of just this server. + # Requires the proxy to have the plugin-messaging channel enabled. + velocity-kick: false + # ── Grace period (HOLD mode only) ─────────────────────────── # How many seconds a player has to accept before being kicked. # Set to 0 to wait forever (player can take as long as they like). diff --git a/consent-plugin/src/main/resources/plugin.yml b/consent-plugin/src/main/resources/plugin.yml index f93116d..0f52ce3 100644 --- a/consent-plugin/src/main/resources/plugin.yml +++ b/consent-plugin/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: MCConsent version: '1.0.0' main: de.simolzimol.mclogger.consent.ConsentPlugin -api-version: '1.20' +api-version: '1.17' description: Privacy Policy consent enforcement for Minecraft servers author: SimolZimol