1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2026-01-06 15:42:03 +00:00

Added initial version of whitelist command and fixed a Java 16 issue

This commit is contained in:
Tim203
2021-02-25 02:17:19 +01:00
parent 3c15fcc298
commit 3a66d524a1
15 changed files with 410 additions and 150 deletions

View File

@@ -43,12 +43,12 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>${adventure-serializer.version}</version>
<version>${adventure-platform.version}</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-bungeecord</artifactId>
<version>${adventure-serializer.version}</version>
<version>${adventure-platform.version}</version>
</dependency>
<!-- the following common dependencies are already present on the platform -->
<dependency>

View File

@@ -144,6 +144,16 @@ public final class BungeeCommandUtil implements CommandUtil {
cast(player).disconnect(translateAndTransform(locale, message, args));
}
@Override
public boolean whitelistPlayer(String xuid, String username) {
return false; // todo
}
@Override
public boolean removePlayerFromWhitelist(String xuid, String username) {
return false; // todo
}
public BaseComponent[] translateAndTransform(String locale, CommandMessage message,
Object... args) {
return TextComponent.fromLegacyText(message.translateMessage(manager, locale, args));

View File

@@ -16,7 +16,7 @@
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.3</version>
<version>5.0.0-BETA-1</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.command;
import cloud.commandframework.Command;
import cloud.commandframework.CommandManager;
import cloud.commandframework.Description;
import cloud.commandframework.context.CommandContext;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
public class WhitelistCommand implements FloodgateCommand {
@Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger;
@Override
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
Command.Builder<UserAudience> builder = commandManager.commandBuilder("fwhitelist",
Description.of("Easy way to whitelist Bedrock players"))
.permission("floodgate.command.fwhitelist");
commandManager.command(builder
.literal("add", "a")
.argument(UserAudienceArgument.of("player", true))
.handler(context -> performCommand(context, true)));
return builder
.literal("remove", "r")
.argument(UserAudienceArgument.of("player", true))
.handler(context -> performCommand(context, false))
.build();
}
public void performCommand(CommandContext<UserAudience> context, boolean add) {
UserAudience sender = context.getSender();
UserAudience player = context.get("player");
String name = player.username();
if (name.startsWith(config.getUsernamePrefix())) {
name = name.substring(config.getUsernamePrefix().length());
}
if (name.length() < 1 || name.length() > 16) {
sender.sendMessage(Component.text(
"The given username '" + name + "' is not a valid username."));
return;
}
// todo let it use translations
final String tempName = name;
HttpUtils.asyncGet(Constants.GET_XUID_URL + name)
.whenComplete((result, error) -> {
if (error != null) {
sender.sendMessage(Component.text(
"An error occurred. See the console for more info"));
error.printStackTrace();
return;
}
JsonObject response = result.getResponse();
boolean success = response.get("success").getAsBoolean();
if (!success) {
sender.sendMessage(Component.text(
"An error occurred. See the console for more info"));
logger.error(
"Got an error from requesting the xuid of a Bedrock player: {}",
response.get("message").getAsString());
return;
}
JsonObject data = response.getAsJsonObject("data");
if (data.size() == 0) {
sender.sendMessage(Component.text(
"Couldn't find the user '" + tempName + "'"));
return;
}
String xuid = data.get("xuid").getAsString();
CommandUtil commandUtil = context.get("CommandUtil");
try {
if (add && commandUtil.whitelistPlayer(xuid, tempName)) {
sender.sendMessage(Component.text("Player has been whitelisted :)"));
} else if (!add && commandUtil.removePlayerFromWhitelist(xuid, tempName)) {
sender.sendMessage(Component.text("Player has been removed :o"));
} else {
sender.sendMessage(Component.text("Player was already whitelisted :o"));
}
} catch (Exception exception) {
logger.error(
"An unexpected error happened while executing the whitelist command",
exception);
}
});
}
@Override
public void execute(CommandContext<UserAudience> context) {
// ignored, all the logic is in the other method
}
}

View File

