mirror of https://github.com/GeyserMC/Geyser.git
Compare commits
2 Commits
d1af3ff7d6
...
bdc2d07718
Author | SHA1 | Date |
---|---|---|
Konicai | bdc2d07718 | |
Konicai | bc1a740e12 |
|
@ -26,7 +26,6 @@
|
||||||
package org.geysermc.geyser.command;
|
package org.geysermc.geyser.command;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.geysermc.geyser.Constants;
|
import org.geysermc.geyser.Constants;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
@ -37,6 +36,7 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
|
||||||
import org.geysermc.geyser.api.extension.Extension;
|
import org.geysermc.geyser.api.extension.Extension;
|
||||||
import org.geysermc.geyser.api.util.PlatformType;
|
import org.geysermc.geyser.api.util.PlatformType;
|
||||||
import org.geysermc.geyser.api.util.TriState;
|
import org.geysermc.geyser.api.util.TriState;
|
||||||
|
import org.geysermc.geyser.command.GeyserPermission.Result;
|
||||||
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
|
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
|
||||||
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
|
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
|
||||||
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
|
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
|
||||||
|
@ -63,14 +63,11 @@ import org.incendo.cloud.exception.InvalidCommandSenderException;
|
||||||
import org.incendo.cloud.exception.InvalidSyntaxException;
|
import org.incendo.cloud.exception.InvalidSyntaxException;
|
||||||
import org.incendo.cloud.exception.NoPermissionException;
|
import org.incendo.cloud.exception.NoPermissionException;
|
||||||
import org.incendo.cloud.exception.NoSuchCommandException;
|
import org.incendo.cloud.exception.NoSuchCommandException;
|
||||||
import org.incendo.cloud.exception.handling.ExceptionContext;
|
|
||||||
import org.incendo.cloud.exception.handling.ExceptionController;
|
import org.incendo.cloud.exception.handling.ExceptionController;
|
||||||
import org.incendo.cloud.exception.handling.ExceptionHandler;
|
|
||||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
@ -106,18 +103,13 @@ public class CommandRegistry {
|
||||||
// Yeet the default exception handlers that the typical cloud implementations provide so that we can perform localization.
|
// Yeet the default exception handlers that the typical cloud implementations provide so that we can perform localization.
|
||||||
// This is kind of meaningless for our Geyser-Standalone implementation since these handlers are the default exception handlers in that case.
|
// This is kind of meaningless for our Geyser-Standalone implementation since these handlers are the default exception handlers in that case.
|
||||||
cloud.exceptionController().clearHandlers();
|
cloud.exceptionController().clearHandlers();
|
||||||
List<GeyserExceptionHandler<?>> exceptionHandlers = List.of(
|
registerExceptionHandler(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax()));
|
||||||
new GeyserExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax())),
|
registerExceptionHandler(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), e.requiredSender()));
|
||||||
new GeyserExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), e.requiredSender())),
|
registerExceptionHandler(NoPermissionException.class, CommandRegistry::handleNoPermission);
|
||||||
new GeyserExceptionHandler<>(NoPermissionException.class, this::handleNoPermission),
|
registerExceptionHandler(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found"));
|
||||||
new GeyserExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")),
|
registerExceptionHandler(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage()));
|
||||||
new GeyserExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())),
|
registerExceptionHandler(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause()));
|
||||||
new GeyserExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())),
|
registerExceptionHandler(Throwable.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause()));
|
||||||
new GeyserExceptionHandler<>(Throwable.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause()))
|
|
||||||
);
|
|
||||||
for (GeyserExceptionHandler<?> handler : exceptionHandlers) {
|
|
||||||
handler.register(cloud);
|
|
||||||
}
|
|
||||||
|
|
||||||
// begin command registration
|
// begin command registration
|
||||||
registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", "geyser.command", this.commands));
|
registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", "geyser.command", this.commands));
|
||||||
|
@ -185,6 +177,13 @@ public class CommandRegistry {
|
||||||
return Collections.unmodifiableMap(this.commands);
|
return Collections.unmodifiableMap(this.commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <E extends Throwable> void registerExceptionHandler(Class<E> type, BiConsumer<GeyserCommandSource, E> handler) {
|
||||||
|
cloud.exceptionController().registerHandler(type, context -> {
|
||||||
|
Throwable unwrapped = ExceptionController.unwrapCompletionException(context.exception());
|
||||||
|
handler.accept(context.context().sender(), type.cast(unwrapped));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For internal Geyser commands
|
* For internal Geyser commands
|
||||||
*/
|
*/
|
||||||
|
@ -196,10 +195,6 @@ public class CommandRegistry {
|
||||||
register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>()));
|
register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPermission(GeyserCommandSource source, String permission) {
|
|
||||||
return cloud.hasPermission(source, permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void register(GeyserCommand command, Map<String, Command> commands) {
|
private void register(GeyserCommand command, Map<String, Command> commands) {
|
||||||
command.register(cloud);
|
command.register(cloud);
|
||||||
|
|
||||||
|
@ -230,6 +225,11 @@ public class CommandRegistry {
|
||||||
event.register(Constants.SETTINGS_GAMERULES_PERMISSION, TriState.NOT_SET);
|
event.register(Constants.SETTINGS_GAMERULES_PERMISSION, TriState.NOT_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasPermission(GeyserCommandSource source, String permission) {
|
||||||
|
return cloud.hasPermission(source, permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the description of the given command
|
* Returns the description of the given command
|
||||||
*
|
*
|
||||||
|
@ -258,22 +258,21 @@ public class CommandRegistry {
|
||||||
cloud.commandExecutor().executeCommand(source, command);
|
cloud.commandExecutor().executeCommand(source, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) {
|
private static void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) {
|
||||||
// we basically recheck bedrock-only and player-only to see if they were the cause of this
|
// custom handling if the source can't use the command because of additional requirements
|
||||||
if (exception.missingPermission() instanceof GeyserPermission permission) {
|
if (exception.permissionResult() instanceof Result result) {
|
||||||
GeyserPermission.Result result = permission.check(source);
|
if (result.meta() == Result.Meta.NOT_BEDROCK) {
|
||||||
if (result == GeyserPermission.Result.NOT_BEDROCK) {
|
|
||||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale()));
|
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (result == GeyserPermission.Result.NOT_PLAYER) {
|
if (result.meta() == Result.Meta.NOT_PLAYER) {
|
||||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale()));
|
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||||
if (logger.isDebug()) {
|
if (logger.isDebug()) {
|
||||||
logger.debug("Expected a GeyserPermission for %s but instead got %s".formatted(exception.currentChain(), exception.missingPermission()));
|
logger.debug("Expected a GeyserPermission.Result for %s but instead got %s from %s".formatted(exception.currentChain(), exception.permissionResult(), exception.missingPermission()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,25 +280,8 @@ public class CommandRegistry {
|
||||||
source.sendLocaleString("geyser.command.permission_fail");
|
source.sendLocaleString("geyser.command.permission_fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) {
|
private static void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) {
|
||||||
source.sendMessage(MinecraftLocale.getLocaleString("command.failed", source.locale())); // java edition translation key
|
source.sendMessage(MinecraftLocale.getLocaleString("command.failed", source.locale())); // java edition translation key
|
||||||
GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable);
|
GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
private static class GeyserExceptionHandler<E extends Throwable> implements ExceptionHandler<GeyserCommandSource, E> {
|
|
||||||
|
|
||||||
final Class<E> type;
|
|
||||||
final BiConsumer<GeyserCommandSource, E> handler;
|
|
||||||
|
|
||||||
void register(CommandManager<GeyserCommandSource> manager) {
|
|
||||||
manager.exceptionController().registerHandler(type, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(@NonNull ExceptionContext context) throws Throwable {
|
|
||||||
Throwable unwrapped = ExceptionController.unwrapCompletionException(context.exception());
|
|
||||||
handler.accept((GeyserCommandSource) context.context().sender(), type.cast(unwrapped));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,33 +33,48 @@ import org.incendo.cloud.permission.Permission;
|
||||||
import org.incendo.cloud.permission.PermissionResult;
|
import org.incendo.cloud.permission.PermissionResult;
|
||||||
import org.incendo.cloud.permission.PredicatePermission;
|
import org.incendo.cloud.permission.PredicatePermission;
|
||||||
|
|
||||||
|
import static org.geysermc.geyser.command.GeyserPermission.Result.Meta;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class GeyserPermission implements PredicatePermission<GeyserCommandSource> {
|
public class GeyserPermission implements PredicatePermission<GeyserCommandSource> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if this permission requires the command source to be a bedrock player
|
||||||
|
*/
|
||||||
private final boolean bedrockOnly;
|
private final boolean bedrockOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if this permission requires the command source to be any player
|
||||||
|
*/
|
||||||
private final boolean playerOnly;
|
private final boolean playerOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The permission node that the command source must have
|
||||||
|
*/
|
||||||
private final String permission;
|
private final String permission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command manager to delegate permission checks to
|
||||||
|
*/
|
||||||
private final CommandManager<GeyserCommandSource> manager;
|
private final CommandManager<GeyserCommandSource> manager;
|
||||||
|
|
||||||
public Result check(GeyserCommandSource source) {
|
@Override
|
||||||
if (permission.isBlank()) {
|
public @NonNull PermissionResult testPermission(@NonNull GeyserCommandSource source) {
|
||||||
return Result.ALLOWED;
|
|
||||||
}
|
|
||||||
if (bedrockOnly) {
|
if (bedrockOnly) {
|
||||||
if (source.connection() == null) {
|
if (source.connection() == null) {
|
||||||
return Result.NOT_BEDROCK;
|
return new Result(Meta.NOT_BEDROCK);
|
||||||
}
|
}
|
||||||
// connection is present -> it is a player -> playerOnly is irrelevant
|
// connection is present -> it is a player -> playerOnly is irrelevant
|
||||||
} else if (playerOnly) {
|
} else if (playerOnly) {
|
||||||
if (source.isConsole()) {
|
if (source.isConsole()) {
|
||||||
return Result.NOT_PLAYER; // must be a player but is console
|
return new Result(Meta.NOT_PLAYER); // must be a player but is console
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manager.hasPermission(source, permission)) {
|
if (permission.isBlank() || manager.hasPermission(source, permission)) {
|
||||||
return Result.ALLOWED;
|
return new Result(Meta.ALLOWED);
|
||||||
}
|
}
|
||||||
return Result.NO_PERMISSION;
|
return new Result(Meta.NO_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,35 +82,55 @@ public class GeyserPermission implements PredicatePermission<GeyserCommandSource
|
||||||
return CloudKey.cloudKey(permission);
|
return CloudKey.cloudKey(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public @NonNull PermissionResult testPermission(@NonNull GeyserCommandSource sender) {
|
* Basic implementation of cloud's {@link PermissionResult} that delegates to the more informative {@link Meta}.
|
||||||
return check(sender).toPermission(permission);
|
*/
|
||||||
}
|
public final class Result implements PermissionResult {
|
||||||
|
|
||||||
public enum Result {
|
private final Meta meta;
|
||||||
|
|
||||||
|
private Result(Meta meta) {
|
||||||
|
this.meta = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Meta meta() {
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowed() {
|
||||||
|
return meta == Meta.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Permission permission() {
|
||||||
|
return GeyserPermission.this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source must be a bedrock player, but is not.
|
* More detailed explanation of whether the permission check passed.
|
||||||
*/
|
*/
|
||||||
NOT_BEDROCK,
|
public enum Meta {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source must be a player, but is not.
|
* The source must be a bedrock player, but is not.
|
||||||
*/
|
*/
|
||||||
NOT_PLAYER,
|
NOT_BEDROCK,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source does not have a required permission node.
|
* The source must be a player, but is not.
|
||||||
*/
|
*/
|
||||||
NO_PERMISSION,
|
NOT_PLAYER,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source meets all requirements.
|
* The source does not have a required permission node.
|
||||||
*/
|
*/
|
||||||
ALLOWED;
|
NO_PERMISSION,
|
||||||
|
|
||||||
public PermissionResult toPermission(String permission) {
|
/**
|
||||||
return PermissionResult.of(this == ALLOWED, Permission.of(permission));
|
* The source meets all requirements.
|
||||||
|
*/
|
||||||
|
ALLOWED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue