mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-30 12:19:22 +00:00
Merge remote-tracking branch 'origin/dev/2.1.1' into feature/cumulus-1.1
# Conflicts: # build-logic/src/main/kotlin/Versions.kt
This commit is contained in:
@@ -40,13 +40,14 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.packet.PacketHandlers;
|
||||
import org.geysermc.floodgate.config.ConfigLoader;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.config.FloodgateConfigHolder;
|
||||
import org.geysermc.floodgate.config.loader.ConfigLoader;
|
||||
import org.geysermc.floodgate.link.PlayerLinkLoader;
|
||||
import org.geysermc.floodgate.module.ConfigLoadedModule;
|
||||
import org.geysermc.floodgate.module.PostInitializeModule;
|
||||
import org.geysermc.floodgate.news.NewsChecker;
|
||||
import org.geysermc.floodgate.util.Metrics;
|
||||
import org.geysermc.floodgate.util.PrefixCheckTask;
|
||||
|
||||
public class FloodgatePlatform {
|
||||
@@ -100,8 +101,6 @@ public class FloodgatePlatform {
|
||||
|
||||
InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY);
|
||||
|
||||
// todo provide build number and branch for Geyser dump
|
||||
|
||||
guice.getInstance(NewsChecker.class).start();
|
||||
}
|
||||
|
||||
@@ -125,6 +124,8 @@ public class FloodgatePlatform {
|
||||
|
||||
PrefixCheckTask.checkAndExecuteDelayed(config, logger);
|
||||
|
||||
guice.getInstance(Metrics.class);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -139,6 +140,7 @@ public class FloodgatePlatform {
|
||||
}
|
||||
}
|
||||
|
||||
guice.getInstance(NewsChecker.class).shutdown();
|
||||
api.getPlayerLink().stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class HandshakeDataImpl implements HandshakeData {
|
||||
int usernameLength = Math.min(bedrockData.getUsername().length(), 16 - prefix.length());
|
||||
javaUsername = prefix + bedrockData.getUsername().substring(0, usernameLength);
|
||||
if (config.isReplaceSpaces()) {
|
||||
javaUsername = javaUsername.replaceAll(" ", "_");
|
||||
javaUsername = javaUsername.replace(" ", "_");
|
||||
}
|
||||
|
||||
javaUniqueId = Utils.getJavaUuid(bedrockData.getXuid());
|
||||
|
||||
@@ -35,20 +35,20 @@ import cloud.commandframework.context.CommandContext;
|
||||
import com.google.inject.Inject;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.link.LinkRequestResult;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.command.util.Permission;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.link.GlobalPlayerLinking;
|
||||
import org.geysermc.floodgate.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.platform.command.TranslatableMessage;
|
||||
import org.geysermc.floodgate.player.UserAudience;
|
||||
import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
|
||||
import org.geysermc.floodgate.player.UserAudienceArgument;
|
||||
import org.geysermc.floodgate.player.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.player.audience.ProfileAudienceArgument;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
import org.geysermc.floodgate.util.Permissions;
|
||||
|
||||
@NoArgsConstructor
|
||||
public final class LinkAccountCommand implements FloodgateCommand {
|
||||
@@ -60,8 +60,8 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
return commandManager.commandBuilder("linkaccount",
|
||||
ArgumentDescription.of("Link your Java account with your Bedrock account"))
|
||||
.senderType(PlayerAudience.class)
|
||||
.permission(Permissions.COMMAND_LINK.get())
|
||||
.argument(UserAudienceArgument.of("player", true))
|
||||
.permission(Permission.COMMAND_LINK.get())
|
||||
.argument(ProfileAudienceArgument.of("player", true))
|
||||
.argument(StringArgument.optional("code"))
|
||||
.handler(this::execute)
|
||||
.build();
|
||||
@@ -90,6 +90,10 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileAudience targetUser = context.get("player");
|
||||
// allowUuid is false so username cannot be null
|
||||
String targetName = targetUser.username();
|
||||
|
||||
// when the player is a Bedrock player
|
||||
if (api.isFloodgatePlayer(sender.uuid())) {
|
||||
if (!context.contains("code")) {
|
||||
@@ -97,8 +101,6 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
UserAudience targetUser = context.get("player");
|
||||
String targetName = targetUser.username();
|
||||
String code = context.get("code");
|
||||
|
||||
link.verifyLinkRequest(sender.uuid(), targetName, sender.username(), code)
|
||||
@@ -125,7 +127,7 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName);
|
||||
break;
|
||||
default:
|
||||
sender.disconnect(Component.text("Invalid account linking result"));
|
||||
sender.disconnect("Invalid account linking result");
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -137,9 +139,6 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
UserAudience targetUser = context.get("player");
|
||||
String targetName = targetUser.username();
|
||||
|
||||
link.createLinkRequest(sender.uuid(), sender.username(), targetName)
|
||||
.whenComplete((result, throwable) -> {
|
||||
if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) {
|
||||
|
||||
@@ -28,7 +28,6 @@ package org.geysermc.floodgate.command;
|
||||
import cloud.commandframework.Command;
|
||||
import cloud.commandframework.CommandManager;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.platform.command.FloodgateCommand;
|
||||
@@ -47,7 +46,7 @@ public class TestCommand implements FloodgateCommand {
|
||||
@Override
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
int players = FloodgateApi.getInstance().getPlayers().size();
|
||||
context.getSender().sendMessage(Component.text(players));
|
||||
context.getSender().sendMessage(String.valueOf(players));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.TranslatableMessage;
|
||||
import org.geysermc.floodgate.player.UserAudience;
|
||||
import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
import org.geysermc.floodgate.util.Permissions;
|
||||
import org.geysermc.floodgate.command.util.Permission;
|
||||
|
||||
@NoArgsConstructor
|
||||
public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
@@ -54,7 +54,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
return commandManager.commandBuilder("unlinkaccount",
|
||||
ArgumentDescription.of("Unlink your Java account from your Bedrock account"))
|
||||
.senderType(PlayerAudience.class)
|
||||
.permission(Permissions.COMMAND_UNLINK.get())
|
||||
.permission(Permission.COMMAND_UNLINK.get())
|
||||
.handler(this::execute)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -38,17 +38,18 @@ import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.command.util.Permission;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.platform.command.CommandUtil;
|
||||
import org.geysermc.floodgate.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.platform.command.TranslatableMessage;
|
||||
import org.geysermc.floodgate.platform.util.PlayerType;
|
||||
import org.geysermc.floodgate.player.UserAudience;
|
||||
import org.geysermc.floodgate.player.UserAudienceArgument;
|
||||
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType;
|
||||
import org.geysermc.floodgate.player.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.player.audience.ProfileAudienceArgument;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
import org.geysermc.floodgate.util.HttpUtils;
|
||||
import org.geysermc.floodgate.util.Permissions;
|
||||
|
||||
public class WhitelistCommand implements FloodgateCommand {
|
||||
@Inject private FloodgateConfig config;
|
||||
@@ -58,25 +59,25 @@ public class WhitelistCommand implements FloodgateCommand {
|
||||
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
|
||||
Command.Builder<UserAudience> builder = commandManager.commandBuilder("fwhitelist",
|
||||
ArgumentDescription.of("Easy way to whitelist Bedrock players"))
|
||||
.permission(Permissions.COMMAND_WHITELIST.get());
|
||||
.permission(Permission.COMMAND_WHITELIST.get());
|
||||
|
||||
commandManager.command(builder
|
||||
.literal("add", "a")
|
||||
.argument(UserAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
|
||||
.argument(ProfileAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
|
||||
.handler(context -> performCommand(context, true)));
|
||||
|
||||
return builder
|
||||
.literal("remove", "r")
|
||||
.argument(UserAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
|
||||
.argument(ProfileAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
|
||||
.handler(context -> performCommand(context, false))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void performCommand(CommandContext<UserAudience> context, boolean add) {
|
||||
UserAudience sender = context.getSender();
|
||||
UserAudience player = context.get("player");
|
||||
UUID uuid = player.uuid();
|
||||
String name = player.username();
|
||||
ProfileAudience profile = context.get("player");
|
||||
UUID uuid = profile.uuid();
|
||||
String name = profile.username();
|
||||
|
||||
if (name == null && uuid == null) {
|
||||
sender.sendMessage(Message.UNEXPECTED_ERROR);
|
||||
|
||||
@@ -35,9 +35,9 @@ import cloud.commandframework.context.CommandContext;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.command.util.Permission;
|
||||
import org.geysermc.floodgate.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.player.UserAudience;
|
||||
import org.geysermc.floodgate.util.Permissions;
|
||||
|
||||
public final class MainCommand implements FloodgateCommand {
|
||||
@Override
|
||||
@@ -46,12 +46,13 @@ public final class MainCommand implements FloodgateCommand {
|
||||
"floodgate",
|
||||
ArgumentDescription.of("A set of Floodgate related actions in one command"))
|
||||
.senderType(UserAudience.class)
|
||||
.permission(Permissions.COMMAND_MAIN.get())
|
||||
.permission(Permission.COMMAND_MAIN.get())
|
||||
.handler(this::execute);
|
||||
|
||||
for (SubCommand subCommand : SubCommand.VALUES) {
|
||||
commandManager.command(builder
|
||||
.literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description)
|
||||
.permission(subCommand.permission.get())
|
||||
.handler(subCommand.executor::accept)
|
||||
);
|
||||
}
|
||||
@@ -65,10 +66,12 @@ public final class MainCommand implements FloodgateCommand {
|
||||
StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n");
|
||||
|
||||
for (SubCommand subCommand : SubCommand.VALUES) {
|
||||
helpMessage.append('\n').append(COLOR_CHAR).append('b')
|
||||
.append(subCommand.name().toLowerCase(Locale.ROOT))
|
||||
.append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7')
|
||||
.append(subCommand.description);
|
||||
if (context.getSender().hasPermission(subCommand.permission.get())) {
|
||||
helpMessage.append('\n').append(COLOR_CHAR).append('b')
|
||||
.append(subCommand.name().toLowerCase(Locale.ROOT))
|
||||
.append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7')
|
||||
.append(subCommand.description);
|
||||
}
|
||||
}
|
||||
|
||||
context.getSender().sendMessage(helpMessage.toString());
|
||||
@@ -77,11 +80,12 @@ public final class MainCommand implements FloodgateCommand {
|
||||
@RequiredArgsConstructor
|
||||
enum SubCommand {
|
||||
FIREWALL("Check if your outgoing firewall allows Floodgate to work properly",
|
||||
FirewallCheckSubcommand::executeFirewall);
|
||||
Permission.COMMAND_MAIN_FIREWALL, FirewallCheckSubcommand::executeFirewall);
|
||||
|
||||
static final SubCommand[] VALUES = values();
|
||||
|
||||
final String description;
|
||||
final Permission permission;
|
||||
final Consumer<CommandContext<UserAudience>> executor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.util;
|
||||
|
||||
import static org.geysermc.floodgate.command.util.PermissionDefault.OP;
|
||||
import static org.geysermc.floodgate.command.util.PermissionDefault.TRUE;
|
||||
|
||||
public enum Permission {
|
||||
COMMAND_MAIN("floodgate.command.floodgate", TRUE),
|
||||
COMMAND_MAIN_FIREWALL(COMMAND_MAIN, "firewall", OP),
|
||||
COMMAND_LINK("floodgate.command.linkaccount", TRUE),
|
||||
COMMAND_UNLINK("floodgate.command.unlinkaccount", TRUE),
|
||||
COMMAND_WHITELIST("floodgate.command.fwhitelist", OP),
|
||||
|
||||
NEWS_RECEIVE("floodgate.news.receive", OP);
|
||||
|
||||
private final String permission;
|
||||
private final PermissionDefault defaultValue;
|
||||
|
||||
Permission(String permission, PermissionDefault defaultValue) {
|
||||
this.permission = permission;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
Permission(Permission parent, String child, PermissionDefault defaultValue) {
|
||||
this(parent.get() + "." + child, defaultValue);
|
||||
}
|
||||
|
||||
public String get() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public PermissionDefault defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -23,23 +23,8 @@
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.player;
|
||||
package org.geysermc.floodgate.command.util;
|
||||
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.floodgate.player.UserAudience.ConsoleAudience;
|
||||
|
||||
public interface ServerAudience extends Audience {
|
||||
@NonNull Iterable<? extends UserAudience> onlineAudiences();
|
||||
|
||||
@NonNull ConsoleAudience consoleAudience();
|
||||
|
||||
@NonNegative int onlineCount();
|
||||
|
||||
@Nullable UserAudience audienceOf(final @NonNull UUID uuid);
|
||||
|
||||
@Nullable UserAudience audienceOf(final @NonNull String username);
|
||||
public enum PermissionDefault {
|
||||
TRUE, FALSE, OP, NOT_OP
|
||||
}
|
||||
@@ -23,26 +23,27 @@
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.config.loader;
|
||||
package org.geysermc.floodgate.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.configutils.ConfigUtilities;
|
||||
import org.geysermc.configutils.file.codec.PathFileCodec;
|
||||
import org.geysermc.configutils.file.template.ResourceTemplateReader;
|
||||
import org.geysermc.configutils.updater.change.Changes;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.config.updater.ConfigUpdater;
|
||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
import org.geysermc.floodgate.crypto.KeyProducer;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class ConfigLoader {
|
||||
private final Path dataFolder;
|
||||
private final Class<? extends FloodgateConfig> configClass;
|
||||
private final DefaultConfigHandler configCreator;
|
||||
private final ConfigUpdater updater;
|
||||
|
||||
private final KeyProducer keyProducer;
|
||||
private final FloodgateCipher cipher;
|
||||
@@ -51,61 +52,42 @@ public final class ConfigLoader {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends FloodgateConfig> T load() {
|
||||
Path configPath = dataFolder.resolve("config.yml");
|
||||
|
||||
String defaultConfigName = "config.yml";
|
||||
boolean proxy = ProxyFloodgateConfig.class.isAssignableFrom(configClass);
|
||||
if (proxy) {
|
||||
defaultConfigName = "proxy-" + defaultConfigName;
|
||||
String templateFile = "config.yml";
|
||||
if (ProxyFloodgateConfig.class.isAssignableFrom(configClass)) {
|
||||
templateFile = "proxy-" + templateFile;
|
||||
}
|
||||
|
||||
boolean newConfig = !Files.exists(configPath);
|
||||
if (newConfig) {
|
||||
try {
|
||||
configCreator.createDefaultConfig(defaultConfigName, configPath);
|
||||
} catch (Exception exception) {
|
||||
logger.error("Error while creating config", exception);
|
||||
}
|
||||
}
|
||||
//todo old Floodgate logged a message when version = 0 and it generated a new key.
|
||||
// Might be nice to allow you to run a function for a specific version.
|
||||
|
||||
// it would also be nice to have sections in versionBuilder so that you don't have to
|
||||
// provide the path all the time
|
||||
|
||||
ConfigUtilities utilities =
|
||||
ConfigUtilities.builder()
|
||||
.fileCodec(PathFileCodec.of(dataFolder))
|
||||
.configFile("config.yml")
|
||||
.templateReader(ResourceTemplateReader.of(getClass()))
|
||||
.template(templateFile)
|
||||
.changes(Changes.builder()
|
||||
.version(1, Changes.versionBuilder()
|
||||
.keyRenamed("player-link.enable", "player-link.enabled")
|
||||
.keyRenamed("player-link.allow-linking", "player-link.allowed"))
|
||||
.version(2, Changes.versionBuilder()
|
||||
.keyRenamed("player-link.use-global-linking", "player-link.enable-global-linking"))
|
||||
.build())
|
||||
.definePlaceholder("metrics.uuid", UUID::randomUUID)
|
||||
.postInitializeCallbackArgument(this)
|
||||
.build();
|
||||
|
||||
T configInstance;
|
||||
try {
|
||||
// check and update if the config is outdated
|
||||
if (!newConfig) {
|
||||
updater.update(this, defaultConfigName);
|
||||
}
|
||||
|
||||
FloodgateConfig config = ConfigInitializer.initializeFrom(
|
||||
Files.newInputStream(configPath), configClass);
|
||||
|
||||
try {
|
||||
configInstance = (T) config;
|
||||
} catch (ClassCastException exception) {
|
||||
logger.error("Failed to cast config file to required class.", exception);
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
logger.error("Error while loading config", exception);
|
||||
return (T) utilities.executeOn(configClass);
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(
|
||||
"Failed to load the config! Try to delete the config file", exception);
|
||||
"Failed to load the config! Try to delete the config file if this error persists",
|
||||
throwable
|
||||
);
|
||||
}
|
||||
|
||||
Path keyPath = dataFolder.resolve(configInstance.getKeyFileName());
|
||||
// don't assume that the key always exists with the existence of a config
|
||||
if (!Files.exists(keyPath)) {
|
||||
generateKey(keyPath);
|
||||
}
|
||||
|
||||
try {
|
||||
Key key = keyProducer.produceFrom(keyPath);
|
||||
cipher.init(key);
|
||||
configInstance.setKey(key);
|
||||
} catch (IOException exception) {
|
||||
logger.error("Error while reading the key", exception);
|
||||
throw new RuntimeException("Failed to read the key!", exception);
|
||||
}
|
||||
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
public void generateKey(Path keyPath) {
|
||||
@@ -25,15 +25,20 @@
|
||||
|
||||
package org.geysermc.floodgate.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.configutils.loader.callback.CallbackResult;
|
||||
import org.geysermc.configutils.loader.callback.GenericPostInitializeCallback;
|
||||
|
||||
/**
|
||||
* The global Floodgate configuration file used in every platform. Some platforms have their own
|
||||
* addition to the global configuration like {@link ProxyFloodgateConfig} for the proxies.
|
||||
*/
|
||||
@Getter
|
||||
public class FloodgateConfig {
|
||||
public class FloodgateConfig implements GenericPostInitializeCallback<ConfigLoader> {
|
||||
private String keyFileName;
|
||||
private String usernamePrefix;
|
||||
private boolean replaceSpaces;
|
||||
@@ -42,22 +47,36 @@ public class FloodgateConfig {
|
||||
|
||||
private DisconnectMessages disconnect;
|
||||
private PlayerLinkConfig playerLink;
|
||||
private MetricsConfig metrics;
|
||||
|
||||
private boolean debug;
|
||||
private int configVersion;
|
||||
|
||||
private Key key;
|
||||
|
||||
public void setKey(Key key) {
|
||||
if (this.key == null) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProxy() {
|
||||
return this instanceof ProxyFloodgateConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallbackResult postInitialize(ConfigLoader loader) {
|
||||
Path keyPath = loader.getDataFolder().resolve(getKeyFileName());
|
||||
|
||||
// don't assume that the key always exists with the existence of a config
|
||||
if (!Files.exists(keyPath)) {
|
||||
loader.generateKey(keyPath);
|
||||
}
|
||||
|
||||
try {
|
||||
Key floodgateKey = loader.getKeyProducer().produceFrom(keyPath);
|
||||
loader.getCipher().init(floodgateKey);
|
||||
key = floodgateKey;
|
||||
} catch (IOException exception) {
|
||||
return CallbackResult.failed(exception.getMessage());
|
||||
}
|
||||
return CallbackResult.ok();
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class DisconnectMessages {
|
||||
private String invalidKey;
|
||||
@@ -68,10 +87,16 @@ public class FloodgateConfig {
|
||||
public static class PlayerLinkConfig {
|
||||
private boolean enabled;
|
||||
private boolean requireLink;
|
||||
private boolean enableOwnLinking = false;
|
||||
private boolean enableOwnLinking;
|
||||
private boolean allowed;
|
||||
private long linkCodeTimeout;
|
||||
private String type;
|
||||
private boolean enableGlobalLinking;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class MetricsConfig {
|
||||
private boolean enabled;
|
||||
private String uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.config.loader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
|
||||
import org.yaml.snakeyaml.introspector.BeanAccess;
|
||||
import org.yaml.snakeyaml.introspector.FieldProperty;
|
||||
import org.yaml.snakeyaml.introspector.Property;
|
||||
import org.yaml.snakeyaml.introspector.PropertyUtils;
|
||||
|
||||
public class ConfigInitializer {
|
||||
private static final Yaml YAML;
|
||||
|
||||
static {
|
||||
Constructor constructor =
|
||||
new CustomClassLoaderConstructor(ConfigInitializer.class.getClassLoader());
|
||||
|
||||
constructor.setPropertyUtils(new PropertyUtils() {
|
||||
@Override
|
||||
protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) {
|
||||
Map<String, Property> properties = new LinkedHashMap<>();
|
||||
getPropertiesFromClass(type, FloodgateConfig.class, properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void getPropertiesFromClass(
|
||||
Class<?> type,
|
||||
Class<?> stopAfter,
|
||||
Map<String, Property> propertyMap) {
|
||||
|
||||
Class<?> current = type;
|
||||
while (!Object.class.equals(current)) {
|
||||
for (Field field : current.getDeclaredFields()) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
|
||||
String correctName = getCorrectName(field.getName());
|
||||
// children should override parents
|
||||
propertyMap.putIfAbsent(correctName, new FieldProperty(field));
|
||||
}
|
||||
|
||||
if (field.getClass().getSuperclass().equals(current)) {
|
||||
getPropertiesFromClass(field.getClass(), field.getClass(), propertyMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (current.equals(stopAfter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
current = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
private String getCorrectName(String name) {
|
||||
// convert sendFloodgateData to send-floodgate-data,
|
||||
// which is the style of writing config fields
|
||||
StringBuilder propertyBuilder = new StringBuilder();
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char current = name.charAt(i);
|
||||
if (Character.isUpperCase(current)) {
|
||||
propertyBuilder.append('-').append(Character.toLowerCase(current));
|
||||
} else {
|
||||
propertyBuilder.append(current);
|
||||
}
|
||||
}
|
||||
return propertyBuilder.toString();
|
||||
}
|
||||
});
|
||||
constructor.getPropertyUtils().setSkipMissingProperties(true);
|
||||
YAML = new Yaml(constructor);
|
||||
}
|
||||
|
||||
public static <T extends FloodgateConfig> T initializeFrom(
|
||||
InputStream dataStream,
|
||||
Class<T> configClass) {
|
||||
return YAML.loadAs(dataStream, configClass);
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.config.loader;
|
||||
|
||||
import static org.geysermc.floodgate.util.MessageFormatter.format;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.geysermc.floodgate.util.Utils;
|
||||
|
||||
public class DefaultConfigHandler {
|
||||
public void createDefaultConfig(String defaultConfigLocation, Path configPath) throws IOException {
|
||||
List<String> configLines = loadDefaultConfig(defaultConfigLocation);
|
||||
|
||||
// writing the new config file
|
||||
Files.write(configPath, configLines);
|
||||
}
|
||||
|
||||
public List<String> loadDefaultConfig(String defaultConfigLocation)
|
||||
throws IOException {
|
||||
List<String> lines = Utils.readAllLines(defaultConfigLocation);
|
||||
|
||||
List<String> configLines = new ArrayList<>();
|
||||
String parentConfig = null;
|
||||
List<String> parentLines = null;
|
||||
|
||||
int lastInsertLine = -1;
|
||||
int tempAddAfter = -1;
|
||||
|
||||
for (String line : lines) {
|
||||
// >>(space) or >>|
|
||||
if (line.startsWith(">>")) {
|
||||
if (line.length() >= 3) {
|
||||
|
||||
// define parent file
|
||||
if (line.charAt(2) == ' ') {
|
||||
if (tempAddAfter != -1) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot define new parent without closing the current section");
|
||||
}
|
||||
parentConfig = line.substring(3);
|
||||
parentLines = null;
|
||||
lastInsertLine = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// define start / end of insert section
|
||||
if (line.charAt(2) == '|') {
|
||||
// end section
|
||||
if (line.length() == 3) {
|
||||
if (tempAddAfter == -1) {
|
||||
throw new IllegalStateException("Cannot close an unclosed section");
|
||||
}
|
||||
lastInsertLine = tempAddAfter;
|
||||
tempAddAfter = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// start insert section
|
||||
if (parentConfig == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot start insert section without providing a parent");
|
||||
}
|
||||
|
||||
if (tempAddAfter != -1) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot start section with an unclosed section");
|
||||
}
|
||||
|
||||
// note that addAfter starts counting from 1
|
||||
int addAfter = Integer.parseInt(line.substring(4)) - 1;
|
||||
if (lastInsertLine > -1 && addAfter < lastInsertLine) {
|
||||
throw new IllegalStateException(format(
|
||||
"Cannot add the same lines twice {} {}",
|
||||
addAfter, lastInsertLine
|
||||
));
|
||||
}
|
||||
|
||||
// as you can see by this implementation
|
||||
// we don't support parent files in parent files
|
||||
|
||||
if (lastInsertLine == -1) {
|
||||
parentLines = Utils.readAllLines(parentConfig);
|
||||
|
||||
for (int i = 0; i <= addAfter; i++) {
|
||||
configLines.add(parentLines.get(i));
|
||||
}
|
||||
} else {
|
||||
for (int i = lastInsertLine; i <= addAfter; i++) {
|
||||
configLines.add(parentLines.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
tempAddAfter = addAfter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.charAt(2) == '*') {
|
||||
if (parentConfig == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot write rest of the parent without providing a parent");
|
||||
}
|
||||
|
||||
if (tempAddAfter != -1) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot write rest of the parent config while an insert section is still open");
|
||||
}
|
||||
|
||||
if (lastInsertLine == -1) {
|
||||
parentLines = Utils.readAllLines(parentConfig);
|
||||
configLines.addAll(parentLines);
|
||||
continue;
|
||||
}
|
||||
|
||||
// the lastInsertLine has already been printed, so we won't print it twice
|
||||
for (int i = lastInsertLine + 1; i < parentLines.size(); i++) {
|
||||
configLines.add(parentLines.get(i));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"The use of >>" + line.charAt(2) + " is unknown");
|
||||
}
|
||||
throw new IllegalStateException("Unable do something with just >>");
|
||||
}
|
||||
// everything else: comments and key/value lines will be added
|
||||
configLines.add(line);
|
||||
}
|
||||
|
||||
return configLines;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.config.updater;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.config.loader.DefaultConfigHandler;
|
||||
|
||||
public final class ConfigFileUpdater {
|
||||
@Inject private FloodgateLogger logger;
|
||||
@Inject private DefaultConfigHandler defaultConfigHandler;
|
||||
|
||||
/**
|
||||
* Simple config file updater. Please note that all the keys should be unique and that this
|
||||
* system wasn't made for complex configurations.
|
||||
*
|
||||
* @param configLocation the location of the Floodgate config
|
||||
* @param currentVersion the key value map of the current config
|
||||
* @param renames name changes introduced in this version. new (key) to old
|
||||
* (value)
|
||||
* @param defaultConfigLocation the location of the default Floodgate config
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public void update(
|
||||
Path configLocation,
|
||||
Map<String, Object> currentVersion,
|
||||
Map<String, String> renames,
|
||||
String defaultConfigLocation)
|
||||
throws IOException {
|
||||
|
||||
List<String> notFound = new ArrayList<>();
|
||||
List<String> newConfig = defaultConfigHandler.loadDefaultConfig(defaultConfigLocation);
|
||||
|
||||
String spaces = "";
|
||||
Map<String, Object> map = null;
|
||||
|
||||
String line;
|
||||
for (int i = 0; i < newConfig.size(); i++) {
|
||||
line = newConfig.get(i);
|
||||
// we don't have to check comments or empty lines
|
||||
if (line.isEmpty() || line.charAt(0) == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringBuilder currentSpaces = new StringBuilder();
|
||||
while (line.charAt(currentSpaces.length()) == Ascii.SPACE) {
|
||||
currentSpaces.append(Ascii.SPACE);
|
||||
}
|
||||
|
||||
// end of subcategory
|
||||
if (!spaces.isEmpty() && currentSpaces.length() < spaces.length()) {
|
||||
// we can assume this since we don't allow subcategories of subcategories
|
||||
spaces = "";
|
||||
map = null;
|
||||
}
|
||||
|
||||
// ignore comments
|
||||
if (line.charAt(currentSpaces.length()) == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
int splitIndex = line.indexOf(':');
|
||||
// if the line has a 'key: value' structure
|
||||
if (splitIndex != -1) {
|
||||
|
||||
// start of a subcategory
|
||||
if (line.length() == splitIndex + 1) {
|
||||
if (currentSpaces.length() > 0) {
|
||||
throw new IllegalStateException(
|
||||
"Config too complex! I can't understand subcategories of a subcategory");
|
||||
}
|
||||
|
||||
spaces = " ";
|
||||
//todo allow rename of subcategory?
|
||||
//noinspection unchecked
|
||||
map = (Map<String, Object>) currentVersion.get(line.substring(0, splitIndex));
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = line.substring(spaces.length(), splitIndex);
|
||||
|
||||
// don't change the config-version to the old value!
|
||||
if (name.equals("config-version")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// allow multiple renames
|
||||
String tempName;
|
||||
String oldName = name;
|
||||
do {
|
||||
tempName = oldName;
|
||||
oldName = renames.getOrDefault(oldName, oldName);
|
||||
} while (!oldName.equals(tempName));
|
||||
|
||||
Object value;
|
||||
if (map != null) {
|
||||
value = map.get(oldName);
|
||||
} else {
|
||||
value = currentVersion.get(spaces + oldName);
|
||||
}
|
||||
|
||||
// use default value if the key doesn't exist in the current version
|
||||
if (value == null) {
|
||||
notFound.add(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
String v = (String) value;
|
||||
if (!v.startsWith("\"") || !v.endsWith("\"")) {
|
||||
value = "\"" + value + "\"";
|
||||
}
|
||||
//todo this doesn't update {0} {1} to {} {} e.g.
|
||||
}
|
||||
|
||||
logger.debug(name + " has been changed to " + value);
|
||||
newConfig.set(i, spaces + name + ": " + value);
|
||||
}
|
||||
}
|
||||
|
||||
Files.deleteIfExists(configLocation.getParent().resolve("config-old.yml"));
|
||||
Files.copy(configLocation, configLocation.getParent().resolve("config-old.yml"));
|
||||
Files.write(configLocation, newConfig);
|
||||
|
||||
logger.info("Successfully updated the config file! " +
|
||||
"Your old config has been moved to config-old.yml");
|
||||
|
||||
if (!notFound.isEmpty()) {
|
||||
StringBuilder messageBuilder = new StringBuilder(
|
||||
"Please note that the following keys we not found in the old config and " +
|
||||
"are now using the default Floodgate config value. Missing/new keys: ");
|
||||
|
||||
boolean first = true;
|
||||
for (String value : notFound) {
|
||||
if (!first) {
|
||||
messageBuilder.append(", ");
|
||||
}
|
||||
|
||||
String renamed = renames.get(value);
|
||||
if (renamed != null) {
|
||||
messageBuilder.append(renamed).append(" to ");
|
||||
}
|
||||
|
||||
messageBuilder.append(value);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
logger.info(messageBuilder.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.config.updater;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static org.geysermc.floodgate.util.MessageFormatter.format;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.config.loader.ConfigLoader;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class ConfigUpdater {
|
||||
private static final int CONFIG_VERSION = 2;
|
||||
private final Path dataFolder;
|
||||
private final ConfigFileUpdater fileUpdater;
|
||||
private final FloodgateLogger logger;
|
||||
|
||||
public void update(ConfigLoader loader, String defaultConfigLocation) {
|
||||
Path configLocation = dataFolder.resolve("config.yml");
|
||||
|
||||
Map<String, Object> config;
|
||||
|
||||
try (BufferedReader configReader = Files.newBufferedReader(configLocation)) {
|
||||
config = new Yaml().load(configReader);
|
||||
} catch (IOException exception) {
|
||||
logger.error("Error while opening the config file", exception);
|
||||
throw new RuntimeException("Failed to update config", exception);
|
||||
}
|
||||
|
||||
// new name -> old name
|
||||
Map<String, String> renames = new HashMap<>();
|
||||
|
||||
int version = 0; // pre-rewrite is the default config version
|
||||
|
||||
Object versionElement = config.get("config-version");
|
||||
// only rewrite configs have a config-version
|
||||
if (versionElement == null) {
|
||||
logger.warn("We've detected a pre-rewrite config file, please note that Floodgate " +
|
||||
"doesn't not work properly if you don't update your Floodgate key used on " +
|
||||
"all your servers (including Geyser). We'll try to update your Floodgate " +
|
||||
"config now and we'll also generate a new Floodgate key for you, but if " +
|
||||
"you're running a network or if you're running a Spigot server with " +
|
||||
"Geyser Standalone please update as you'll no longer be able to connect.");
|
||||
renames.put("enabled", "enable"); //todo make dump system and add a boolean 'found-legacy-key' or something like that
|
||||
renames.put("allowed", "allow-linking");
|
||||
|
||||
// relocate the old key so that they can restore it if it was a new key
|
||||
Path keyFilePath = dataFolder.resolve((String) config.get("key-file-name"));
|
||||
if (Files.exists(keyFilePath)) {
|
||||
try {
|
||||
Files.copy(keyFilePath, dataFolder.resolve("old-key.pem"));
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException(
|
||||
"Failed to relocate the old key to make place for a new key",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
loader.generateKey(keyFilePath);
|
||||
} else {
|
||||
// get (and verify) the config version
|
||||
checkArgument(
|
||||
versionElement instanceof Integer,
|
||||
"Config version should be an integer. Did someone mess with the config?"
|
||||
);
|
||||
|
||||
version = (int) versionElement;
|
||||
checkArgument(
|
||||
version > 0 && version <= CONFIG_VERSION,
|
||||
format("Config is newer then possible on this version! Expected {}, got {}",
|
||||
CONFIG_VERSION, version));
|
||||
}
|
||||
|
||||
// config is already up-to-date
|
||||
if (version == CONFIG_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (version < 2) {
|
||||
// renamed 'use-global-linking' to 'enable-global-linking'
|
||||
// and added 'enable-own-linking'
|
||||
renames.put("enable-global-linking", "use-global-linking");
|
||||
}
|
||||
|
||||
try {
|
||||
fileUpdater.update(configLocation, config, renames, defaultConfigLocation);
|
||||
} catch (IOException exception) {
|
||||
logger.error("Error while updating the config file", exception);
|
||||
throw new RuntimeException("Failed to update config", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public abstract class CommonPlatformInjector implements PlatformInjector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to loop throguh all the addons and call {@link InjectorAddon#onChannelClosed(Channel)}
|
||||
* Method to loop through all the addons and call {@link InjectorAddon#onChannelClosed(Channel)}
|
||||
* if {@link InjectorAddon#shouldInject()}
|
||||
*
|
||||
* @param channel the channel that was injected
|
||||
|
||||
@@ -40,12 +40,9 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.packet.PacketHandlers;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.config.ConfigLoader;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.config.FloodgateConfigHolder;
|
||||
import org.geysermc.floodgate.config.loader.ConfigLoader;
|
||||
import org.geysermc.floodgate.config.loader.DefaultConfigHandler;
|
||||
import org.geysermc.floodgate.config.updater.ConfigFileUpdater;
|
||||
import org.geysermc.floodgate.config.updater.ConfigUpdater;
|
||||
import org.geysermc.floodgate.crypto.AesCipher;
|
||||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||
@@ -105,27 +102,10 @@ public class CommonModule extends AbstractModule {
|
||||
@Singleton
|
||||
public ConfigLoader configLoader(
|
||||
@Named("configClass") Class<? extends FloodgateConfig> configClass,
|
||||
DefaultConfigHandler defaultConfigHandler,
|
||||
ConfigUpdater configUpdater,
|
||||
KeyProducer producer,
|
||||
FloodgateCipher cipher,
|
||||
FloodgateLogger logger) {
|
||||
return new ConfigLoader(dataDirectory, configClass, defaultConfigHandler, configUpdater,
|
||||
producer, cipher, logger);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public DefaultConfigHandler defaultConfigCreator() {
|
||||
return new DefaultConfigHandler();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public ConfigUpdater configUpdater(
|
||||
ConfigFileUpdater configFileUpdater,
|
||||
FloodgateLogger logger) {
|
||||
return new ConfigUpdater(dataDirectory, configFileUpdater, logger);
|
||||
return new ConfigLoader(dataDirectory, configClass, producer, cipher, logger);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.CommandUtil;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
import org.geysermc.floodgate.util.HttpUtils;
|
||||
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
|
||||
import org.geysermc.floodgate.util.Permissions;
|
||||
import org.geysermc.floodgate.command.util.Permission;
|
||||
|
||||
public class NewsChecker {
|
||||
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||
@@ -72,8 +72,6 @@ public class NewsChecker {
|
||||
}
|
||||
|
||||
private void checkNews() {
|
||||
// todo also check news for the downloaded database types
|
||||
|
||||
HttpResponse<JsonArray> response =
|
||||
HttpUtils.getSilent(
|
||||
Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME,
|
||||
@@ -121,14 +119,14 @@ public class NewsChecker {
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandUtil.hasPermission(player, Permissions.NEWS_RECEIVE.get())) {
|
||||
if (commandUtil.hasPermission(player, Permission.NEWS_RECEIVE.get())) {
|
||||
String message = Constants.COLOR_CHAR + "a " + news.getMessage();
|
||||
commandUtil.sendMessage(player, message);
|
||||
}
|
||||
break;
|
||||
case BROADCAST_TO_OPERATORS:
|
||||
Collection<Object> onlinePlayers = commandUtil.getOnlinePlayersWithPermission(
|
||||
Permissions.NEWS_RECEIVE.get()
|
||||
Permission.NEWS_RECEIVE.get()
|
||||
);
|
||||
|
||||
for (Object onlinePlayer : onlinePlayers) {
|
||||
@@ -182,7 +180,8 @@ public class NewsChecker {
|
||||
schedule(delayMs > 0 ? delayMs : 0);
|
||||
break;
|
||||
case CONFIG_SPECIFIC:
|
||||
//todo
|
||||
//todo this can replace the downloaded database types update check.
|
||||
// check if ConfigUtils has a way to check this easily
|
||||
break;
|
||||
}
|
||||
activeNewsItems.put(item.getId(), item);
|
||||
|
||||
@@ -25,30 +25,118 @@
|
||||
|
||||
package org.geysermc.floodgate.platform.command;
|
||||
|
||||
import static org.geysermc.floodgate.platform.util.PlayerType.ALL_PLAYERS;
|
||||
import static org.geysermc.floodgate.platform.util.PlayerType.ONLY_BEDROCK;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.platform.util.PlayerType;
|
||||
import org.geysermc.floodgate.player.UserAudience;
|
||||
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType;
|
||||
import org.geysermc.floodgate.player.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.util.LanguageManager;
|
||||
import org.geysermc.floodgate.util.Utils;
|
||||
|
||||
/**
|
||||
* An interface used across all Floodgate platforms to simple stuff in commands like kicking players
|
||||
* and sending player messages independent of the Floodgate platform implementation.
|
||||
*/
|
||||
public interface CommandUtil {
|
||||
@NonNull UserAudience getAudience(final @NonNull Object source);
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public abstract class CommandUtil {
|
||||
protected final LanguageManager manager;
|
||||
protected final FloodgateApi api;
|
||||
|
||||
@Nullable UserAudience getAudienceByUuid(final @NonNull UUID uuid);
|
||||
public abstract @NonNull UserAudience getUserAudience(@NonNull Object source);
|
||||
|
||||
@NonNull UserAudience getOfflineAudienceByUuid(final @NonNull UUID uuid);
|
||||
/**
|
||||
* Get a ProfileAudience from a source. The source should be a platform-specific player instance
|
||||
* when the player is online, and the username / uuid of the requested player when offline.
|
||||
*
|
||||
* @param source source to create a ProfileAudience from
|
||||
* @param allowOffline if offline players are allowed
|
||||
* @return a ProfileAudience unless allowOffline is false and the player isn't online
|
||||
*/
|
||||
public @Nullable ProfileAudience getProfileAudience(
|
||||
@NonNull Object source,
|
||||
boolean allowOffline) {
|
||||
Objects.requireNonNull(source);
|
||||
|
||||
@Nullable UserAudience getAudienceByUsername(final @NonNull String username);
|
||||
if (source instanceof UUID) {
|
||||
return allowOffline ? new ProfileAudience((UUID) source, null) : null;
|
||||
} else if (source instanceof String) {
|
||||
return allowOffline ? new ProfileAudience(null, (String) source) : null;
|
||||
} else {
|
||||
return new ProfileAudience(getUuidFromSource(source), getUsernameFromSource(source));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull UserAudience getOfflineAudienceByUsername(final @NonNull String username);
|
||||
protected abstract String getUsernameFromSource(@NonNull Object source);
|
||||
protected abstract UUID getUuidFromSource(@NonNull Object source);
|
||||
|
||||
@NonNull Collection<String> getOnlineUsernames(final @NonNull PlayerType limitTo);
|
||||
protected abstract Collection<?> getOnlinePlayers();
|
||||
|
||||
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
|
||||
Collection<?> players = getOnlinePlayers();
|
||||
|
||||
Collection<String> usernames = new ArrayList<>();
|
||||
switch (limitTo) {
|
||||
case ALL_PLAYERS:
|
||||
for (Object player : players) {
|
||||
usernames.add(getUsernameFromSource(player));
|
||||
}
|
||||
break;
|
||||
case ONLY_JAVA:
|
||||
for (Object player : players) {
|
||||
if (!api.isFloodgatePlayer(getUuidFromSource(player))) {
|
||||
usernames.add(getUsernameFromSource(player));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ONLY_BEDROCK:
|
||||
for (Object player : players) {
|
||||
if (api.isFloodgatePlayer(getUuidFromSource(player))) {
|
||||
usernames.add(getUsernameFromSource(player));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown PlayerType");
|
||||
}
|
||||
return usernames;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uuid
|
||||
* @return
|
||||
*/
|
||||
public abstract Object getPlayerByUuid(@NonNull UUID uuid);
|
||||
|
||||
public Object getPlayerByUuid(@NonNull UUID uuid, PlayerType limitTo) {
|
||||
return applyPlayerTypeFilter(getPlayerByUuid(uuid), limitTo, uuid);
|
||||
}
|
||||
|
||||
public abstract Object getPlayerByUsername(@NonNull String username);
|
||||
|
||||
public Object getPlayerByUsername(@NonNull String username, PlayerType limitTo) {
|
||||
return applyPlayerTypeFilter(getPlayerByUsername(username), limitTo, username);
|
||||
}
|
||||
|
||||
protected Object applyPlayerTypeFilter(Object player, PlayerType filter, Object fallback) {
|
||||
if (filter == ALL_PLAYERS || player instanceof String || player instanceof UUID) {
|
||||
return player;
|
||||
}
|
||||
return (filter == ONLY_BEDROCK) == api.isFloodgatePlayer(getUuidFromSource(player))
|
||||
? player
|
||||
: fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given player has the given permission.
|
||||
@@ -57,7 +145,7 @@ public interface CommandUtil {
|
||||
* @param permission the permission to check
|
||||
* @return true or false depending on if the player has the permission
|
||||
*/
|
||||
boolean hasPermission(Object player, String permission);
|
||||
public abstract boolean hasPermission(Object player, String permission);
|
||||
|
||||
/**
|
||||
* Get all online players with the given permission.
|
||||
@@ -65,17 +153,15 @@ public interface CommandUtil {
|
||||
* @param permission the permission to check
|
||||
* @return a list of online players that have the given permission
|
||||
*/
|
||||
Collection<Object> getOnlinePlayersWithPermission(String permission);
|
||||
|
||||
/**
|
||||
* Send a message to the specified target, no matter what platform Floodgate is running on.
|
||||
*
|
||||
* @param target the player that should receive the message
|
||||
* @param message the command message
|
||||
* @param locale the locale of the player
|
||||
* @param args the arguments
|
||||
*/
|
||||
void sendMessage(Object target, String locale, TranslatableMessage message, Object... args);
|
||||
public Collection<Object> getOnlinePlayersWithPermission(String permission) {
|
||||
List<Object> players = new ArrayList<>();
|
||||
for (Object player : getOnlinePlayers()) {
|
||||
if (hasPermission(player, permission)) {
|
||||
players.add(player);
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a raw message to the specified target, no matter what platform Floodgate is running
|
||||
@@ -84,18 +170,19 @@ public interface CommandUtil {
|
||||
* @param target the player that should receive the message
|
||||
* @param message the message
|
||||
*/
|
||||
void sendMessage(Object target, String message);
|
||||
public abstract void sendMessage(Object target, String message);
|
||||
|
||||
/**
|
||||
* Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)}
|
||||
* except it kicks the player using the given message as the kick reason.
|
||||
* Kicks the given player using the given message as the kick reason.
|
||||
*
|
||||
* @param player the player that should be kicked
|
||||
* @param message the command message
|
||||
* @param locale the locale of the player
|
||||
* @param args the arguments
|
||||
*/
|
||||
void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args);
|
||||
public abstract void kickPlayer(Object player, String message);
|
||||
|
||||
public String translateMessage(String locale, TranslatableMessage message, Object... args) {
|
||||
return message.translateMessage(manager, locale, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist the given Bedrock player.
|
||||
@@ -105,7 +192,7 @@ public interface CommandUtil {
|
||||
* @return true if the player has been whitelisted, false if the player was already whitelisted.
|
||||
* Defaults to false when this platform doesn't support whitelisting.
|
||||
*/
|
||||
default boolean whitelistPlayer(String xuid, String username) {
|
||||
public boolean whitelistPlayer(String xuid, String username) {
|
||||
UUID uuid = Utils.getJavaUuid(xuid);
|
||||
return whitelistPlayer(uuid, username);
|
||||
}
|
||||
@@ -118,7 +205,7 @@ public interface CommandUtil {
|
||||
* @return true if the player has been whitelisted, false if the player was already whitelisted.
|
||||
* Defaults to false when this platform doesn't support whitelisting.
|
||||
*/
|
||||
default boolean whitelistPlayer(UUID uuid, String username) {
|
||||
public boolean whitelistPlayer(UUID uuid, String username) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -130,7 +217,7 @@ public interface CommandUtil {
|
||||
* @return true if the player has been removed from the whitelist, false if the player wasn't
|
||||
* whitelisted. Defaults to false when this platform doesn't support whitelisting.
|
||||
*/
|
||||
default boolean removePlayerFromWhitelist(String xuid, String username) {
|
||||
public boolean removePlayerFromWhitelist(String xuid, String username) {
|
||||
UUID uuid = Utils.getJavaUuid(xuid);
|
||||
return removePlayerFromWhitelist(uuid, username);
|
||||
}
|
||||
@@ -143,7 +230,7 @@ public interface CommandUtil {
|
||||
* @return true if the player has been removed from the whitelist, false if the player wasn't
|
||||
* whitelisted. Defaults to false when this platform doesn't support whitelisting.
|
||||
*/
|
||||
default boolean removePlayerFromWhitelist(UUID uuid, String username) {
|
||||
public boolean removePlayerFromWhitelist(UUID uuid, String username) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ package org.geysermc.floodgate.platform.command;
|
||||
import org.geysermc.floodgate.util.LanguageManager;
|
||||
|
||||
/**
|
||||
* TranslatableMessage is the interface for a message that can be translated.
|
||||
* Messages are generally implemented using enums.
|
||||
* TranslatableMessage is the interface for a message that can be translated. Messages are generally
|
||||
* implemented using enums.
|
||||
*/
|
||||
public interface TranslatableMessage {
|
||||
/**
|
||||
@@ -52,7 +52,7 @@ public interface TranslatableMessage {
|
||||
for (int i = 0; i < translateParts.length; i++) {
|
||||
builder.append(manager.getString(translateParts[i], locale, args));
|
||||
if (translateParts.length != i + 1) {
|
||||
builder.append(" ");
|
||||
builder.append(' ');
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
|
||||
@@ -25,37 +25,26 @@
|
||||
|
||||
package org.geysermc.floodgate.platform.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.geysermc.floodgate.platform.command.CommandUtil;
|
||||
import org.geysermc.floodgate.platform.command.TranslatableMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public interface PlatformUtils {
|
||||
@RequiredArgsConstructor
|
||||
public abstract class PlatformUtils {
|
||||
/**
|
||||
* Send a message to the specified player, no matter what platform Floodgate is running on.
|
||||
*
|
||||
* @param player the player to send the message to
|
||||
* @param message the command message
|
||||
* @param locale the locale of the player
|
||||
* @param args the arguments
|
||||
* Returns the authentication type used on the platform
|
||||
*/
|
||||
void sendMessage(Object player, String locale, TranslatableMessage message, Object... args);
|
||||
public abstract AuthType authType();
|
||||
|
||||
/**
|
||||
* Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)} except it
|
||||
* kicks the player.
|
||||
*
|
||||
* @param player the player to send the message to
|
||||
* @param message the command message
|
||||
* @param locale the locale of the player
|
||||
* @param args the arguments
|
||||
* Returns the Minecraft version the server is based on (or the most recent supported version
|
||||
* for proxy platforms)
|
||||
*/
|
||||
void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args);
|
||||
public abstract String minecraftVersion();
|
||||
|
||||
Collection<String> getOnlineUsernames(PlayerType limitTo);
|
||||
public abstract String serverImplementationName();
|
||||
|
||||
enum PlayerType {
|
||||
ALL_PLAYERS,
|
||||
ONLY_BEDROCK,
|
||||
ONLY_JAVA
|
||||
public enum AuthType {
|
||||
ONLINE,
|
||||
PROXIED,
|
||||
OFFLINE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.platform.util;
|
||||
|
||||
public enum PlayerType {
|
||||
ALL_PLAYERS,
|
||||
ONLY_BEDROCK,
|
||||
ONLY_JAVA
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import io.netty.util.AttributeKey;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import lombok.AccessLevel;
|
||||
@@ -96,7 +95,7 @@ public final class FloodgateHandshakeHandler {
|
||||
String floodgateData = null;
|
||||
int dataVersion = -1;
|
||||
|
||||
StringBuilder hostnameBuilder = new StringBuilder();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String value : hostnameItems) {
|
||||
int version = FloodgateCipher.version(value);
|
||||
if (floodgateData == null && version != -1) {
|
||||
@@ -104,10 +103,14 @@ public final class FloodgateHandshakeHandler {
|
||||
dataVersion = version;
|
||||
continue;
|
||||
}
|
||||
hostnameBuilder.append(value).append('\0');
|
||||
|
||||
if (builder.length() > 0) {
|
||||
builder.append('\0');
|
||||
}
|
||||
builder.append(value);
|
||||
}
|
||||
// hostname now doesn't have Floodgate data anymore if it had
|
||||
return new HostnameSeparationResult(floodgateData, dataVersion, hostnameBuilder.toString());
|
||||
// the new hostname doesn't have Floodgate data anymore, if it had Floodgate data.
|
||||
return new HostnameSeparationResult(floodgateData, dataVersion, builder.toString());
|
||||
}
|
||||
|
||||
public CompletableFuture<HandshakeResult> handle(
|
||||
@@ -218,8 +221,6 @@ public final class FloodgateHandshakeHandler {
|
||||
bedrockData.getVerifyCode());
|
||||
}
|
||||
|
||||
correctHostname(handshakeData);
|
||||
|
||||
FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, handshakeData);
|
||||
|
||||
api.addPlayer(player);
|
||||
@@ -247,30 +248,9 @@ public final class FloodgateHandshakeHandler {
|
||||
bedrockData, configHolder.get(), null, hostname);
|
||||
handshakeHandlers.callHandshakeHandlers(handshakeData);
|
||||
|
||||
if (bedrockData != null) {
|
||||
correctHostname(handshakeData);
|
||||
}
|
||||
|
||||
return new HandshakeResult(resultType, handshakeData, bedrockData, null);
|
||||
}
|
||||
|
||||
private void correctHostname(HandshakeData handshakeData) {
|
||||
BedrockData bedrockData = handshakeData.getBedrockData();
|
||||
UUID correctUuid = handshakeData.getCorrectUniqueId();
|
||||
|
||||
// replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid
|
||||
String[] split = handshakeData.getHostname().split("\0");
|
||||
if (split.length >= 3) {
|
||||
if (logger.isDebug()) {
|
||||
logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'",
|
||||
split[1], bedrockData.getIp(), split[2], correctUuid.toString());
|
||||
}
|
||||
split[1] = bedrockData.getIp();
|
||||
split[2] = correctUuid.toString();
|
||||
}
|
||||
handshakeData.setHostname(String.join("\0", split));
|
||||
}
|
||||
|
||||
private CompletableFuture<Pair<BedrockData, LinkedPlayer>> fetchLinkedPlayer(BedrockData data) {
|
||||
if (!api.getPlayerLink().isEnabled()) {
|
||||
return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null));
|
||||
|
||||
@@ -25,52 +25,80 @@
|
||||
|
||||
package org.geysermc.floodgate.player;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.MessageType;
|
||||
import net.kyori.adventure.identity.Identified;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.platform.command.CommandUtil;
|
||||
import org.geysermc.floodgate.platform.command.TranslatableMessage;
|
||||
|
||||
public interface UserAudience extends Identified, Identity, Audience {
|
||||
@Override
|
||||
@NonNull UUID uuid();
|
||||
@Getter @Accessors(fluent = true)
|
||||
public class UserAudience {
|
||||
private final @NonNull UUID uuid;
|
||||
private final @NonNull String username;
|
||||
private final @NonNull String locale;
|
||||
private final @NonNull Object source;
|
||||
private final @NonNull CommandUtil commandUtil;
|
||||
|
||||
@NonNull String username();
|
||||
|
||||
@NonNull String locale();
|
||||
|
||||
@NonNull Object source();
|
||||
|
||||
boolean hasPermission(@NonNull final String permission);
|
||||
|
||||
@Override
|
||||
void sendMessage(final @NonNull Identity source,
|
||||
final @NonNull Component message,
|
||||
final @NonNull MessageType type);
|
||||
|
||||
void sendMessage(TranslatableMessage message, Object... args);
|
||||
|
||||
default void sendMessage(String message) {
|
||||
sendMessage(Component.text(message));
|
||||
public UserAudience(
|
||||
@NonNull UUID uuid,
|
||||
@NonNull String username,
|
||||
@NonNull String locale,
|
||||
@NonNull Object source,
|
||||
@NonNull CommandUtil commandUtil) {
|
||||
this.uuid = Objects.requireNonNull(uuid);
|
||||
this.username = username;
|
||||
this.locale = Objects.requireNonNull(locale);
|
||||
this.source = Objects.requireNonNull(source);
|
||||
this.commandUtil = Objects.requireNonNull(commandUtil);
|
||||
}
|
||||
|
||||
void disconnect(@NonNull final Component reason);
|
||||
|
||||
void disconnect(TranslatableMessage message, Object... args);
|
||||
|
||||
@Override
|
||||
default @NonNull Identity identity() {
|
||||
return this;
|
||||
public boolean hasPermission(@NonNull String permission) {
|
||||
return commandUtil.hasPermission(source(), permission);
|
||||
}
|
||||
|
||||
interface PlayerAudience extends UserAudience {
|
||||
boolean online();
|
||||
public void sendMessage(String message) {
|
||||
commandUtil.sendMessage(source(), message);
|
||||
}
|
||||
|
||||
interface ConsoleAudience extends UserAudience {
|
||||
public void sendMessage(TranslatableMessage message, Object... args) {
|
||||
sendMessage(translateMessage(message, args));
|
||||
}
|
||||
|
||||
public void disconnect(@NonNull String reason) {
|
||||
commandUtil.kickPlayer(source(), reason);
|
||||
}
|
||||
|
||||
public void disconnect(TranslatableMessage message, Object... args) {
|
||||
disconnect(translateMessage(message, args));
|
||||
}
|
||||
|
||||
public String translateMessage(TranslatableMessage message, Object... args) {
|
||||
return commandUtil.translateMessage(locale(), message, args);
|
||||
}
|
||||
|
||||
@Getter @Accessors(fluent = true)
|
||||
public static class PlayerAudience extends UserAudience {
|
||||
private final boolean online;
|
||||
|
||||
public PlayerAudience(
|
||||
@NonNull UUID uuid,
|
||||
@NonNull String username,
|
||||
@NonNull String locale,
|
||||
@NonNull Object source,
|
||||
@NonNull CommandUtil commandUtil,
|
||||
boolean online) {
|
||||
super(uuid, username, locale, source, commandUtil);
|
||||
|
||||
this.online = online;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter @Accessors(fluent = true)
|
||||
public static class ConsoleAudience extends UserAudience {
|
||||
public ConsoleAudience(@NonNull Object source, @NonNull CommandUtil commandUtil) {
|
||||
super(new UUID(0, 0), "CONSOLE", "en_us", source, commandUtil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,23 +23,20 @@
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.util;
|
||||
package org.geysermc.floodgate.player.audience;
|
||||
|
||||
public enum Permissions {
|
||||
COMMAND_MAIN("floodgate.command.floodgate"),
|
||||
COMMAND_LINK("floodgate.command.linkaccount"),
|
||||
COMMAND_UNLINK("floodgate.command.unlinkaccount"),
|
||||
COMMAND_WHITELIST("floodgate.command.fwhitelist"),
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
NEWS_RECEIVE("floodgate.news.receive");
|
||||
@Getter @Accessors(fluent = true)
|
||||
public final class ProfileAudience {
|
||||
private final @Nullable UUID uuid;
|
||||
private final @Nullable String username;
|
||||
|
||||
private final String permission;
|
||||
|
||||
Permissions(String permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
public String get() {
|
||||
return permission;
|
||||
public ProfileAudience(@Nullable UUID uuid, @Nullable String username) {
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.player;
|
||||
package org.geysermc.floodgate.player.audience;
|
||||
|
||||
import cloud.commandframework.arguments.CommandArgument;
|
||||
import cloud.commandframework.arguments.parser.ArgumentParseResult;
|
||||
@@ -37,62 +37,58 @@ import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.platform.command.CommandUtil;
|
||||
import org.geysermc.floodgate.platform.util.PlayerType;
|
||||
import org.geysermc.floodgate.player.UserAudience;
|
||||
|
||||
public final class UserAudienceArgument extends CommandArgument<UserAudience, UserAudience> {
|
||||
private UserAudienceArgument(final @NonNull String name, final UserAudienceParser parser) {
|
||||
super(true, name, parser, UserAudience.class);
|
||||
public class ProfileAudienceArgument extends CommandArgument<UserAudience, ProfileAudience> {
|
||||
private ProfileAudienceArgument(@NonNull String name, ProfileAudienceParser parser) {
|
||||
super(true, name, parser, ProfileAudience.class);
|
||||
}
|
||||
|
||||
public static UserAudienceArgument of(
|
||||
final String name,
|
||||
final boolean allowUuid,
|
||||
final boolean allowOffline,
|
||||
final PlayerType limitTo) {
|
||||
return new UserAudienceArgument(name,
|
||||
new UserAudienceParser(allowUuid, allowOffline, limitTo));
|
||||
public static ProfileAudienceArgument of(
|
||||
String name,
|
||||
boolean allowUuid,
|
||||
boolean allowOffline,
|
||||
PlayerType limitTo) {
|
||||
return new ProfileAudienceArgument(name,
|
||||
new ProfileAudienceParser(allowUuid, allowOffline, limitTo));
|
||||
}
|
||||
|
||||
public static UserAudienceArgument of(
|
||||
final String name,
|
||||
final boolean allowOffline,
|
||||
final PlayerType limitTo) {
|
||||
public static ProfileAudienceArgument of(
|
||||
String name,
|
||||
boolean allowOffline,
|
||||
PlayerType limitTo) {
|
||||
return of(name, false, allowOffline, limitTo);
|
||||
}
|
||||
|
||||
public static UserAudienceArgument ofOnline(final String name, final PlayerType limitTo) {
|
||||
public static ProfileAudienceArgument ofOnline(String name, PlayerType limitTo) {
|
||||
return of(name, false, false, limitTo);
|
||||
}
|
||||
|
||||
public static UserAudienceArgument ofOnline(final String name, final boolean allowUuid) {
|
||||
public static ProfileAudienceArgument ofOnline(String name, boolean allowUuid) {
|
||||
return of(name, allowUuid, false, PlayerType.ALL_PLAYERS);
|
||||
}
|
||||
|
||||
public static CommandArgument<UserAudience, UserAudience> ofOnline(final String name) {
|
||||
public static CommandArgument<UserAudience, ProfileAudience> ofOnline(String name) {
|
||||
return of(name, false, false, PlayerType.ALL_PLAYERS);
|
||||
}
|
||||
|
||||
public static UserAudienceArgument of(final String name, final boolean allowOffline) {
|
||||
public static ProfileAudienceArgument of(String name, boolean allowOffline) {
|
||||
return of(name, false, allowOffline, PlayerType.ALL_PLAYERS);
|
||||
}
|
||||
|
||||
public enum PlayerType {
|
||||
ALL_PLAYERS,
|
||||
ONLY_BEDROCK,
|
||||
ONLY_JAVA
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static final class UserAudienceParser
|
||||
implements ArgumentParser<UserAudience, UserAudience> {
|
||||
public static final class ProfileAudienceParser
|
||||
implements ArgumentParser<UserAudience, ProfileAudience> {
|
||||
|
||||
private final boolean allowUuid;
|
||||
private final boolean allowOffline;
|
||||
private final PlayerType limitTo;
|
||||
|
||||
@Override
|
||||
public @NonNull ArgumentParseResult<UserAudience> parse(
|
||||
final @NonNull CommandContext<@NonNull UserAudience> commandContext,
|
||||
final @NonNull Queue<@NonNull String> inputQueue) {
|
||||
public @NonNull ArgumentParseResult<ProfileAudience> parse(
|
||||
@NonNull CommandContext<@NonNull UserAudience> commandContext,
|
||||
@NonNull Queue<@NonNull String> inputQueue) {
|
||||
CommandUtil commandUtil = commandContext.get("CommandUtil");
|
||||
|
||||
String input = inputQueue.poll();
|
||||
@@ -111,7 +107,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
|
||||
StringBuilder builder = new StringBuilder(input);
|
||||
while (!inputQueue.isEmpty()) {
|
||||
String string = inputQueue.remove();
|
||||
builder.append(" ").append(string);
|
||||
builder.append(' ').append(string);
|
||||
if (string.endsWith("\"")) {
|
||||
break;
|
||||
}
|
||||
@@ -129,7 +125,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
|
||||
}
|
||||
}
|
||||
|
||||
UserAudience userAudience;
|
||||
ProfileAudience profileAudience;
|
||||
|
||||
if (input.length() > 16) {
|
||||
// This must be a UUID.
|
||||
@@ -146,46 +142,39 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
|
||||
|
||||
try {
|
||||
// We only want to make sure the UUID is valid here.
|
||||
final UUID uuid = UUID.fromString(input);
|
||||
userAudience = commandUtil.getAudienceByUuid(uuid);
|
||||
|
||||
if (userAudience == null && allowOffline) {
|
||||
userAudience = commandUtil.getOfflineAudienceByUuid(uuid);
|
||||
}
|
||||
Object player = commandUtil.getPlayerByUuid(UUID.fromString(input), limitTo);
|
||||
profileAudience = commandUtil.getProfileAudience(player, allowOffline);
|
||||
} catch (final IllegalArgumentException ignored) {
|
||||
return ArgumentParseResult.failure(
|
||||
new InvalidPlayerIdentifierException("Invalid UUID '" + input + "'"));
|
||||
}
|
||||
} else {
|
||||
// This is a username.
|
||||
userAudience = commandUtil.getAudienceByUsername(input);
|
||||
|
||||
if (userAudience == null && allowOffline) {
|
||||
userAudience = commandUtil.getOfflineAudienceByUsername(input);
|
||||
}
|
||||
Object player = commandUtil.getPlayerByUsername(input, limitTo);
|
||||
profileAudience = commandUtil.getProfileAudience(player, allowOffline);
|
||||
}
|
||||
|
||||
if (userAudience == null) {
|
||||
if (profileAudience == null) {
|
||||
return ArgumentParseResult.failure(
|
||||
new InvalidPlayerIdentifierException("Invalid player '" + input + "'"));
|
||||
}
|
||||
|
||||
return ArgumentParseResult.success(userAudience);
|
||||
return ArgumentParseResult.success(profileAudience);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<String> suggestions(
|
||||
final @NonNull CommandContext<UserAudience> commandContext,
|
||||
final @NonNull String input) {
|
||||
final CommandUtil commandUtil = commandContext.get("CommandUtil");
|
||||
final String trimmedInput = input.trim();
|
||||
@NonNull CommandContext<UserAudience> commandContext,
|
||||
@NonNull String input) {
|
||||
CommandUtil commandUtil = commandContext.get("CommandUtil");
|
||||
String trimmedInput = input.trim();
|
||||
|
||||
if (trimmedInput.isEmpty()) {
|
||||
return ImmutableList.copyOf(commandUtil.getOnlineUsernames(limitTo));
|
||||
}
|
||||
|
||||
final String lowercaseInput = input.toLowerCase(Locale.ROOT);
|
||||
final ImmutableList.Builder<String> builder = ImmutableList.builder();
|
||||
String lowercaseInput = input.toLowerCase(Locale.ROOT);
|
||||
ImmutableList.Builder<String> builder = ImmutableList.builder();
|
||||
|
||||
for (final String player : commandUtil.getOnlineUsernames(limitTo)) {
|
||||
if (player.toLowerCase(Locale.ROOT).startsWith(lowercaseInput)) {
|
||||
@@ -205,7 +194,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
|
||||
public static final class InvalidPlayerIdentifierException extends IllegalArgumentException {
|
||||
private static final long serialVersionUID = -6500019324607183855L;
|
||||
|
||||
public InvalidPlayerIdentifierException(final @NonNull String message) {
|
||||
public InvalidPlayerIdentifierException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
package org.geysermc.floodgate.util;
|
||||
|
||||
public final class Constants {
|
||||
public static final String VERSION = "${floodgateVersion}";
|
||||
public static final int BUILD_NUMBER = Integer.parseInt("${buildNumber}");
|
||||
public static final String GIT_BRANCH = "${branch}";
|
||||
public static final int METRICS_ID = 14649;
|
||||
|
||||
public static final char COLOR_CHAR = '§';
|
||||
|
||||
|
||||
147
core/src/main/java/org/geysermc/floodgate/util/Metrics.java
Normal file
147
core/src/main/java/org/geysermc/floodgate/util/Metrics.java
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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 com.google.inject.Inject;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Named;
|
||||
import org.bstats.MetricsBase;
|
||||
import org.bstats.charts.DrilldownPie;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import org.bstats.json.JsonObjectBuilder;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig.MetricsConfig;
|
||||
import org.geysermc.floodgate.platform.util.PlatformUtils;
|
||||
|
||||
public final class Metrics {
|
||||
private final MetricsBase metricsBase;
|
||||
|
||||
@Inject
|
||||
Metrics(FloodgateConfig config, PlatformUtils platformUtils, FloodgateApi api,
|
||||
@Named("implementationName") String implementationName, FloodgateLogger logger) {
|
||||
|
||||
MetricsConfig metricsConfig = config.getMetrics();
|
||||
|
||||
metricsBase = new MetricsBase(
|
||||
"server-implementation",
|
||||
metricsConfig.getUuid(),
|
||||
Constants.METRICS_ID,
|
||||
metricsConfig.isEnabled(),
|
||||
this::appendPlatformData,
|
||||
jsonObjectBuilder -> { /* NOP */ },
|
||||
null,
|
||||
() -> true, // remove this if/when we add some form of reload support
|
||||
logger::error,
|
||||
logger::info,
|
||||
Constants.DEBUG_MODE,
|
||||
Constants.DEBUG_MODE,
|
||||
Constants.DEBUG_MODE
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new SingleLineChart("players", api::getPlayerCount)
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new DrilldownPie("player_count", () -> {
|
||||
int playerCount = api.getPlayerCount();
|
||||
// 0 = 0 - 4, 9 = 5 - 9, etc.
|
||||
int category = playerCount / 5 * 5;
|
||||
String categoryName = category + " - " + (category + 4);
|
||||
|
||||
return Collections.singletonMap(
|
||||
implementationName,
|
||||
Collections.singletonMap(categoryName, 1)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new SimplePie("authentication",
|
||||
() -> platformUtils.authType().name().toLowerCase(Locale.ROOT))
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new SimplePie("floodgate_version", () -> Constants.VERSION)
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new DrilldownPie("platform", () -> Collections.singletonMap(
|
||||
implementationName,
|
||||
Collections.singletonMap(platformUtils.serverImplementationName(), 1)
|
||||
)));
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new DrilldownPie("minecraft_version", () -> {
|
||||
// e.g.: 1.16.5 => (Spigot, 1)
|
||||
return Collections.singletonMap(
|
||||
implementationName,
|
||||
Collections.singletonMap(platformUtils.minecraftVersion(), 1)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// Source: Geyser
|
||||
metricsBase.addCustomChart(new DrilldownPie("java_version", () -> {
|
||||
Map<String, Map<String, Integer>> map = new HashMap<>();
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
Map<String, Integer> entry = new HashMap<>();
|
||||
entry.put(javaVersion, 1);
|
||||
|
||||
String majorVersion = javaVersion.split("\\.")[0];
|
||||
String release;
|
||||
|
||||
int indexOf = javaVersion.lastIndexOf('.');
|
||||
|
||||
if (majorVersion.equals("1")) {
|
||||
release = "Java " + javaVersion.substring(0, indexOf);
|
||||
} else {
|
||||
Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
|
||||
if (versionMatcher.find()) {
|
||||
majorVersion = versionMatcher.group(0);
|
||||
}
|
||||
release = "Java " + majorVersion;
|
||||
}
|
||||
map.put(release, entry);
|
||||
return map;
|
||||
}));
|
||||
}
|
||||
|
||||
private void appendPlatformData(JsonObjectBuilder builder) {
|
||||
builder.appendField("osName", System.getProperty("os.name"));
|
||||
builder.appendField("osArch", System.getProperty("os.arch"));
|
||||
builder.appendField("osVersion", System.getProperty("os.version"));
|
||||
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
|
||||
}
|
||||
}
|
||||
@@ -269,6 +269,11 @@ public final class ReflectionUtils {
|
||||
return (T) getValue(instance, getField(instance.getClass(), fieldName));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T castedStaticValue(Field field) {
|
||||
return getCastedValue(null, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a field. This method make the field accessible and then sets the value.<br>
|
||||
* This method doesn't throw an exception when failed, but it'll log the error to the console.
|
||||
|
||||
@@ -58,5 +58,9 @@ player-link:
|
||||
# you have limited internet access.
|
||||
enable-global-linking: true
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
uuid: ${metrics.uuid}
|
||||
|
||||
# Do not change this
|
||||
config-version: 2
|
||||
config-version: 3
|
||||
|
||||
Submodule core/src/main/resources/languages updated: d08abbfab6...38cb4a52df
Reference in New Issue
Block a user