From f2856699904cbb1a136840f3e59133070252e301 Mon Sep 17 00:00:00 2001 From: Vankka Date: Sat, 29 Jun 2024 15:09:34 +0300 Subject: [PATCH] Kick/(un)freeze player when they lose/gain additional requirements --- .../api/module/type/PunishmentModule.java | 2 - .../component/PaperComponentHandle.java | 2 +- .../discordsrv/bukkit/player/PaperPlayer.java | 41 ++- .../discordsrv/bukkit/BukkitDiscordSRV.java | 4 - .../bukkit/ban/BukkitBanModule.java | 14 - .../BukkitRequiredLinkingListener.java | 306 ------------------ .../bukkit/player/BukkitPlayer.java | 13 + .../BukkitRequiredLinkingModule.java | 267 ++++++++++++++- .../common/bansync/BanSyncModule.java | 2 +- .../common/component/util/ComponentUtil.java | 10 +- .../requirelinking/RequiredLinkingModule.java | 6 +- .../type/DiscordRoleRequirementType.java | 2 +- .../com/discordsrv/common/player/IPlayer.java | 3 + .../common/storage/impl/sql/SQLStorage.java | 5 +- 14 files changed, 333 insertions(+), 344 deletions(-) delete mode 100644 bukkit/src/main/java/com/discordsrv/bukkit/listener/BukkitRequiredLinkingListener.java diff --git a/api/src/main/java/com/discordsrv/api/module/type/PunishmentModule.java b/api/src/main/java/com/discordsrv/api/module/type/PunishmentModule.java index 86d64499..77b524fc 100644 --- a/api/src/main/java/com/discordsrv/api/module/type/PunishmentModule.java +++ b/api/src/main/java/com/discordsrv/api/module/type/PunishmentModule.java @@ -25,7 +25,6 @@ package com.discordsrv.api.module.type; import com.discordsrv.api.component.MinecraftComponent; import com.discordsrv.api.module.Module; -import com.discordsrv.api.player.DiscordSRVPlayer; import com.discordsrv.api.punishment.Punishment; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,7 +39,6 @@ public interface PunishmentModule extends Module { CompletableFuture<@Nullable Punishment> getBan(@NotNull UUID playerUUID); CompletableFuture addBan(@NotNull UUID playerUUID, @Nullable Instant until, @Nullable MinecraftComponent reason, @NotNull MinecraftComponent punisher); CompletableFuture removeBan(@NotNull UUID playerUUID); - CompletableFuture kickPlayer(@NotNull DiscordSRVPlayer player, @NotNull MinecraftComponent message); } interface Mutes extends PunishmentModule { diff --git a/bukkit/paper/src/main/java/com/discordsrv/bukkit/component/PaperComponentHandle.java b/bukkit/paper/src/main/java/com/discordsrv/bukkit/component/PaperComponentHandle.java index ee3c9bc5..1910521f 100644 --- a/bukkit/paper/src/main/java/com/discordsrv/bukkit/component/PaperComponentHandle.java +++ b/bukkit/paper/src/main/java/com/discordsrv/bukkit/component/PaperComponentHandle.java @@ -66,7 +66,7 @@ public class PaperComponentHandle { unrelocated = handle.invoke(target); } catch (Throwable ignored) {} if (unrelocated != null) { - return ComponentUtil.fromUnrelocated(unrelocated); + return ComponentUtil.fromUnrelocated((com.discordsrv.unrelocate.net.kyori.adventure.text.Component) unrelocated); } } diff --git a/bukkit/paper/src/main/java/com/discordsrv/bukkit/player/PaperPlayer.java b/bukkit/paper/src/main/java/com/discordsrv/bukkit/player/PaperPlayer.java index 0e748d54..0b83dae7 100644 --- a/bukkit/paper/src/main/java/com/discordsrv/bukkit/player/PaperPlayer.java +++ b/bukkit/paper/src/main/java/com/discordsrv/bukkit/player/PaperPlayer.java @@ -18,16 +18,23 @@ package com.discordsrv.bukkit.player; +import com.discordsrv.common.component.util.ComponentUtil; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.Locale; +@SuppressWarnings("JavaLangInvokeHandleSignature") // Unrelocate public final class PaperPlayer { private PaperPlayer() {} - private static final boolean localeMethodExists; - private static final boolean getLocaleMethodExists; + private static final boolean LOCALE_METHOD_EXISTS; + private static final boolean GETLOCALE_METHOD_EXISTS; + private static final MethodHandle KICK_COMPONENT_HANDLE; static { Class playerClass = Player.class; @@ -41,18 +48,40 @@ public final class PaperPlayer { playerClass.getMethod("getLocale"); getLocale = true; } catch (ReflectiveOperationException ignored) {} - localeMethodExists = locale; - getLocaleMethodExists = getLocale; + LOCALE_METHOD_EXISTS = locale; + GETLOCALE_METHOD_EXISTS = getLocale; + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle handle = null; + try { + handle = lookup.findVirtual(Player.class, "kick", MethodType.methodType( + void.class, + com.discordsrv.unrelocate.net.kyori.adventure.text.Component.class + )); + } catch (ReflectiveOperationException ignored) {} + KICK_COMPONENT_HANDLE = handle; } @SuppressWarnings("deprecation") public static Locale getLocale(Player player) { - if (localeMethodExists) { + if (LOCALE_METHOD_EXISTS) { return player.locale(); - } else if (getLocaleMethodExists) { + } else if (GETLOCALE_METHOD_EXISTS) { return Locale.forLanguageTag(player.getLocale()); } else { return null; } } + + public static boolean isKickAvailable() { + return KICK_COMPONENT_HANDLE != null; + } + + public static void kick(Player player, Component reason) { + try { + KICK_COMPONENT_HANDLE.invokeExact(player, ComponentUtil.toUnrelocated(ComponentUtil.toAPI(reason))); + } catch (Throwable e) { + throw new RuntimeException("Failed to kick player", e); + } + } } diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java index b242509a..72a473fb 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java @@ -31,7 +31,6 @@ import com.discordsrv.bukkit.config.manager.BukkitMessagesConfigManager; import com.discordsrv.bukkit.console.BukkitConsole; import com.discordsrv.bukkit.listener.BukkitConnectionListener; import com.discordsrv.bukkit.listener.BukkitDeathListener; -import com.discordsrv.bukkit.listener.BukkitRequiredLinkingListener; import com.discordsrv.bukkit.listener.BukkitStatusMessageListener; import com.discordsrv.bukkit.listener.award.BukkitAwardForwarder; import com.discordsrv.bukkit.listener.chat.BukkitChatForwarder; @@ -70,7 +69,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV implements return null; }); } - - @Override - public CompletableFuture kickPlayer(@NotNull DiscordSRVPlayer srvPlayer, @NotNull MinecraftComponent message) { - Player player = discordSRV.server().getPlayer(srvPlayer.uniqueId()); - if (player == null) { - return CompletableFuture.completedFuture(null); - } - - return discordSRV.scheduler().executeOnMainThread( - player, - () -> player.kickPlayer(BukkitComponentSerializer.legacy().serialize(ComponentUtil.fromAPI(message))) - ); - } } diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/listener/BukkitRequiredLinkingListener.java b/bukkit/src/main/java/com/discordsrv/bukkit/listener/BukkitRequiredLinkingListener.java deleted file mode 100644 index 16c0d336..00000000 --- a/bukkit/src/main/java/com/discordsrv/bukkit/listener/BukkitRequiredLinkingListener.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * This file is part of DiscordSRV, licensed under the GPLv3 License - * Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.discordsrv.bukkit.listener; - -import com.discordsrv.bukkit.BukkitDiscordSRV; -import com.discordsrv.bukkit.config.main.BukkitRequiredLinkingConfig; -import com.discordsrv.bukkit.requiredlinking.BukkitRequiredLinkingModule; -import com.discordsrv.common.DiscordSRV; -import com.discordsrv.common.component.util.ComponentUtil; -import com.discordsrv.common.config.main.linking.ServerRequiredLinkingConfig; -import com.discordsrv.common.linking.LinkStore; -import com.discordsrv.common.player.IPlayer; -import com.github.benmanes.caffeine.cache.Cache; -import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer; -import net.kyori.adventure.text.Component; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.*; -import org.bukkit.event.player.*; - -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class BukkitRequiredLinkingListener implements Listener { - - private final BukkitDiscordSRV discordSRV; - private final Cache linkCheckRateLimit; - - public BukkitRequiredLinkingListener(BukkitDiscordSRV discordSRV) { - this.discordSRV = discordSRV; - this.linkCheckRateLimit = discordSRV.caffeineBuilder() - .expireAfterWrite(LinkStore.LINKING_CODE_RATE_LIMIT) - .build(); - - register(PlayerLoginEvent.class, this::handle); - register(AsyncPlayerPreLoginEvent.class, this::handle); - discordSRV.server().getPluginManager().registerEvents(this, discordSRV.plugin()); - } - - @SuppressWarnings("unchecked") - private void register(Class eventType, BiConsumer eventConsumer) { - for (EventPriority priority : EventPriority.values()) { - if (priority == EventPriority.MONITOR) { - continue; - } - - discordSRV.server().getPluginManager().registerEvent( - eventType, - this, - priority, - (listener, event) -> eventConsumer.accept((T) event, priority), - discordSRV.plugin(), - true - ); - } - } - - public void disable() { - HandlerList.unregisterAll(this); - } - - private BukkitRequiredLinkingModule getModule() { - int tries = 0; - - BukkitRequiredLinkingModule module; - do { - module = discordSRV.getModule(BukkitRequiredLinkingModule.class); - - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - tries++; - } while (module == null || tries >= 50); - - return module; - } - - private CompletableFuture getBlockReason(UUID playerUUID, String playerName, boolean join) { - BukkitRequiredLinkingModule module = getModule(); - if (module == null) { - Component message = ComponentUtil.fromAPI( - discordSRV.messagesConfig().minecraft.unableToLinkAtThisTime.textBuilder().build() - ); - return CompletableFuture.completedFuture(message); - } - - return module.getBlockReason(playerUUID, playerName, join); - } - - // - // Kick - // - - private void handle(AsyncPlayerPreLoginEvent event, EventPriority priority) { - handle( - "AsyncPlayerPreLoginEvent", - priority, - event.getUniqueId(), - event.getName(), - () -> event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED ? event.getLoginResult().name() : null, - text -> event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, text) - ); - } - - private void handle(PlayerLoginEvent event, EventPriority priority) { - Player player = event.getPlayer(); - handle( - "PlayerLoginEvent", - priority, - player.getUniqueId(), - player.getName(), - () -> event.getResult() != PlayerLoginEvent.Result.ALLOWED ? event.getResult().name() : null, - text -> event.disallow(PlayerLoginEvent.Result.KICK_OTHER, text) - ); - } - - private void handle( - String eventType, - EventPriority priority, - UUID playerUUID, - String playerName, - Supplier alreadyBlocked, - Consumer disallow - ) { - BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking; - if (!config.enabled || config.action != ServerRequiredLinkingConfig.Action.KICK - || !eventType.equals(config.kick.event) || !priority.name().equals(config.kick.priority)) { - return; - } - - String blockType = alreadyBlocked.get(); - if (blockType != null) { - discordSRV.logger().debug(playerName + " is already blocked for " + eventType + "/" + priority + " (" + blockType + ")"); - return; - } - - Component kickReason = getBlockReason(playerUUID, playerName, true).join(); - if (kickReason != null) { - disallow.accept(BukkitComponentSerializer.legacy().serialize(kickReason)); - } - } - - // - // Freeze - // - - private final Map frozen = new ConcurrentHashMap<>(); - - private boolean isFrozen(Player player) { - return frozen.containsKey(player.getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onPreLogin(AsyncPlayerPreLoginEvent event) { - if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - return; - } - - if (discordSRV.isShutdown()) { - return; - } else if (!discordSRV.isReady()) { - try { - discordSRV.waitForStatus(DiscordSRV.Status.CONNECTED); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking; - if (!config.enabled || config.action != ServerRequiredLinkingConfig.Action.FREEZE) { - return; - } - - Component blockReason = getBlockReason(event.getUniqueId(), event.getName(), false).join(); - if (blockReason != null) { - frozen.put(event.getUniqueId(), blockReason); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onLogin(PlayerLoginEvent event) { - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { - frozen.remove(event.getPlayer().getUniqueId()); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onJoin(PlayerJoinEvent event) { - UUID uuid = event.getPlayer().getUniqueId(); - Component blockReason = frozen.get(uuid); - if (blockReason == null) { - return; - } - - IPlayer player = discordSRV.playerProvider().player(uuid); - if (player == null) { - throw new IllegalStateException("Player not available: " + uuid); - } - - player.sendMessage(blockReason); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerMove(PlayerMoveEvent event) { - Component freezeReason = frozen.get(event.getPlayer().getUniqueId()); - if (freezeReason == null) { - return; - } - - Location from = event.getFrom(), to = event.getTo(); - if (from.getWorld().getName().equals(to.getWorld().getName()) - && from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ() - && from.getBlockY() >= to.getBlockY()) { - return; - } - - event.setTo( - new Location( - from.getWorld(), - from.getBlockX() + 0.5, - from.getBlockY(), - from.getBlockZ() + 0.5, - from.getYaw(), - from.getPitch() - ) - ); - - IPlayer player = discordSRV.playerProvider().player(event.getPlayer()); - player.sendMessage(freezeReason); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerChat(AsyncPlayerChatEvent event) { - Component freezeReason = frozen.get(event.getPlayer().getUniqueId()); - if (freezeReason == null) { - event.getRecipients().removeIf(this::isFrozen); - return; - } - - event.setCancelled(true); - - IPlayer player = discordSRV.playerProvider().player(event.getPlayer()); - player.sendMessage(freezeReason); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerCommand(PlayerCommandPreprocessEvent event) { - if (!isFrozen(event.getPlayer())) { - return; - } - - event.setCancelled(true); - - String message = event.getMessage(); - if (message.startsWith("/")) message = message.substring(1); - if (message.equals("discord link") || message.equals("link")) { - IPlayer player = discordSRV.playerProvider().player(event.getPlayer()); - - if (linkCheckRateLimit.getIfPresent(player.uniqueId()) != null) { - player.sendMessage(discordSRV.messagesConfig(player).pleaseWaitBeforeRunningThatCommandAgain.asComponent()); - return; - } - linkCheckRateLimit.put(player.uniqueId(), true); - - player.sendMessage(Component.text("Checking...")); - - UUID uuid = player.uniqueId(); - getBlockReason(uuid, player.username(), false).whenComplete((reason, t) -> { - if (t != null) { - return; - } - - if (reason == null) { - frozen.remove(uuid); - } else { - frozen.put(uuid, reason); - player.sendMessage(reason); - } - }); - } - } -} diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java b/bukkit/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java index f8cb24ef..9ffd04b7 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java @@ -26,12 +26,14 @@ import com.discordsrv.common.component.util.ComponentUtil; import com.discordsrv.common.player.IPlayer; import com.discordsrv.common.player.provider.model.SkinInfo; import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Locale; +import java.util.concurrent.CompletableFuture; public class BukkitPlayer extends BukkitCommandSender implements IPlayer { @@ -64,6 +66,17 @@ public class BukkitPlayer extends BukkitCommandSender implements IPlayer { return player.getName(); } + @Override + public CompletableFuture kick(Component component) { + return discordSRV.scheduler().executeOnMainThread(player, () -> { + if (PaperPlayer.isKickAvailable()) { + PaperPlayer.kick(player, component); + } else { + player.kickPlayer(BukkitComponentSerializer.legacy().serialize(component)); + } + }); + } + @Override public @Nullable SkinInfo skinInfo() { return SpigotPlayer.getSkinInfo(player); diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/requiredlinking/BukkitRequiredLinkingModule.java b/bukkit/src/main/java/com/discordsrv/bukkit/requiredlinking/BukkitRequiredLinkingModule.java index c2972add..f51a12e0 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/requiredlinking/BukkitRequiredLinkingModule.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/requiredlinking/BukkitRequiredLinkingModule.java @@ -20,14 +20,35 @@ package com.discordsrv.bukkit.requiredlinking; import com.discordsrv.bukkit.BukkitDiscordSRV; import com.discordsrv.bukkit.config.main.BukkitRequiredLinkingConfig; +import com.discordsrv.common.DiscordSRV; +import com.discordsrv.common.config.main.linking.ServerRequiredLinkingConfig; +import com.discordsrv.common.linking.LinkStore; import com.discordsrv.common.linking.requirelinking.ServerRequireLinkingModule; import com.discordsrv.common.player.IPlayer; -import org.bukkit.event.Listener; +import com.github.benmanes.caffeine.cache.Cache; +import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer; +import net.kyori.adventure.text.Component; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.*; +import org.bukkit.event.player.*; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; public class BukkitRequiredLinkingModule extends ServerRequireLinkingModule implements Listener { + private final Cache linkCheckRateLimit; + public BukkitRequiredLinkingModule(BukkitDiscordSRV discordSRV) { super(discordSRV); + this.linkCheckRateLimit = discordSRV.caffeineBuilder() + .expireAfterWrite(LinkStore.LINKING_CODE_RATE_LIMIT) + .build(); } @Override @@ -35,12 +56,254 @@ public class BukkitRequiredLinkingModule extends ServerRequireLinkingModule void register(Class eventType, BiConsumer eventConsumer) { + for (EventPriority priority : EventPriority.values()) { + if (priority == EventPriority.MONITOR) { + continue; + } + + discordSRV.server().getPluginManager().registerEvent( + eventType, + this, + priority, + (listener, event) -> eventConsumer.accept((T) event, priority), + discordSRV.plugin(), + true + ); + } + } + @Override public void recheck(IPlayer player) { getBlockReason(player.uniqueId(), player.username(), false).whenComplete((component, throwable) -> { if (component != null) { - // TODO: handle + switch (action()) { + case KICK: + player.kick(component); + break; + case FREEZE: + freeze(player, component); + break; + } + } else if (action() == ServerRequiredLinkingConfig.Action.FREEZE) { + frozen.remove(player.uniqueId()); } }); } + + public ServerRequiredLinkingConfig.Action action() { + return config().action; + } + + // + // Kick + // + + private void handle(AsyncPlayerPreLoginEvent event, EventPriority priority) { + handle( + "AsyncPlayerPreLoginEvent", + priority, + event.getUniqueId(), + event.getName(), + () -> event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED ? event.getLoginResult().name() : null, + text -> event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, text) + ); + } + + private void handle(PlayerLoginEvent event, EventPriority priority) { + Player player = event.getPlayer(); + handle( + "PlayerLoginEvent", + priority, + player.getUniqueId(), + player.getName(), + () -> event.getResult() != PlayerLoginEvent.Result.ALLOWED ? event.getResult().name() : null, + text -> event.disallow(PlayerLoginEvent.Result.KICK_OTHER, text) + ); + } + + private void handle( + String eventType, + EventPriority priority, + UUID playerUUID, + String playerName, + Supplier alreadyBlocked, + Consumer disallow + ) { + BukkitRequiredLinkingConfig config = config(); + if (!config.enabled || config.action != ServerRequiredLinkingConfig.Action.KICK + || !eventType.equals(config.kick.event) || !priority.name().equals(config.kick.priority)) { + return; + } + + String blockType = alreadyBlocked.get(); + if (blockType != null) { + discordSRV.logger().debug(playerName + " is already blocked for " + eventType + "/" + priority + " (" + blockType + ")"); + return; + } + + Component kickReason = getBlockReason(playerUUID, playerName, true).join(); + if (kickReason != null) { + disallow.accept(BukkitComponentSerializer.legacy().serialize(kickReason)); + } + } + + // + // Freeze + // + + private final Map frozen = new ConcurrentHashMap<>(); + + private boolean isFrozen(Player player) { + return frozen.containsKey(player.getUniqueId()); + } + + private void freeze(IPlayer player, Component blockReason) { + frozen.put(player.uniqueId(), blockReason); + player.sendMessage(blockReason); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPreLogin(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + return; + } + + if (discordSRV.isShutdown()) { + return; + } else if (!discordSRV.isReady()) { + try { + discordSRV.waitForStatus(DiscordSRV.Status.CONNECTED); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + BukkitRequiredLinkingConfig config = config(); + if (!config.enabled || config.action != ServerRequiredLinkingConfig.Action.FREEZE) { + return; + } + + Component blockReason = getBlockReason(event.getUniqueId(), event.getName(), false).join(); + if (blockReason != null) { + frozen.put(event.getUniqueId(), blockReason); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onLogin(PlayerLoginEvent event) { + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + frozen.remove(event.getPlayer().getUniqueId()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onJoin(PlayerJoinEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + Component blockReason = frozen.get(uuid); + if (blockReason == null) { + return; + } + + IPlayer player = discordSRV.playerProvider().player(uuid); + if (player == null) { + throw new IllegalStateException("Player not available: " + uuid); + } + + player.sendMessage(blockReason); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent event) { + Component freezeReason = frozen.get(event.getPlayer().getUniqueId()); + if (freezeReason == null) { + return; + } + + Location from = event.getFrom(), to = event.getTo(); + if (from.getWorld().getName().equals(to.getWorld().getName()) + && from.getBlockX() == to.getBlockX() + && from.getBlockZ() == to.getBlockZ() + && from.getBlockY() >= to.getBlockY()) { + return; + } + + event.setTo( + new Location( + from.getWorld(), + from.getBlockX() + 0.5, + from.getBlockY(), + from.getBlockZ() + 0.5, + from.getYaw(), + from.getPitch() + ) + ); + + IPlayer player = discordSRV.playerProvider().player(event.getPlayer()); + player.sendMessage(freezeReason); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerChat(AsyncPlayerChatEvent event) { + Component freezeReason = frozen.get(event.getPlayer().getUniqueId()); + if (freezeReason == null) { + event.getRecipients().removeIf(this::isFrozen); + return; + } + + event.setCancelled(true); + + IPlayer player = discordSRV.playerProvider().player(event.getPlayer()); + player.sendMessage(freezeReason); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + if (!isFrozen(event.getPlayer())) { + return; + } + + event.setCancelled(true); + + String message = event.getMessage(); + if (message.startsWith("/")) message = message.substring(1); + if (message.equals("discord link") || message.equals("link")) { + IPlayer player = discordSRV.playerProvider().player(event.getPlayer()); + + if (linkCheckRateLimit.getIfPresent(player.uniqueId()) != null) { + player.sendMessage(discordSRV.messagesConfig(player).pleaseWaitBeforeRunningThatCommandAgain.asComponent()); + return; + } + linkCheckRateLimit.put(player.uniqueId(), true); + + player.sendMessage(Component.text("Checking...")); + + UUID uuid = player.uniqueId(); + getBlockReason(uuid, player.username(), false).whenComplete((reason, t) -> { + if (t != null) { + return; + } + + if (reason == null) { + frozen.remove(uuid); + } else { + freeze(player, reason); + } + }); + } + } } diff --git a/common/src/main/java/com/discordsrv/common/bansync/BanSyncModule.java b/common/src/main/java/com/discordsrv/common/bansync/BanSyncModule.java index eaca09c0..92cb42a4 100644 --- a/common/src/main/java/com/discordsrv/common/bansync/BanSyncModule.java +++ b/common/src/main/java/com/discordsrv/common/bansync/BanSyncModule.java @@ -309,7 +309,7 @@ public class BanSyncModule extends AbstractSyncModule GenericSyncResults.ADD_GAME); } else { diff --git a/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java b/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java index 543dfdc5..5d36f06c 100644 --- a/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java +++ b/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java @@ -90,7 +90,7 @@ public final class ComponentUtil { } } - public static MinecraftComponent fromUnrelocated(@NotNull Object unrelocatedAdventure) { + public static MinecraftComponent fromUnrelocated(@NotNull com.discordsrv.unrelocate.net.kyori.adventure.text.Component unrelocatedAdventure) { MinecraftComponentImpl component = MinecraftComponentImpl.empty(); MinecraftComponent.Adapter adapter = component.unrelocatedAdapter(); if (adapter == null) { @@ -100,6 +100,14 @@ public final class ComponentUtil { return component; } + public static com.discordsrv.unrelocate.net.kyori.adventure.text.Component toUnrelocated(MinecraftComponent component) { + MinecraftComponent.Adapter adapter = component.unrelocatedAdapter(); + if (adapter == null) { + throw new IllegalStateException("Could not get unrelocated adventure gson serializer"); + } + return (com.discordsrv.unrelocate.net.kyori.adventure.text.Component) adapter.getComponent(); + } + public static Component join(@NotNull Component delimiter, @NotNull Collection components) { return join(delimiter, components.toArray(new ComponentLike[0])); } diff --git a/common/src/main/java/com/discordsrv/common/linking/requirelinking/RequiredLinkingModule.java b/common/src/main/java/com/discordsrv/common/linking/requirelinking/RequiredLinkingModule.java index 399233f5..dee4d252 100644 --- a/common/src/main/java/com/discordsrv/common/linking/requirelinking/RequiredLinkingModule.java +++ b/common/src/main/java/com/discordsrv/common/linking/requirelinking/RequiredLinkingModule.java @@ -154,9 +154,7 @@ public abstract class RequiredLinkingModule extends Abstra public void stateChanged(Someone someone, RequirementType requirementType, RT value, boolean newState) { for (ParsedRequirements activeRequirement : getAllActiveRequirements()) { for (Requirement requirement : activeRequirement.usedRequirements()) { - if (requirement.type() != requirementType - || !Objects.equals(requirement.value(), value) - || newState == requirement.negated()) { + if (requirement.type() != requirementType || !Objects.equals(requirement.value(), value)) { continue; } @@ -258,7 +256,7 @@ public abstract class RequiredLinkingModule extends Abstra } // None of the futures passed: additional requirements not met - return Component.text("You did not pass additionalRequirements"); + return Component.text("You did not pass requirements"); }); }); } diff --git a/common/src/main/java/com/discordsrv/common/linking/requirelinking/requirement/type/DiscordRoleRequirementType.java b/common/src/main/java/com/discordsrv/common/linking/requirelinking/requirement/type/DiscordRoleRequirementType.java index e5b481a6..fca86106 100644 --- a/common/src/main/java/com/discordsrv/common/linking/requirelinking/requirement/type/DiscordRoleRequirementType.java +++ b/common/src/main/java/com/discordsrv/common/linking/requirelinking/requirement/type/DiscordRoleRequirementType.java @@ -52,7 +52,7 @@ public class DiscordRoleRequirementType extends LongRequirementType { } @Subscribe - public void onDiscordMemberRoleAdd(AbstractDiscordMemberRoleChangeEvent event) { + public void onDiscordMemberRoleChange(AbstractDiscordMemberRoleChangeEvent event) { boolean add = event instanceof DiscordMemberRoleAddEvent; Someone someone = Someone.of(event.getMember().getUser().getId()); diff --git a/common/src/main/java/com/discordsrv/common/player/IPlayer.java b/common/src/main/java/com/discordsrv/common/player/IPlayer.java index c6cb94d5..d0c6282f 100644 --- a/common/src/main/java/com/discordsrv/common/player/IPlayer.java +++ b/common/src/main/java/com/discordsrv/common/player/IPlayer.java @@ -34,6 +34,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.UUID; +import java.util.concurrent.CompletableFuture; @PlaceholderPrefix("player_") public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender { @@ -66,6 +67,8 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende return identity().uuid(); } + CompletableFuture kick(Component component); + @NotNull @Placeholder("display_name") Component displayName(); diff --git a/common/src/main/java/com/discordsrv/common/storage/impl/sql/SQLStorage.java b/common/src/main/java/com/discordsrv/common/storage/impl/sql/SQLStorage.java index 133b7e74..1bdc26b4 100644 --- a/common/src/main/java/com/discordsrv/common/storage/impl/sql/SQLStorage.java +++ b/common/src/main/java/com/discordsrv/common/storage/impl/sql/SQLStorage.java @@ -174,8 +174,9 @@ public abstract class SQLStorage implements Storage { } // Get the uuid for the code - try (Statement statement = connection.createStatement()) { - try (ResultSet resultSet = statement.executeQuery("select PLAYERUUID from " + tablePrefix() + LINKING_CODES_TABLE_NAME + " LIMIT 1;")) { + try (PreparedStatement statement = connection.prepareStatement("select PLAYERUUID from " + tablePrefix() + LINKING_CODES_TABLE_NAME + " where CODE = ? LIMIT 1;")) { + statement.setString(1, code); + try (ResultSet resultSet = statement.executeQuery()) { if (resultSet.next()) { return UUID.fromString(resultSet.getString("PLAYERUUID")); }