mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-19 14:59:20 +00:00
@@ -27,7 +27,6 @@ package org.geysermc.floodgate.core.command;
|
||||
|
||||
import static org.incendo.cloud.parser.standard.StringParser.stringParser;
|
||||
|
||||
import io.micronaut.context.annotation.Secondary;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -40,7 +39,6 @@ import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
|
||||
import org.geysermc.floodgate.core.link.CommonPlayerLink;
|
||||
import org.geysermc.floodgate.core.link.GlobalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.link.LinkVerificationException;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.TranslatableMessage;
|
||||
@@ -52,7 +50,6 @@ import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.description.Description;
|
||||
|
||||
@Singleton
|
||||
@Secondary
|
||||
public final class LinkAccountCommand implements FloodgateCommand {
|
||||
@Inject SimpleFloodgateApi api;
|
||||
@Inject CommonPlayerLink link;
|
||||
@@ -73,21 +70,16 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
public void execute(CommandContext<PlayerAudience> context) {
|
||||
UserAudience sender = context.sender();
|
||||
|
||||
//todo make this less hacky
|
||||
if (link instanceof GlobalPlayerLinking) {
|
||||
if (((GlobalPlayerLinking) link).getDatabase() != null) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE,
|
||||
Constants.LINK_INFO_URL);
|
||||
var linkState = link.state();
|
||||
if (!linkState.localLinkingActive()) {
|
||||
if (!linkState.globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
} else {
|
||||
sender.sendMessage(CommonCommandMessage.GLOBAL_LINKING_NOTICE,
|
||||
Constants.LINK_INFO_URL);
|
||||
return;
|
||||
sender.sendMessage(CommonCommandMessage.GLOBAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!link.isActive()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
} else if (linkState.globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
|
||||
ProfileAudience targetUser = context.get("player");
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
|
||||
import org.geysermc.floodgate.core.link.CommonPlayerLink;
|
||||
import org.geysermc.floodgate.core.link.GlobalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.TranslatableMessage;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
@@ -59,21 +58,16 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
public void execute(CommandContext<PlayerAudience> context) {
|
||||
UserAudience sender = context.sender();
|
||||
|
||||
//todo make this less hacky
|
||||
if (link instanceof GlobalPlayerLinking) {
|
||||
if (((GlobalPlayerLinking) link).getDatabase() != null) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE,
|
||||
Constants.LINK_INFO_URL);
|
||||
var linkState = link.state();
|
||||
if (!linkState.localLinkingActive()) {
|
||||
if (!linkState.globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
} else {
|
||||
sender.sendMessage(CommonCommandMessage.GLOBAL_LINKING_NOTICE,
|
||||
Constants.LINK_INFO_URL);
|
||||
return;
|
||||
sender.sendMessage(CommonCommandMessage.GLOBAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!link.isActive()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
} else if (linkState.globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
|
||||
link.isLinked(sender.uuid())
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.geysermc.floodgate.core.command.linkedaccounts;
|
||||
|
||||
import static org.geysermc.floodgate.core.command.linkedaccounts.LinkedAccountsCommand.linkInfoMessage;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.command.CommonCommandMessage;
|
||||
import org.geysermc.floodgate.core.command.LinkAccountCommand;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.geysermc.floodgate.core.http.ProfileFetcher;
|
||||
import org.geysermc.floodgate.core.link.LocalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
@Singleton
|
||||
final class AddLinkedAccountCommand extends FloodgateSubCommand {
|
||||
@Inject Optional<LocalPlayerLinking> optionalLinking;
|
||||
@Inject ProfileFetcher fetcher;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
AddLinkedAccountCommand() {
|
||||
super(LinkedAccountsCommand.class, "add", "Manually add a locally linked account", Permission.COMMAND_LINKED_MANAGE, "a");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<UserAudience> onBuild(Command.Builder<UserAudience> commandBuilder) {
|
||||
return super.onBuild(commandBuilder)
|
||||
.argument(ProfileAudience.ofAnyIdentifierBedrock("bedrock"))
|
||||
.argument(ProfileAudience.ofAnyIdentifierJava("java"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
UserAudience sender = context.sender();
|
||||
|
||||
if (optionalLinking.isEmpty()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
}
|
||||
|
||||
var linking = optionalLinking.get();
|
||||
if (linking.state().globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
|
||||
ProfileAudience bedrockInput = context.get("bedrock");
|
||||
ProfileAudience javaInput = context.get("java");
|
||||
AtomicReference<ProfileAudience> bedrockRef = new AtomicReference<>(bedrockInput);
|
||||
AtomicReference<ProfileAudience> javaRef = new AtomicReference<>(javaInput);
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<?>>();
|
||||
|
||||
if (bedrockRef.get().uuid() == null) {
|
||||
futures.add(fetcher.fetchXuidFor(bedrockRef.get().username()).thenAccept(bedrockRef::set));
|
||||
}
|
||||
if (javaRef.get().uuid() == null) {
|
||||
futures.add(fetcher.fetchUniqueIdFor(javaRef.get().username()).thenAccept(javaRef::set));
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
|
||||
.thenAccept($ -> {
|
||||
var bedrock = bedrockRef.get();
|
||||
var java = javaRef.get();
|
||||
|
||||
if (bedrock == null) {
|
||||
sender.sendMessage("Could not find Bedrock account with username " + bedrockInput.username());
|
||||
}
|
||||
if (java == null) {
|
||||
sender.sendMessage("Could not find Java account with username " + javaInput.username());
|
||||
}
|
||||
|
||||
linking.addLink(java.uuid(), java.username(), bedrock.uuid()).whenComplete((player, throwable) -> {
|
||||
if (throwable != null) {
|
||||
sender.sendMessage(LinkAccountCommand.Message.LINK_REQUEST_ERROR);
|
||||
logger.error("Exception while manually linking accounts", throwable);
|
||||
return;
|
||||
}
|
||||
sender.sendMessage("You've successfully linked:\n" + linkInfoMessage(player));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.geysermc.floodgate.core.command.linkedaccounts;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.command.CommonCommandMessage;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.geysermc.floodgate.core.http.ProfileFetcher;
|
||||
import org.geysermc.floodgate.core.link.LocalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
@Singleton
|
||||
final class InfoLinkedAccountCommand extends FloodgateSubCommand {
|
||||
@Inject Optional<LocalPlayerLinking> optionalLinking;
|
||||
@Inject FloodgateConfig config;
|
||||
@Inject ProfileFetcher fetcher;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
InfoLinkedAccountCommand() {
|
||||
super(LinkedAccountsCommand.class, "info", "Gets info about the link status of an user", "i");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<UserAudience> onBuild(Command.Builder<UserAudience> commandBuilder) {
|
||||
return super.onBuild(commandBuilder).argument(ProfileAudience.ofAnyIdentifierBoth("player"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
UserAudience sender = context.sender();
|
||||
|
||||
if (optionalLinking.isEmpty()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
}
|
||||
|
||||
var linking = optionalLinking.get();
|
||||
if (linking.state().globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
|
||||
ProfileAudience playerInput = context.get("player");
|
||||
final boolean bedrock;
|
||||
|
||||
var future = CompletableFuture.completedFuture(playerInput);
|
||||
if (playerInput.uuid() == null) {
|
||||
if (playerInput.username().startsWith(config.usernamePrefix())) {
|
||||
future = fetcher.fetchXuidFor(playerInput.username().substring(config.usernamePrefix().length()));
|
||||
bedrock = true;
|
||||
} else {
|
||||
bedrock = false;
|
||||
future = fetcher.fetchUniqueIdFor(playerInput.username());
|
||||
}
|
||||
} else {
|
||||
bedrock = playerInput.uuid().getMostSignificantBits() == 0;
|
||||
}
|
||||
|
||||
String platform = bedrock ? "Bedrock" : "Java";
|
||||
|
||||
future.whenComplete((result, throwable) -> {
|
||||
if (throwable != null) {
|
||||
logger.error("Error while fetching player", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
sender.sendMessage("Could not find %s user with username %s".formatted(platform, playerInput.username()));
|
||||
return;
|
||||
}
|
||||
|
||||
linking.fetchLink(result.uuid()).whenComplete((link, error) -> {
|
||||
if (error != null) {
|
||||
sender.sendMessage("Error while looking up player link, see console");
|
||||
logger.error("Exception while fetching link status", error);
|
||||
return;
|
||||
}
|
||||
|
||||
var usernameOrUniqueId = playerInput.username() != null ? playerInput.username() : playerInput.uuid();
|
||||
|
||||
if (link == null) {
|
||||
sender.sendMessage("%s user %s is not linked!".formatted(platform, usernameOrUniqueId));
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage("Link info for %s user %s:\n%s"
|
||||
.formatted(platform, usernameOrUniqueId, LinkedAccountsCommand.linkInfoMessage(link)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.geysermc.floodgate.core.command.linkedaccounts;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
import org.geysermc.floodgate.core.platform.command.SubCommands;
|
||||
|
||||
@Singleton
|
||||
final class LinkedAccountsCommand extends SubCommands {
|
||||
LinkedAccountsCommand() {
|
||||
super("linkedaccounts", "Manage locally linked accounts", Permission.COMMAND_LINKED);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
static String linkInfoMessage(LinkedPlayer player) {
|
||||
return "Java UUID: %s\nJava username: %s\nBedrock UUID: %s"
|
||||
.formatted(player.javaUniqueId(), player.javaUsername(), player.bedrockId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.geysermc.floodgate.core.command.linkedaccounts;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.command.CommonCommandMessage;
|
||||
import org.geysermc.floodgate.core.command.LinkAccountCommand;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.geysermc.floodgate.core.http.ProfileFetcher;
|
||||
import org.geysermc.floodgate.core.link.LocalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
@Singleton
|
||||
final class RemoveLinkedAccountCommand extends FloodgateSubCommand {
|
||||
@Inject Optional<LocalPlayerLinking> optionalLinking;
|
||||
@Inject FloodgateConfig config;
|
||||
@Inject ProfileFetcher fetcher;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
RemoveLinkedAccountCommand() {
|
||||
super(LinkedAccountsCommand.class, "remove", "Manually remove a locally linked account", Permission.COMMAND_LINKED_MANAGE, "r");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<UserAudience> onBuild(Command.Builder<UserAudience> commandBuilder) {
|
||||
return super.onBuild(commandBuilder).argument(ProfileAudience.ofAnyIdentifierBoth("player"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
UserAudience sender = context.sender();
|
||||
|
||||
if (optionalLinking.isEmpty()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
}
|
||||
|
||||
var linking = optionalLinking.get();
|
||||
if (linking.state().globalLinkingEnabled()) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL);
|
||||
}
|
||||
|
||||
ProfileAudience playerInput = context.get("player");
|
||||
final boolean bedrock;
|
||||
|
||||
var future = CompletableFuture.completedFuture(playerInput);
|
||||
if (playerInput.uuid() == null) {
|
||||
if (playerInput.username().startsWith(config.usernamePrefix())) {
|
||||
future = fetcher.fetchXuidFor(playerInput.username().substring(config.usernamePrefix().length()));
|
||||
bedrock = true;
|
||||
} else {
|
||||
bedrock = false;
|
||||
future = fetcher.fetchUniqueIdFor(playerInput.username());
|
||||
}
|
||||
} else {
|
||||
bedrock = playerInput.uuid().getMostSignificantBits() == 0;
|
||||
}
|
||||
|
||||
String platform = bedrock ? "Bedrock" : "Java";
|
||||
|
||||
future.whenComplete((result, throwable) -> {
|
||||
if (throwable != null) {
|
||||
logger.error("Error while fetching player", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
sender.sendMessage("Could not find %s user with username %s"
|
||||
.formatted(platform, playerInput.username()));
|
||||
return;
|
||||
}
|
||||
|
||||
linking.unlink(result.uuid()).whenComplete(($, error) -> {
|
||||
if (error != null) {
|
||||
sender.sendMessage(LinkAccountCommand.Message.LINK_REQUEST_ERROR);
|
||||
logger.error("Exception while manually linking accounts", error);
|
||||
return;
|
||||
}
|
||||
sender.sendMessage("You've successfully unlinked %s user %s"
|
||||
.formatted(platform, playerInput.username()));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR;
|
||||
import com.google.gson.JsonElement;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BooleanSupplier;
|
||||
@@ -42,27 +43,17 @@ import org.geysermc.floodgate.core.util.HttpClient.HttpResponse;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
@Singleton
|
||||
final class FirewallCheckSubcommand extends FloodgateSubCommand {
|
||||
@Inject HttpClient httpProvider;
|
||||
|
||||
@Override
|
||||
public Class<?> parent() {
|
||||
return FirewallCheckSubcommand.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "firewall";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Check if your outgoing firewall allows Floodgate to work properly";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permission.COMMAND_MAIN_FIREWALL;
|
||||
FirewallCheckSubcommand() {
|
||||
super(
|
||||
MainCommand.class,
|
||||
"firewall",
|
||||
"Check if your outgoing firewall allows Floodgate to work properly",
|
||||
Permission.COMMAND_MAIN_FIREWALL
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,57 +25,14 @@
|
||||
|
||||
package org.geysermc.floodgate.core.command.main;
|
||||
|
||||
import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR;
|
||||
import static org.incendo.cloud.description.Description.description;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Locale;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.SubCommands;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.Command.Builder;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.description.Description;
|
||||
|
||||
@Singleton
|
||||
public final class MainCommand extends SubCommands implements FloodgateCommand {
|
||||
@Override
|
||||
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
|
||||
Builder<UserAudience> builder = commandManager.commandBuilder(
|
||||
"floodgate",
|
||||
Description.of("A set of Floodgate related actions in one command"))
|
||||
.senderType(UserAudience.class)
|
||||
.permission(Permission.COMMAND_MAIN.get())
|
||||
.handler(this::execute);
|
||||
|
||||
for (FloodgateSubCommand subCommand : subCommands()) {
|
||||
commandManager.command(builder
|
||||
.literal(subCommand.name().toLowerCase(Locale.ROOT), description(subCommand.description()))
|
||||
.permission(subCommand.permission().get())
|
||||
.handler(subCommand::execute)
|
||||
);
|
||||
}
|
||||
|
||||
// also register /floodgate itself
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n");
|
||||
|
||||
for (FloodgateSubCommand subCommand : subCommands()) {
|
||||
if (context.sender().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.sender().sendMessage(helpMessage.toString());
|
||||
MainCommand() {
|
||||
super("floodgate", "A set of Floodgate related actions in one command", Permission.COMMAND_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.command.WhitelistCommand.Message;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
@@ -38,28 +39,18 @@ import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.HttpClient;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
@Singleton
|
||||
public class VersionSubcommand extends FloodgateSubCommand {
|
||||
@Inject HttpClient httpClient;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
@Override
|
||||
public Class<?> parent() {
|
||||
return MainCommand.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Displays version information about Floodgate";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permission.COMMAND_MAIN_VERSION;
|
||||
VersionSubcommand() {
|
||||
super(
|
||||
MainCommand.class,
|
||||
"version",
|
||||
"Displays version information about Floodgate",
|
||||
Permission.COMMAND_MAIN_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,6 +32,8 @@ public enum Permission {
|
||||
COMMAND_LINK("floodgate.command.linkaccount", PermissionDefault.TRUE),
|
||||
COMMAND_UNLINK("floodgate.command.unlinkaccount", PermissionDefault.TRUE),
|
||||
COMMAND_WHITELIST("floodgate.command.fwhitelist", PermissionDefault.OP),
|
||||
COMMAND_LINKED("floodgate.command.linkedaccounts", PermissionDefault.OP),
|
||||
COMMAND_LINKED_MANAGE(COMMAND_LINKED, "manage", PermissionDefault.OP),
|
||||
|
||||
NEWS_RECEIVE("floodgate.news.receive", PermissionDefault.OP);
|
||||
|
||||
|
||||
@@ -43,11 +43,23 @@ public record ProfileAudience(@Nullable UUID uuid, @Nullable String username) {
|
||||
return of(name, true, true, PlayerType.ONLY_BEDROCK);
|
||||
}
|
||||
|
||||
public static CommandComponent.Builder<UserAudience, ProfileAudience> ofAnyIdentifierJava(String name) {
|
||||
return of(name, true, true, PlayerType.ONLY_JAVA);
|
||||
}
|
||||
|
||||
public static CommandComponent.Builder<UserAudience, ProfileAudience> ofAnyIdentifierBoth(String name) {
|
||||
return of(name, true, true, PlayerType.ALL_PLAYERS);
|
||||
}
|
||||
|
||||
public static CommandComponent.Builder<UserAudience, ProfileAudience> ofAnyUsernameBoth(String name) {
|
||||
return of(name, false, true, PlayerType.ALL_PLAYERS);
|
||||
}
|
||||
|
||||
private static CommandComponent.Builder<UserAudience, ProfileAudience> of(String name, boolean allowUuid, boolean allowOffline, PlayerType limitTo) {
|
||||
private static CommandComponent.Builder<UserAudience, ProfileAudience> of(
|
||||
String name,
|
||||
boolean allowUuid,
|
||||
boolean allowOffline,
|
||||
PlayerType limitTo) {
|
||||
return CommandComponent.<UserAudience, ProfileAudience>builder()
|
||||
.name(name)
|
||||
.parser(quotedStringParser().flatMapSuccess(ProfileAudience.class, (context, input) -> {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.geysermc.floodgate.core.http;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.http.minecraft.MinecraftClient;
|
||||
import org.geysermc.floodgate.core.http.minecraft.ProfileResult;
|
||||
import org.geysermc.floodgate.core.http.xbox.XboxClient;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
|
||||
@Singleton
|
||||
public final class ProfileFetcher {
|
||||
@Inject MinecraftClient minecraftClient;
|
||||
@Inject XboxClient xboxClient;
|
||||
|
||||
public CompletableFuture<@Nullable ProfileAudience> fetchUniqueIdFor(String username) {
|
||||
return minecraftClient.profileByName(username).thenApply(this::convert);
|
||||
}
|
||||
|
||||
public CompletableFuture<@Nullable ProfileAudience> fetchUsernameFor(UUID uniqueId) {
|
||||
return minecraftClient.profileByUniqueId(uniqueId).thenApply(this::convert);
|
||||
}
|
||||
|
||||
public CompletableFuture<@Nullable ProfileAudience> fetchXuidFor(String gamertag) {
|
||||
return xboxClient.xuidByGamertag(gamertag).thenApply(result -> {
|
||||
var xuid = result.xuid();
|
||||
if (xuid == null) {
|
||||
return null;
|
||||
}
|
||||
return new ProfileAudience(Utils.getJavaUuid(xuid), gamertag);
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<@Nullable ProfileAudience> fetchGamertagFor(long xuid) {
|
||||
return xboxClient.gamertagByXuid(xuid).thenApply(result -> {
|
||||
var gamertag = result.gamertag();
|
||||
if (gamertag == null) {
|
||||
return null;
|
||||
}
|
||||
return new ProfileAudience(Utils.getJavaUuid(xuid), gamertag);
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<@Nullable ProfileAudience> fetchGamertagFor(UUID xuid) {
|
||||
return fetchGamertagFor(xuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
private @Nullable ProfileAudience convert(@Nullable ProfileResult result) {
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
return new ProfileAudience(Utils.fromShortUniqueId(result.id()), result.name());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.geysermc.floodgate.core.http.minecraft;
|
||||
|
||||
import static io.micronaut.http.HttpHeaders.ACCEPT;
|
||||
import static io.micronaut.http.HttpHeaders.USER_AGENT;
|
||||
|
||||
import io.micronaut.http.annotation.Get;
|
||||
import io.micronaut.http.annotation.Header;
|
||||
import io.micronaut.http.client.annotation.Client;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@Client("https://api.minecraftservices.com/minecraft")
|
||||
@Header(name = USER_AGENT, value = "${http.userAgent}")
|
||||
@Header(name = ACCEPT, value = "application/json")
|
||||
public interface MinecraftClient {
|
||||
@Get("/profile/lookup/name/{name}")
|
||||
CompletableFuture<@Nullable ProfileResult> profileByName(@NonNull String name);
|
||||
|
||||
@Get("/profile/lookup/{uuid}")
|
||||
CompletableFuture<@Nullable ProfileResult> profileByUniqueId(@NonNull UUID uuid);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.geysermc.floodgate.core.http.minecraft;
|
||||
|
||||
import io.micronaut.serde.annotation.Serdeable;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
@Serdeable
|
||||
public record ProfileResult(@NonNull String id, @NonNull String name) {
|
||||
}
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
package org.geysermc.floodgate.core.http.xbox;
|
||||
|
||||
import io.micronaut.serde.annotation.Serdeable;
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
@Serdeable
|
||||
public record GetGamertagResult(@Nullable String gamertag) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
package org.geysermc.floodgate.core.http.xbox;
|
||||
|
||||
import io.micronaut.serde.annotation.Serdeable;
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
@Serdeable
|
||||
public record GetXuidResult(@Nullable Long xuid) {
|
||||
}
|
||||
|
||||
@@ -97,7 +97,13 @@ public abstract class CommonPlayerLink {
|
||||
|
||||
public abstract CompletableFuture<Void> invalidateLinkRequest(@NonNull LinkRequest request);
|
||||
|
||||
public boolean isActive() {
|
||||
return enabled && allowLinking;
|
||||
public PlayerLinkState state() {
|
||||
return new PlayerLinkState(enabled && allowLinking);
|
||||
}
|
||||
|
||||
public record PlayerLinkState(boolean localLinkingActive, boolean globalLinkingEnabled) {
|
||||
public PlayerLinkState(boolean localLinkingActive) {
|
||||
this(localLinkingActive, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.geysermc.floodgate.core.database.entity.LinkRequest;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
import org.geysermc.floodgate.core.http.link.GlobalLinkClient;
|
||||
|
||||
@Requires(property = "config.playerLink.enabled", value = "true")
|
||||
@Requires(property = "config.playerLink.enableGlobalLinking", value = "true")
|
||||
@Primary
|
||||
@Singleton
|
||||
@@ -151,8 +152,8 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return database != null && database.isActive();
|
||||
public PlayerLinkState state() {
|
||||
return new PlayerLinkState(database != null && database.state().localLinkingActive(), true);
|
||||
}
|
||||
|
||||
private <U> CompletableFuture<U> failedFuture() {
|
||||
|
||||
@@ -27,6 +27,7 @@ package org.geysermc.floodgate.core.platform.command;
|
||||
|
||||
import static org.geysermc.floodgate.core.platform.util.PlayerType.ALL_PLAYERS;
|
||||
import static org.geysermc.floodgate.core.platform.util.PlayerType.ONLY_BEDROCK;
|
||||
import static org.geysermc.floodgate.core.platform.util.PlayerType.ONLY_JAVA;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -130,12 +131,18 @@ public abstract class CommandUtil {
|
||||
}
|
||||
|
||||
protected Object applyPlayerTypeFilter(Object player, PlayerType filter, Object fallback) {
|
||||
if (player == null) {
|
||||
return fallback;
|
||||
}
|
||||
if (filter == ALL_PLAYERS || player instanceof String || player instanceof UUID) {
|
||||
return player;
|
||||
}
|
||||
return (filter == ONLY_BEDROCK) == api.isBedrockPlayer(getUuidFromSource(player))
|
||||
? player
|
||||
: fallback;
|
||||
if (filter == ONLY_BEDROCK || filter == ONLY_JAVA) {
|
||||
if (api.isBedrockPlayer(getUuidFromSource(player)) == (filter == ONLY_BEDROCK)) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,22 +154,6 @@ public abstract class CommandUtil {
|
||||
*/
|
||||
public abstract boolean hasPermission(Object player, String permission);
|
||||
|
||||
/**
|
||||
* Get all online players with the given permission.
|
||||
*
|
||||
* @param permission the permission to check
|
||||
* @return a list of online players that have the given permission
|
||||
*/
|
||||
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
|
||||
* on.
|
||||
|
||||
@@ -25,18 +25,58 @@
|
||||
|
||||
package org.geysermc.floodgate.core.platform.command;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.description.Description;
|
||||
|
||||
public abstract class FloodgateSubCommand {
|
||||
public abstract Class<?> parent();
|
||||
private final Class<?> parent;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final Permission permission;
|
||||
private final String[] aliases;
|
||||
|
||||
public abstract String name();
|
||||
protected FloodgateSubCommand(Class<?> parent, String name, String description, Permission permission, String... aliases) {
|
||||
this.parent = Objects.requireNonNull(parent);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.description = Objects.requireNonNull(description);
|
||||
this.permission = permission;
|
||||
this.aliases = Objects.requireNonNull(aliases);
|
||||
}
|
||||
|
||||
public abstract String description();
|
||||
protected FloodgateSubCommand(Class<?> parent, String name, String description, String... aliases) {
|
||||
this(parent, name, description, null, aliases);
|
||||
}
|
||||
|
||||
public abstract Permission permission();
|
||||
public Command.Builder<UserAudience> onBuild(Command.Builder<UserAudience> commandBuilder) {
|
||||
var builder = commandBuilder;
|
||||
if (permission != null) {
|
||||
builder = builder.permission(permission.get());
|
||||
}
|
||||
return builder.literal(name.toLowerCase(Locale.ROOT), Description.of(description), aliases)
|
||||
.handler(this::execute);
|
||||
}
|
||||
|
||||
public abstract void execute(CommandContext<UserAudience> context);
|
||||
|
||||
public Class<?> parent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public @Nullable Permission permission() {
|
||||
return permission;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,66 @@
|
||||
|
||||
package org.geysermc.floodgate.core.platform.command;
|
||||
|
||||
import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR;
|
||||
import static org.incendo.cloud.description.Description.description;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.connection.audience.UserAudience;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
public abstract class SubCommands implements FloodgateCommand {
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final Permission permission;
|
||||
|
||||
public abstract class SubCommands {
|
||||
@Inject Set<FloodgateSubCommand> subCommands;
|
||||
|
||||
protected SubCommands(String name, String description, Permission permission) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
|
||||
var builder = commandManager
|
||||
.commandBuilder(name, description(description))
|
||||
.senderType(UserAudience.class)
|
||||
.permission(permission.get())
|
||||
.handler(this::execute);
|
||||
|
||||
for (FloodgateSubCommand command : subCommands) {
|
||||
commandManager.command(command.onBuild(builder));
|
||||
}
|
||||
|
||||
// also register /floodgate itself
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n");
|
||||
|
||||
for (FloodgateSubCommand subCommand : subCommands) {
|
||||
var permission = subCommand.permission();
|
||||
if (permission == null || context.sender().hasPermission(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.sender().sendMessage(helpMessage.toString());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void setup() {
|
||||
subCommands.removeIf(subCommand -> !subCommand.parent().isAssignableFrom(this.getClass()));
|
||||
}
|
||||
|
||||
protected Set<FloodgateSubCommand> subCommands() {
|
||||
return subCommands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
@@ -43,7 +44,8 @@ import org.geysermc.floodgate.core.crypto.RandomUtils;
|
||||
|
||||
public class Utils {
|
||||
private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^\\w{0,16}$");
|
||||
private static final Random random = RandomUtils.secureRandom();
|
||||
private static final Random RANDOM = RandomUtils.secureRandom();
|
||||
private static final BigInteger MAX_LONG_VALUE = BigInteger.ONE.shiftLeft(64);
|
||||
|
||||
/**
|
||||
* This method is used in Addons.<br> Most addons can be removed once the player associated to
|
||||
@@ -92,6 +94,11 @@ public class Utils {
|
||||
return getJavaUuid(Long.parseLong(xuid));
|
||||
}
|
||||
|
||||
public static UUID fromShortUniqueId(String uuid) {
|
||||
var bigInt = new BigInteger(uuid, 16);
|
||||
return new UUID(bigInt.shiftRight(64).longValue(), bigInt.xor(MAX_LONG_VALUE).longValue());
|
||||
}
|
||||
|
||||
public static boolean isUniquePrefix(String prefix) {
|
||||
return !NON_UNIQUE_PREFIX.matcher(prefix).matches();
|
||||
}
|
||||
@@ -105,7 +112,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static char generateCodeChar() {
|
||||
var codeChar = random.nextInt() % (10 + 26);
|
||||
var codeChar = RANDOM.nextInt() % (10 + 26);
|
||||
if (codeChar < 10) {
|
||||
return (char) ('0' + codeChar);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user