modified: consent-plugin/pom.xml
modified: consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentConfig.java modified: consent-plugin/src/main/java/de/simolzimol/mclogger/consent/ConsentPlugin.java modified: consent-plugin/src/main/java/de/simolzimol/mclogger/consent/commands/ConsentCommand.java modified: consent-plugin/src/main/java/de/simolzimol/mclogger/consent/listeners/ConsentListener.java modified: consent-plugin/src/main/java/de/simolzimol/mclogger/consent/util/MessageUtil.java modified: consent-plugin/src/main/resources/config.yml modified: consent-plugin/src/main/resources/plugin.yml
This commit is contained in:
@@ -27,11 +27,11 @@
|
|||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Paper API 1.21 -->
|
<!-- Paper API 1.17+ (lowest supported; ensures no 1.18+-only APIs are used) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.papermc.paper</groupId>
|
<groupId>io.papermc.paper</groupId>
|
||||||
<artifactId>paper-api</artifactId>
|
<artifactId>paper-api</artifactId>
|
||||||
<version>1.21-R0.1-SNAPSHOT</version>
|
<version>1.17.1-R0.1-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -164,4 +164,13 @@ public class ConsentConfig {
|
|||||||
"<gray>Policy: <aqua>{policy_url}</aqua>"
|
"<gray>Policy: <aqua>{policy_url}</aqua>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import java.util.Set;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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.command.PluginCommand;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import de.simolzimol.mclogger.consent.commands.ConsentCommand;
|
import de.simolzimol.mclogger.consent.commands.ConsentCommand;
|
||||||
@@ -59,6 +64,9 @@ public class ConsentPlugin extends JavaPlugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register plugin-messaging channel for Velocity / BungeeCord network kick
|
||||||
|
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
|
||||||
|
|
||||||
// Register event listeners
|
// Register event listeners
|
||||||
ConsentListener listener = new ConsentListener(this);
|
ConsentListener listener = new ConsentListener(this);
|
||||||
getServer().getPluginManager().registerEvents(listener, this);
|
getServer().getPluginManager().registerEvents(listener, this);
|
||||||
@@ -107,4 +115,31 @@ public class ConsentPlugin extends JavaPlugin {
|
|||||||
public Set<UUID> getPendingConsent() {
|
public Set<UUID> getPendingConsent() {
|
||||||
return pendingConsent;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class ConsentCommand implements CommandExecutor, TabCompleter {
|
|||||||
plugin.getPendingConsent().remove(player.getUniqueId());
|
plugin.getPendingConsent().remove(player.getUniqueId());
|
||||||
|
|
||||||
String msg = MessageUtil.replace(cfg.getMsgDeclined(), player, cfg);
|
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) {
|
private void handleStatus(CommandSender sender, ConsentConfig cfg) {
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
package de.simolzimol.mclogger.consent.listeners;
|
package de.simolzimol.mclogger.consent.listeners;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
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.AsyncPlayerChatEvent;
|
||||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.bukkit.event.player.PlayerLoginEvent;
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
import org.bukkit.event.player.PlayerMoveEvent;
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
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;
|
||||||
import de.simolzimol.mclogger.consent.ConsentConfig.EnforcementMode;
|
import de.simolzimol.mclogger.consent.ConsentConfig.EnforcementMode;
|
||||||
@@ -93,9 +102,12 @@ public class ConsentListener implements Listener {
|
|||||||
|
|
||||||
EnforcementMode mode = cfg.getEnforcementMode();
|
EnforcementMode mode = cfg.getEnforcementMode();
|
||||||
|
|
||||||
// Send the join prompt
|
// Open the policy book 1 tick after join (openBook can't be called during join)
|
||||||
String prompt = MessageUtil.replace(cfg.getMsgJoinPrompt(), player, cfg);
|
String promptMini = MessageUtil.replace(cfg.getMsgJoinPrompt(), player, cfg);
|
||||||
player.sendMessage(MiniMessage.miniMessage().deserialize(prompt));
|
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
openPolicyBook(player, promptMini);
|
||||||
|
}, 1L);
|
||||||
|
|
||||||
if (mode == EnforcementMode.HOLD) {
|
if (mode == EnforcementMode.HOLD) {
|
||||||
plugin.getPendingConsent().add(player.getUniqueId());
|
plugin.getPendingConsent().add(player.getUniqueId());
|
||||||
@@ -177,6 +189,28 @@ public class ConsentListener implements Listener {
|
|||||||
event.getPlayer().sendMessage(MiniMessage.miniMessage().deserialize(msg));
|
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
|
// Internal helpers
|
||||||
// ─────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────
|
||||||
@@ -193,6 +227,40 @@ public class ConsentListener implements Listener {
|
|||||||
return cfg.getExemptWorlds().contains(worldName);
|
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("<bold><blue>Privacy Policy</blue></bold>"));
|
||||||
|
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
|
* Sends a MiniMessage-formatted message to the player with a cooldown to
|
||||||
* avoid flooding the chat (e.g. when the player spams movement).
|
* avoid flooding the chat (e.g. when the player spams movement).
|
||||||
@@ -241,7 +309,7 @@ public class ConsentListener implements Listener {
|
|||||||
plugin.getPendingConsent().remove(uuid);
|
plugin.getPendingConsent().remove(uuid);
|
||||||
String kickMsg = MessageUtil.replace(
|
String kickMsg = MessageUtil.replace(
|
||||||
plugin.getConsentConfig().getMsgGraceKick(), p, plugin.getConsentConfig());
|
plugin.getConsentConfig().getMsgGraceKick(), p, plugin.getConsentConfig());
|
||||||
p.kick(MiniMessage.miniMessage().deserialize(kickMsg));
|
doKick(p, MiniMessage.miniMessage().deserialize(kickMsg));
|
||||||
}, (long) graceSec * 20);
|
}, (long) graceSec * 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ public final class MessageUtil {
|
|||||||
* <p>Supported placeholders:
|
* <p>Supported placeholders:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code {player}} – player's display name</li>
|
* <li>{@code {player}} – player's display name</li>
|
||||||
* <li>{@code {policy_url}} – configured policy URL</li>
|
* <li>{@code {url}} – configured policy URL</li>
|
||||||
|
* <li>{@code {policy_url}} – configured policy URL (alias for {url})</li>
|
||||||
|
* <li>{@code {command}} – consent command name (e.g. "consent")</li>
|
||||||
* <li>{@code {version}} – policy version string</li>
|
* <li>{@code {version}} – policy version string</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@@ -24,7 +26,9 @@ public final class MessageUtil {
|
|||||||
if (template == null) return "";
|
if (template == null) return "";
|
||||||
return template
|
return template
|
||||||
.replace("{player}", player.getName())
|
.replace("{player}", player.getName())
|
||||||
|
.replace("{url}", cfg.getPolicyUrl())
|
||||||
.replace("{policy_url}", cfg.getPolicyUrl())
|
.replace("{policy_url}", cfg.getPolicyUrl())
|
||||||
|
.replace("{command}", cfg.getCommand())
|
||||||
.replace("{version}", cfg.getPolicyVersion());
|
.replace("{version}", cfg.getPolicyVersion());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# MCConsent – Paper Plugin Configuration
|
# MCConsent – Paper Plugin Configuration
|
||||||
# Author: SimolZimol
|
# Author: SimolZimol
|
||||||
# Docs: https://github.com/SimolZimol/MCLogger
|
# Website: https://log.devanturas.net
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
# ── Global switch ────────────────────────────────────────────
|
# ── Global switch ────────────────────────────────────────────
|
||||||
@@ -55,6 +55,14 @@ consent:
|
|||||||
# in plugin.yml with aliases /privacy and /pp).
|
# in plugin.yml with aliases /privacy and /pp).
|
||||||
command: "consent"
|
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) ───────────────────────────
|
# ── Grace period (HOLD mode only) ───────────────────────────
|
||||||
# How many seconds a player has to accept before being kicked.
|
# 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).
|
# Set to 0 to wait forever (player can take as long as they like).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: MCConsent
|
name: MCConsent
|
||||||
version: '1.0.0'
|
version: '1.0.0'
|
||||||
main: de.simolzimol.mclogger.consent.ConsentPlugin
|
main: de.simolzimol.mclogger.consent.ConsentPlugin
|
||||||
api-version: '1.20'
|
api-version: '1.17'
|
||||||
description: Privacy Policy consent enforcement for Minecraft servers
|
description: Privacy Policy consent enforcement for Minecraft servers
|
||||||
author: SimolZimol
|
author: SimolZimol
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user