@@ -31,6 +31,7 @@ import com.google.inject.multibindings.ProvidesIntoSet;
import org.geysermc.floodgate.command.LinkAccountCommand;
import org.geysermc.floodgate.command.TestCommand;
import org.geysermc.floodgate.command.UnlinkAccountCommand;
import org.geysermc.floodgate.command.WhitelistCommand;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.register.CommandRegister;
@@ -52,6 +53,12 @@ public class CommandModule extends AbstractModule {
return new UnlinkAccountCommand();
}
@Singleton
@ProvidesIntoSet
public FloodgateCommand whitelistCommand() {
return new WhitelistCommand();
}
@Singleton
@ProvidesIntoSet
public FloodgateCommand testCommand() {

View File

@@ -69,4 +69,23 @@ public interface CommandUtil {
* @param args the arguments
*/
void kickPlayer(Object player, String locale, CommandMessage message, Object... args);
/**
* Whitelist the given Bedrock player.
*
* @param xuid the xuid of the username to be whitelisted
* @param username the username to be whitelisted
* @return true if the player has been whitelisted, false if the player was already whitelisted
*/
boolean whitelistPlayer(String xuid, String username);
/**
* Removes the given Bedrock player from the whitelist.
*
* @param xuid the xuid of the username to be removed from the whitelist
* @param username the username to be removed from the whitelist
* @return true if the player has been removed from the whitelist, false if the player wasn't
* whitelisted
*/
boolean removePlayerFromWhitelist(String xuid, String username);
}

View File

@@ -28,7 +28,10 @@ package org.geysermc.floodgate.util;
public final class Constants {
public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$";
public static final int LOGIN_SUCCESS_PACKET_ID = 2;
public static final String WEBSOCKET_URL = "wss://api.geysermc.org/ws";
private static final String API_BASE_URL = "s://api.geysermc.org";
public static final String WEBSOCKET_URL = "ws" + API_BASE_URL + "/ws";
public static final String GET_XUID_URL = "http" + API_BASE_URL + "/v1/xbox/xuid/";
public static final boolean DEBUG_MODE = true;

View File

@@ -27,27 +27,33 @@ package org.geysermc.floodgate.util;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.awt.image.BufferedImage;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.imageio.ImageIO;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@SuppressWarnings("all")
public class HttpUtils {
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
private static final Gson GSON = new Gson();
private static final String USER_AGENT = "GeyserMC/Floodgate";
private static final String CONNECTION_STRING = "--";
private static final String BOUNDARY = "******";
private static final String END = "\r\n";
public static CompletableFuture<HttpResponse> asyncGet(String urlString) {
return CompletableFuture.supplyAsync(() -> {
return get(urlString);
}, EXECUTOR_SERVICE);
}
public static HttpResponse get(String urlString) {
HttpURLConnection connection;
@@ -70,42 +76,6 @@ public class HttpUtils {
return readResponse(connection);
}
public static HttpResponse post(String urlString, BufferedImage... images) {
HttpURLConnection connection;
try {
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
} catch (Exception exception) {
throw new RuntimeException("Failed to create connection", exception);
}
DataOutputStream outputStream = null;
try {
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setRequestProperty("User-Agent", USER_AGENT);
connection.setRequestProperty(
"Content-Type",
"multipart/form-data;boundary=" + BOUNDARY
);
outputStream = new DataOutputStream(connection.getOutputStream());
writeDataFor(outputStream, images);
} catch (Exception exception) {
throw new RuntimeException("Failed to create request", exception);
} finally {
try {
outputStream.close();
} catch (Exception ignored) {
}
}
return readResponse(connection);
}
private static HttpResponse readResponse(HttpURLConnection connection) {
InputStream stream = null;
try {
@@ -136,31 +106,6 @@ public class HttpUtils {
}
}
public static void writeDataFor(DataOutputStream outputStream, BufferedImage... images) {
try {
for (int i = 0; i < images.length; i++) {
outputStream.writeBytes(CONNECTION_STRING + BOUNDARY + END);
outputStream.writeBytes(
"Content-Disposition:form-data;name=file;filename=image" + i + ".png");
outputStream.writeBytes(END);
outputStream.writeBytes(END);
fileDataForImage(outputStream, images[i]);
outputStream.writeBytes(END);
}
outputStream.writeBytes(CONNECTION_STRING + BOUNDARY + CONNECTION_STRING + END);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
public static void fileDataForImage(OutputStream outputStream, BufferedImage image) {
try {
ImageIO.write(image, "png", outputStream);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static final class HttpResponse {

View File

@@ -25,21 +25,16 @@
package org.geysermc.floodgate.util;
import static org.geysermc.floodgate.util.MessageFormatter.format;
import com.google.common.base.Preconditions;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
public final class ReflectionUtils {
private static final Field MODIFIERS_FIELD;
/**
* The package name that is shared between all the {@link #getPrefixedClass(String)} calls so
* that the className will be a lot shorter. Example net.minecraft.server.v1_8R3.PacketHandshakingInSetProtocol
@@ -50,43 +45,6 @@ public final class ReflectionUtils {
@Setter
private static String prefix;
static {
Field modifiersField = null;
try {
modifiersField = Field.class.getDeclaredField("modifiers");
} catch (NoSuchFieldException ignored) {
// Java 12 compatibility, thanks to https://github.com/powermock/powermock/pull/1010
try {
Method declaredFields = getMethod(Class.class, "getDeclaredFields0", boolean.class);
if (declaredFields == null) {
throw new NoSuchMethodException("Cannot find method getDeclaredFields0");
}
Field[] fields = castedInvoke(Field.class, declaredFields, false);
if (fields == null) {
throw new RuntimeException("The Field class cannot have null fields");
}
for (Field field : fields) {
if ("modifiers".equals(field.getName())) {
modifiersField = field;
break;
}
}
} catch (Exception exception) {
throw new RuntimeException(format(
"Cannot find the modifiers field :/\nJava version: {}\nVendor: {} ({})",
System.getProperty("java.version"),
System.getProperty("java.vendor"),
System.getProperty("java.vendor.url")
), exception);
}
}
Preconditions.checkNotNull(modifiersField, "Modifiers field cannot be null!");
MODIFIERS_FIELD = modifiersField;
}
/**
* Get a class that is prefixed with the prefix provided in {@link #setPrefix(String)}. Calling
* this method is equal to calling {@link #getClass(String)} with <i>prefix</i>.<i>classname</i>
@@ -119,6 +77,24 @@ public final class ReflectionUtils {
}
}
@Nullable
public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... parameters) {
try {
return clazz.getConstructor(parameters);
} catch (NoSuchMethodException e) {
return null;
}
}
@Nullable
public static Object newInstance(Constructor<?> constructor, Object... parameters) {
try {
return constructor.newInstance(parameters);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
return null;
}
}
/**
* Get a field of a class. Calling this method is equal to calling {@link
* Class#getField(String)} where String is the fieldName when isPublic is true and calling this
@@ -288,33 +264,6 @@ public final class ReflectionUtils {
return field != null;
}
/**
* Set the value of a <b>final</b> field. This method first makes the field accessible, then
* removes the final modifier and then sets the value.<br> This method will not throw exceptions
* when failed, but it'll log the error to the console.
*
* @param instance the instance to set the value to
* @param field the field to set the value to
* @param value the value to set
* @return true if succeeded
*/
public static boolean setFinalValue(Object instance, Field field, Object value) {
try {
makeAccessible(field);
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers)) {
makeAccessible(MODIFIERS_FIELD);
MODIFIERS_FIELD.setInt(field, modifiers & ~Modifier.FINAL);
}
setValue(instance, field, value);
return true;
} catch (Exception exception) {
exception.printStackTrace();
return false;
}
}
/**
* Get a method from a class, it doesn't matter if the field is public or not. This method will
* first try to get a declared field and if that failed it'll try to get a public field.<br>
@@ -328,8 +277,10 @@ public final class ReflectionUtils {
* @return the requested method if it has been found, otherwise null
*/
@Nullable
public static Method getMethod(Class<?> clazz, String method, boolean declared,
Class<?>... arguments) {
public static Method getMethod(
Class<?> clazz, String method,
boolean declared,
Class<?>... arguments) {
try {
if (declared) {
return clazz.getMethod(method, arguments);
@@ -372,6 +323,46 @@ public final class ReflectionUtils {
return getMethod(instance.getClass(), methodName, arguments);
}
/**
* Get a method from a class by using the name of the method.
*
* @param clazz the class to search the method in
* @param methodName the name of the method
* @param declared if the method is declared or public
* @return the method if it has been found, otherwise null
*/
@Nullable
public static Method getMethodByName(Class<?> clazz, String methodName, boolean declared) {
Method[] methods = declared ? clazz.getDeclaredMethods() : clazz.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
return method;
}
}
return null;
}
/**
* Get a method from a class without having to provide a method name.
*
* @param clazz the class to search the method in
* @param paramType the type of one of the method parameters
* @param declared if the method is declared or public
* @return the method if it has been found, otherwise null
*/
@Nullable
public static Method getMethodFromParam(Class<?> clazz, Class<?> paramType, boolean declared) {
Method[] methods = declared ? clazz.getDeclaredMethods() : clazz.getMethods();
for (Method method : methods) {
for (Class<?> parameter : method.getParameterTypes()) {
if (parameter == paramType) {
return method;
}
}
}
return null;
}
/**
* Invoke the given method of the given instance with the given arguments.
*

View File

@@ -62,8 +62,9 @@
<spigot.version>1.13-R0.1-SNAPSHOT</spigot.version>
<bungee.version>1.15-SNAPSHOT</bungee.version>
<velocity.version>1.1.0</velocity.version>
<cloud.version>1.3.0</cloud.version>
<adventure-serializer.version>4.0.0-SNAPSHOT</adventure-serializer.version>
<cloud.version>1.4.0</cloud.version>
<adventure-api.version>4.5.0</adventure-api.version>
<adventure-platform.version>4.0.0-SNAPSHOT</adventure-platform.version>
<outputName>floodgate-${project.name}</outputName>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@@ -57,8 +57,13 @@
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-craftbukkit</artifactId>
<version>${adventure-serializer.version}</version>
<artifactId>adventure-text-serializer-legacy</artifactId>
<version>${adventure-api.version}</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>${adventure-api.version}</version>
</dependency>
<!-- the following common dependencies are already present on the platform -->
<dependency>

View File

@@ -147,6 +147,16 @@ public final class SpigotCommandUtil implements CommandUtil {
() -> cast(player).kickPlayer(translateAndTransform(locale, message, args)));
}
@Override
public boolean whitelistPlayer(String xuid, String username) {
return WhitelistUtils.addPlayer(xuid, username);
}
@Override
public boolean removePlayerFromWhitelist(String xuid, String username) {
return WhitelistUtils.removePlayer(xuid, username);
}
public String translateAndTransform(String locale, CommandMessage message, Object... args) {
// unlike others, Bukkit doesn't have to transform a message into another class.
return message.translateMessage(manager, locale, args);

View File

@@ -25,8 +25,6 @@
package org.geysermc.floodgate.util;
import static net.kyori.adventure.text.serializer.craftbukkit.BukkitComponentSerializer.legacy;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.audience.Audience;
@@ -35,6 +33,7 @@ import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -106,7 +105,6 @@ public class SpigotUserAudience implements UserAudience, ForwardingAudience.Sing
public static final class SpigotConsoleAudience extends SpigotUserAudience
implements ConsoleAudience {
public SpigotConsoleAudience(CommandSender source, CommandUtil commandUtil) {
super(new UUID(0, 0), "en_us", source, commandUtil);
}
@@ -116,7 +114,7 @@ public class SpigotUserAudience implements UserAudience, ForwardingAudience.Sing
@NonNull Identity source,
@NonNull Component message,
@NonNull MessageType type) {
source().sendMessage(legacy().serialize(message));
source().sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import static com.google.common.base.Preconditions.checkNotNull;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.bukkit.Bukkit;
@SuppressWarnings("ConstantConditions")
public final class WhitelistUtils {
private static final Method GET_SERVER;
private static final Method GET_PLAYER_LIST;
private static final Method GET_WHITELIST;
private static final Method IS_WHITELISTED;
private static final Constructor<?> WHITELIST_ENTRY;
private static final Method ADD_ENTRY;
private static final Method REMOVE_ENTRY;
static {
Class<?> bukkitServerClass = Bukkit.getServer().getClass();
GET_SERVER = ReflectionUtils.getMethod(bukkitServerClass, "getServer");
checkNotNull(GET_SERVER, bukkitServerClass.getName() + " doesn't have a getServer method?");
Class<?> minecraftServer = ReflectionUtils.getPrefixedClass("MinecraftServer");
GET_PLAYER_LIST = ReflectionUtils.getMethod(minecraftServer, "getPlayerList");
checkNotNull(GET_PLAYER_LIST, "Cannot find getPlayerList");
Class<?> playerList = ReflectionUtils.getPrefixedClass("PlayerList");
GET_WHITELIST = ReflectionUtils.getMethod(playerList, "getWhitelist");
checkNotNull(GET_WHITELIST, "Cannot find getWhitelist");
Class<?> whitelist = ReflectionUtils.getPrefixedClass("WhiteList");
IS_WHITELISTED = ReflectionUtils.getMethod(whitelist, "isWhitelisted", GameProfile.class);
checkNotNull(IS_WHITELISTED, "Couldn't find the isWhitelisted method!");
Class<?> whitelistEntry = ReflectionUtils.getPrefixedClass("WhiteListEntry");
WHITELIST_ENTRY = ReflectionUtils.getConstructor(whitelistEntry, GameProfile.class);
checkNotNull(WHITELIST_ENTRY, "Could not find required WhiteListEntry constructor");
Class<?> jsonList = ReflectionUtils.getPrefixedClass("JsonList");
ADD_ENTRY = ReflectionUtils.getMethodByName(jsonList, "add", false);
checkNotNull(ADD_ENTRY, "Cannot find add method");
Class<?> jsonListEntry = ReflectionUtils.getPrefixedClass("JsonListEntry");
REMOVE_ENTRY = ReflectionUtils.getMethodFromParam(jsonList, jsonListEntry, false);
checkNotNull(REMOVE_ENTRY, "Cannot find remove method");
}
/**
* Whitelist the given Bedrock player.
*
* @param xuid the xuid of the Bedrock player to be whitelisted
* @param username the username of the Bedrock player to be whitelisted
* @return true if the player has been whitelisted, false if the player is already whitelisted
*/
public static boolean addPlayer(String xuid, String username) {
Object whitelist = getWhitelist();
GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username);
if (ReflectionUtils.castedInvoke(whitelist, IS_WHITELISTED, profile)) {
return false;
}
Object entry = ReflectionUtils.newInstance(WHITELIST_ENTRY, profile);
ReflectionUtils.invoke(whitelist, ADD_ENTRY, entry);
return true;
}
/**
* Removes the given Bedrock player from the whitelist.
*
* @param xuid the xuid of the Bedrock player to be removed
* @param username the username of the Bedrock player to be removed
* @return true if the player has been removed from the whitelist, false if the player wasn't
* whitelisted
*/
public static boolean removePlayer(String xuid, String username) {
Object whitelist = getWhitelist();
GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username);
if (!(boolean) ReflectionUtils.castedInvoke(whitelist, IS_WHITELISTED, profile)) {
return false;
}
Object entry = ReflectionUtils.newInstance(WHITELIST_ENTRY, profile);
ReflectionUtils.invoke(whitelist, REMOVE_ENTRY, entry);
return true;
}
private static Object getWhitelist() {
Object minecraftServer = ReflectionUtils.invoke(Bukkit.getServer(), GET_SERVER);
Object playerList = ReflectionUtils.invoke(minecraftServer, GET_PLAYER_LIST);
return ReflectionUtils.invoke(playerList, GET_WHITELIST);
}
}

View File

@@ -143,6 +143,16 @@ public final class VelocityCommandUtil implements CommandUtil {
cast(player).disconnect(translateAndTransform(locale, message, args));
}
@Override
public boolean whitelistPlayer(String xuid, String username) {
return false; // todo
}
@Override
public boolean removePlayerFromWhitelist(String xuid, String username) {
return false; // todo
}
public Component translateAndTransform(String locale, CommandMessage message,
Object... args) {
return Component.text(message.translateMessage(manager, locale, args));