Stop Bedrock commands from appearing in Java players' brig suggestions

move bedrock-only and player-only checks to the permission check, instead of being a postprocessor
This commit is contained in:
Konicai 2023-09-08 01:45:06 -04:00
parent 55e50fecb9
commit 3ec2125d3f
No known key found for this signature in database
GPG Key ID: 710D09287708C823
4 changed files with 45 additions and 63 deletions

View File

@ -26,6 +26,7 @@
package org.geysermc.geyser.command;
import cloud.commandframework.CommandManager;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.exceptions.ArgumentParseException;
import cloud.commandframework.exceptions.CommandExecutionException;
import cloud.commandframework.exceptions.InvalidCommandSenderException;
@ -33,6 +34,7 @@ import cloud.commandframework.exceptions.InvalidSyntaxException;
import cloud.commandframework.exceptions.NoPermissionException;
import cloud.commandframework.exceptions.NoSuchCommandException;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.meta.CommandMeta;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -59,6 +61,7 @@ import org.geysermc.geyser.command.defaults.VersionCommand;
import org.geysermc.geyser.event.GeyserEventRegistrar;
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.jetbrains.annotations.NotNull;
@ -66,6 +69,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.function.BiConsumer;
@ -86,7 +90,7 @@ public class CommandRegistry {
private final List<ExceptionHandler<?>> exceptionHandlers = List.of(
new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.getCorrectSyntax())),
new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.getCommandSender().getClass().getSimpleName(), e.getRequiredSender().getSimpleName())),
new ExceptionHandler<>(NoPermissionException.class, (src, e) -> src.sendLocaleString("geyser.command.permission_fail")),
new ExceptionHandler<>(NoPermissionException.class, this::handleNoPermission),
new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")),
new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())),
new ExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause()))
@ -96,8 +100,6 @@ public class CommandRegistry {
this.geyser = geyser;
this.cloud = cloud;
// Restricts command source types from executing commands they don't have access to
cloud.registerCommandPostProcessor(new SourceTypeProcessor());
// Override 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.
for (ExceptionHandler<?> handler : exceptionHandlers) {
@ -242,6 +244,30 @@ public class CommandRegistry {
handleUnexpectedThrowable(source, throwable);
}
private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) {
// we basically recheck bedrock-only and player-only to see if they were the cause of this
// find the Command and its Meta that the source tried executing
List<CommandArgument<?, ?>> argumentChain = exception.getCurrentChain();
CommandArgument<?, ?> argument = argumentChain.get(argumentChain.size() - 1);
CommandMeta meta = Objects.requireNonNull(argument.getOwningCommand()).getCommandMeta();
// See GeyserCommand#baseBuilder()
if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) {
if (source.connection().isEmpty()) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale()));
return;
}
} else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) {
if (source.isConsole()) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale()));
return;
}
}
source.sendLocaleString("geyser.command.permission_fail");
}
private void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) {
source.sendLocaleString("command.failed"); // java edition translation key
GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable);

View File

@ -162,13 +162,26 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C
/**
* Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it.
* The Applicable from {@link #meta()} is also applied to the builder.
* A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isExecutableOnConsole()}
* is applied. The Applicable from {@link #meta()} is also applied to the builder.
*/
@Contract(value = "_ -> new", pure = true)
public final Command.Builder<GeyserCommandSource> baseBuilder(CommandManager<GeyserCommandSource> manager) {
return manager.commandBuilder(rootCommand())
.literal(name, aliases.toArray(new String[0]))
.permission(permission)
.permission(source -> {
if (bedrockOnly) {
if (source.connection().isEmpty()) {
return false;
}
// connection is present -> it is a player -> executableOnConsole is irrelevant
} else if (!executableOnConsole) {
if (source.isConsole()) {
return false; // not executable on console but is console
}
}
return manager.hasPermission(source, permission);
})
.apply(meta());
}

View File

@ -1,58 +0,0 @@
/*
* Copyright (c) 2019-2023 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/Geyser
*/
package org.geysermc.geyser.command;
import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext;
import cloud.commandframework.execution.postprocessor.CommandPostprocessor;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.services.types.ConsumerService;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
public class SourceTypeProcessor implements CommandPostprocessor<GeyserCommandSource> {
@Override
public void accept(@NonNull CommandPostprocessingContext<GeyserCommandSource> processContext) {
CommandMeta meta = processContext.getCommand().getCommandMeta();
GeyserCommandSource source = processContext.getCommandContext().getSender();
if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) {
if (source.connection().isEmpty()) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale()));
ConsumerService.interrupt();
}
} else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) {
// it should be fine to use else-if here, because if the command is bedrock-only,
// performing the bedrock player check is also inherently a player check
if (source.playerUuid().isEmpty()) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale()));
ConsumerService.interrupt();
}
}
}
}

View File

@ -53,6 +53,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
// if cloud gives a NoSuchCommandException? might be more accurate.
CommandRegistry registry = GeyserImpl.getInstance().commandRegistry();
if (registry.cloud().rootCommands().contains(root)) {
// todo: cloud might not like the trailing whitespace either
registry.runCommand(session, strippedCommand);
return; // don't pass the command to the java server
}