mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-10-31 08:32:18 +01:00
Kick/(un)freeze player when they lose/gain additional requirements
This commit is contained in:
parent
a81e8305a0
commit
f285669990
@ -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<Void> addBan(@NotNull UUID playerUUID, @Nullable Instant until, @Nullable MinecraftComponent reason, @NotNull MinecraftComponent punisher);
|
||||
CompletableFuture<Void> removeBan(@NotNull UUID playerUUID);
|
||||
CompletableFuture<Void> kickPlayer(@NotNull DiscordSRVPlayer player, @NotNull MinecraftComponent message);
|
||||
}
|
||||
|
||||
interface Mutes extends PunishmentModule {
|
||||
|
@ -66,7 +66,7 @@ public class PaperComponentHandle<T> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<DiscordSRVBukkitBootstrap
|
||||
private final BukkitPlayerProvider playerProvider;
|
||||
private final BukkitPluginManager pluginManager;
|
||||
private AbstractBukkitCommandHandler commandHandler;
|
||||
private final BukkitRequiredLinkingListener requiredLinkingListener;
|
||||
private final BukkitGameCommandExecutionHelper autoCompleteHelper;
|
||||
|
||||
private final BukkitConnectionConfigManager connectionConfigManager;
|
||||
@ -101,7 +99,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
|
||||
load();
|
||||
|
||||
this.requiredLinkingListener = new BukkitRequiredLinkingListener(this);
|
||||
this.autoCompleteHelper = new BukkitGameCommandExecutionHelper(this);
|
||||
}
|
||||
|
||||
@ -238,7 +235,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
protected void disable() {
|
||||
super.disable();
|
||||
|
||||
requiredLinkingListener.disable();
|
||||
audiences.close();
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ package com.discordsrv.bukkit.ban;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.api.module.type.PunishmentModule;
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.api.punishment.Punishment;
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.common.bansync.BanSyncModule;
|
||||
@ -126,17 +125,4 @@ public class BukkitBanModule extends AbstractModule<BukkitDiscordSRV> implements
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> 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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<UUID, Boolean> 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 <T extends Event> void register(Class<T> eventType, BiConsumer<T, EventPriority> 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<Component> 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<String> alreadyBlocked,
|
||||
Consumer<String> 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<UUID, Component> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Void> 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);
|
||||
|
@ -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<BukkitDiscordSRV> implements Listener {
|
||||
|
||||
private final Cache<UUID, Boolean> 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<Bukk
|
||||
return discordSRV.config().requiredLinking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
super.enable();
|
||||
|
||||
register(PlayerLoginEvent.class, this::handle);
|
||||
register(AsyncPlayerPreLoginEvent.class, this::handle);
|
||||
discordSRV.server().getPluginManager().registerEvents(this, discordSRV.plugin());
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Event> void register(Class<T> eventType, BiConsumer<T, EventPriority> 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<String> alreadyBlocked,
|
||||
Consumer<String> 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<UUID, Component> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
|
||||
.applyPlaceholderService()
|
||||
.build();
|
||||
|
||||
return bans.kickPlayer(player, kickMessage);
|
||||
return player.kick(ComponentUtil.fromAPI(kickMessage));
|
||||
})
|
||||
.thenApply(v -> GenericSyncResults.ADD_GAME);
|
||||
} else {
|
||||
|
@ -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<Object> 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<Object> 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<? extends ComponentLike> components) {
|
||||
return join(delimiter, components.toArray(new ComponentLike[0]));
|
||||
}
|
||||
|
@ -154,9 +154,7 @@ public abstract class RequiredLinkingModule<T extends DiscordSRV> extends Abstra
|
||||
public <RT> void stateChanged(Someone someone, RequirementType<RT> 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<T extends DiscordSRV> 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");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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<Void> kick(Component component);
|
||||
|
||||
@NotNull
|
||||
@Placeholder("display_name")
|
||||
Component displayName();
|
||||
|
@ -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"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user