mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2025-02-17 01:51:32 +01:00
Merge branch 'execute-command'
This commit is contained in:
commit
2e48e91a8f
@ -27,7 +27,7 @@ import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.*;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -147,11 +147,11 @@ public interface DiscordAPI {
|
||||
* Registers a Discord command.
|
||||
* @param command the command to register
|
||||
*/
|
||||
Command.RegistrationResult registerCommand(Command command);
|
||||
DiscordCommand.RegistrationResult registerCommand(DiscordCommand command);
|
||||
|
||||
/**
|
||||
* Unregisters a Discord command.
|
||||
* @param command the command to unregister
|
||||
*/
|
||||
void unregisterCommand(Command command);
|
||||
void unregisterCommand(DiscordCommand command);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* A Discord command.
|
||||
*/
|
||||
public class Command implements JDAEntity<CommandData> {
|
||||
public class DiscordCommand implements JDAEntity<CommandData> {
|
||||
|
||||
private static final String CHAT_INPUT_NAME_REGEX = "(?U)[\\w-]{1,32}";
|
||||
public static final Pattern CHAT_INPUT_NAME_PATTERN = Pattern.compile(CHAT_INPUT_NAME_REGEX);
|
||||
@ -100,27 +100,27 @@ public class Command implements JDAEntity<CommandData> {
|
||||
private final Map<Locale, String> nameTranslations;
|
||||
private final Map<Locale, String> descriptionTranslations;
|
||||
private final List<SubCommandGroup> subCommandGroups;
|
||||
private final List<Command> subCommands;
|
||||
private final List<DiscordCommand> subCommands;
|
||||
private final List<CommandOption> options;
|
||||
private final Long guildId;
|
||||
private final boolean guildOnly;
|
||||
private final DefaultPermission defaultPermission;
|
||||
private final Consumer<? extends AbstractCommandInteractionEvent<?>> eventHandler;
|
||||
private final Consumer<DiscordCommandAutoCompleteInteractionEvent> autoCompleteHandler;
|
||||
private final AutoCompleteHandler autoCompleteHandler;
|
||||
|
||||
private Command(
|
||||
private DiscordCommand(
|
||||
ComponentIdentifier id,
|
||||
CommandType type,
|
||||
Map<Locale, String> nameTranslations,
|
||||
Map<Locale, String> descriptionTranslations,
|
||||
List<SubCommandGroup> subCommandGroups,
|
||||
List<Command> subCommands,
|
||||
List<DiscordCommand> subCommands,
|
||||
List<CommandOption> options,
|
||||
Long guildId,
|
||||
boolean guildOnly,
|
||||
DefaultPermission defaultPermission,
|
||||
Consumer<? extends AbstractCommandInteractionEvent<?>> eventHandler,
|
||||
Consumer<DiscordCommandAutoCompleteInteractionEvent> autoCompleteHandler
|
||||
AutoCompleteHandler autoCompleteHandler
|
||||
) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
@ -181,7 +181,7 @@ public class Command implements JDAEntity<CommandData> {
|
||||
|
||||
@NotNull
|
||||
@Unmodifiable
|
||||
public List<Command> getSubCommands() {
|
||||
public List<DiscordCommand> getSubCommands() {
|
||||
return Collections.unmodifiableList(subCommands);
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ public class Command implements JDAEntity<CommandData> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Consumer<DiscordCommandAutoCompleteInteractionEvent> getAutoCompleteHandler() {
|
||||
public AutoCompleteHandler getAutoCompleteHandler() {
|
||||
return autoCompleteHandler;
|
||||
}
|
||||
|
||||
@ -228,7 +228,8 @@ public class Command implements JDAEntity<CommandData> {
|
||||
case CHAT_INPUT:
|
||||
SlashCommandData slashCommandData = Commands.slash(getName(), Objects.requireNonNull(getDescription()));
|
||||
slashCommandData.addSubcommandGroups(subCommandGroups.stream().map(JDAEntity::asJDA).toArray(SubcommandGroupData[]::new));
|
||||
slashCommandData.addSubcommands(subCommands.stream().map(Command::asJDASubcommand).toArray(SubcommandData[]::new));
|
||||
slashCommandData.addSubcommands(subCommands.stream().map(
|
||||
DiscordCommand::asJDASubcommand).toArray(SubcommandData[]::new));
|
||||
slashCommandData.addOptions(options.stream().map(JDAEntity::asJDA).toArray(OptionData[]::new));
|
||||
commandData = slashCommandData;
|
||||
break;
|
||||
@ -252,9 +253,9 @@ public class Command implements JDAEntity<CommandData> {
|
||||
|
||||
private final Map<Locale, String> descriptionTranslations = new LinkedHashMap<>();
|
||||
private final List<SubCommandGroup> subCommandGroups = new ArrayList<>();
|
||||
private final List<Command> subCommands = new ArrayList<>();
|
||||
private final List<DiscordCommand> subCommands = new ArrayList<>();
|
||||
private final List<CommandOption> options = new ArrayList<>();
|
||||
private Consumer<DiscordCommandAutoCompleteInteractionEvent> autoCompleteHandler;
|
||||
private AutoCompleteHandler autoCompleteHandler;
|
||||
|
||||
private ChatInputBuilder(ComponentIdentifier id, String name, String description) {
|
||||
super(id, CommandType.CHAT_INPUT, name);
|
||||
@ -296,7 +297,7 @@ public class Command implements JDAEntity<CommandData> {
|
||||
* @return this builder, useful for chaining
|
||||
*/
|
||||
@NotNull
|
||||
public ChatInputBuilder addSubCommand(@NotNull Command command) {
|
||||
public ChatInputBuilder addSubCommand(@NotNull DiscordCommand command) {
|
||||
this.subCommands.add(command);
|
||||
return this;
|
||||
}
|
||||
@ -319,14 +320,14 @@ public class Command implements JDAEntity<CommandData> {
|
||||
* @return this builder, useful for chaining
|
||||
*/
|
||||
@NotNull
|
||||
public ChatInputBuilder setAutoCompleteHandler(Consumer<DiscordCommandAutoCompleteInteractionEvent> autoCompleteHandler) {
|
||||
public ChatInputBuilder setAutoCompleteHandler(AutoCompleteHandler autoCompleteHandler) {
|
||||
this.autoCompleteHandler = autoCompleteHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command build() {
|
||||
return new Command(
|
||||
public DiscordCommand build() {
|
||||
return new DiscordCommand(
|
||||
id,
|
||||
type,
|
||||
nameTranslations,
|
||||
@ -343,6 +344,13 @@ public class Command implements JDAEntity<CommandData> {
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AutoCompleteHandler {
|
||||
|
||||
void autoComplete(DiscordCommandAutoCompleteInteractionEvent event);
|
||||
|
||||
}
|
||||
|
||||
public static class Builder<E extends AbstractCommandInteractionEvent<?>> {
|
||||
|
||||
protected final ComponentIdentifier id;
|
||||
@ -414,8 +422,8 @@ public class Command implements JDAEntity<CommandData> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Command build() {
|
||||
return new Command(
|
||||
public DiscordCommand build() {
|
||||
return new DiscordCommand(
|
||||
id,
|
||||
type,
|
||||
nameTranslations,
|
@ -43,15 +43,15 @@ public class SubCommandGroup implements JDAEntity<SubcommandGroupData> {
|
||||
* @return a new sub command group
|
||||
*/
|
||||
@NotNull
|
||||
public static SubCommandGroup of(@NotNull String name, @NotNull String description, @NotNull Command... commands) {
|
||||
public static SubCommandGroup of(@NotNull String name, @NotNull String description, @NotNull DiscordCommand... commands) {
|
||||
return new SubCommandGroup(name, description, Arrays.asList(commands));
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final List<Command> commands;
|
||||
private final List<DiscordCommand> commands;
|
||||
|
||||
private SubCommandGroup(String name, String description, List<Command> commands) {
|
||||
private SubCommandGroup(String name, String description, List<DiscordCommand> commands) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.commands = commands;
|
||||
@ -69,13 +69,13 @@ public class SubCommandGroup implements JDAEntity<SubcommandGroupData> {
|
||||
|
||||
@NotNull
|
||||
@Unmodifiable
|
||||
public List<Command> getCommands() {
|
||||
public List<DiscordCommand> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubcommandGroupData asJDA() {
|
||||
return new SubcommandGroupData(name, description)
|
||||
.addSubcommands(commands.stream().map(Command::asJDASubcommand).toArray(SubcommandData[]::new));
|
||||
.addSubcommands(commands.stream().map(DiscordCommand::asJDASubcommand).toArray(SubcommandData[]::new));
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
package com.discordsrv.api.discord.events.interaction.command;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.event.events.Event;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
@ -34,24 +34,24 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An event for registering {@link com.discordsrv.api.discord.entity.interaction.command.Command}s,
|
||||
* an alternative to {@link com.discordsrv.api.discord.DiscordAPI#registerCommand(Command)}.
|
||||
* An event for registering {@link DiscordCommand}s,
|
||||
* an alternative to {@link com.discordsrv.api.discord.DiscordAPI#registerCommand(DiscordCommand)}.
|
||||
*/
|
||||
public class CommandRegisterEvent implements Event {
|
||||
|
||||
private final List<Command> commands = new ArrayList<>();
|
||||
private final List<DiscordCommand> commands = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add events to be registered.
|
||||
* @param commands the commands to be registered, use of the same command instances is recommended
|
||||
*/
|
||||
public void registerCommands(@NotNull Command... commands) {
|
||||
public void registerCommands(@NotNull DiscordCommand... commands) {
|
||||
this.commands.addAll(Arrays.asList(commands));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Unmodifiable
|
||||
public List<Command> getCommands() {
|
||||
public List<DiscordCommand> getCommands() {
|
||||
return Collections.unmodifiableList(commands);
|
||||
}
|
||||
}
|
||||
|
@ -47,16 +47,16 @@ public class DiscordCommandAutoCompleteInteractionEvent extends AbstractInteract
|
||||
super(jdaEvent, identifier, user, member, channel);
|
||||
}
|
||||
|
||||
public void addChoice(String key, String value) {
|
||||
this.choices.put(key, value);
|
||||
public void addChoice(String name, String value) {
|
||||
this.choices.put(name, value);
|
||||
}
|
||||
|
||||
public void addChoice(String key, double value) {
|
||||
this.choices.put(key, value);
|
||||
public void addChoice(String name, double value) {
|
||||
this.choices.put(name, value);
|
||||
}
|
||||
|
||||
public void addChoice(String key, long value) {
|
||||
this.choices.put(key, value);
|
||||
public void addChoice(String name, long value) {
|
||||
this.choices.put(name, value);
|
||||
}
|
||||
|
||||
public Map<String, Object> getChoices() {
|
||||
|
@ -0,0 +1,24 @@
|
||||
package com.discordsrv.bukkit;
|
||||
|
||||
import org.bukkit.Server;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class PaperCommandMap {
|
||||
|
||||
public static final boolean IS_AVAILABLE;
|
||||
|
||||
static {
|
||||
boolean is = false;
|
||||
try {
|
||||
Class<?> serverClass = Server.class;
|
||||
serverClass.getDeclaredMethod("getCommandMap");
|
||||
is = true;
|
||||
} catch (Throwable ignored) {}
|
||||
IS_AVAILABLE = is;
|
||||
}
|
||||
|
||||
public static Set<String> getKnownCommands(Server server) {
|
||||
return server.getCommandMap().getKnownCommands().keySet();
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@
|
||||
package com.discordsrv.bukkit.component;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.ComponentFactory;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
|
||||
@ -60,21 +59,14 @@ public class PaperComponentHandle<T> {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public MinecraftComponent getComponent(DiscordSRV discordSRV, T target) {
|
||||
public MinecraftComponent getComponent(T target) {
|
||||
if (handle != null) {
|
||||
Object unrelocated = null;
|
||||
try {
|
||||
unrelocated = handle.invoke(target);
|
||||
} catch (Throwable ignored) {}
|
||||
if (unrelocated != null) {
|
||||
MinecraftComponent component = discordSRV.componentFactory().empty();
|
||||
MinecraftComponent.Adapter<Object> adapter = component.unrelocatedAdapter();
|
||||
if (adapter == null) {
|
||||
throw new IllegalStateException("Unrelocated adventure unavailable");
|
||||
}
|
||||
|
||||
adapter.setComponent(unrelocated);
|
||||
return component;
|
||||
return ComponentUtil.fromUnrelocated(unrelocated);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,8 @@ import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("deprecation") // Paper
|
||||
@Proxy(value = CommandSender.class, className = "BukkitCommandExecutorProxy")
|
||||
public abstract class BukkitCommandExecutorProxyTemplate implements CommandSender {
|
||||
@Proxy(value = CommandSender.class, className = "BukkitCommandFeedbackExecutorProxy")
|
||||
public abstract class BukkitCommandFeedbackExecutorProxyTemplate implements CommandSender {
|
||||
|
||||
@Original
|
||||
private final CommandSender commandSender;
|
||||
@ -40,7 +40,7 @@ public abstract class BukkitCommandExecutorProxyTemplate implements CommandSende
|
||||
|
||||
private Spigot spigot;
|
||||
|
||||
public BukkitCommandExecutorProxyTemplate(CommandSender commandSender, Consumer<Component> componentConsumer) {
|
||||
public BukkitCommandFeedbackExecutorProxyTemplate(CommandSender commandSender, Consumer<Component> componentConsumer) {
|
||||
this.commandSender = commandSender;
|
||||
this.componentConsumer = componentConsumer;
|
||||
try {
|
@ -0,0 +1,31 @@
|
||||
package com.discordsrv.bukkit.console.executor;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.unrelocate.net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PaperCommandFeedbackExecutor implements Consumer<Component> {
|
||||
|
||||
private final Consumer<MinecraftComponent> componentConsumer;
|
||||
private final CommandSender sender;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public PaperCommandFeedbackExecutor(Server server, Consumer<MinecraftComponent> componentConsumer) {
|
||||
this.componentConsumer = componentConsumer;
|
||||
this.sender = server.createCommandSender((Consumer<? super net.kyori.adventure.text.Component>) (Object) this);
|
||||
}
|
||||
|
||||
public CommandSender sender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Component component) {
|
||||
MinecraftComponent minecraftComponent = ComponentUtil.fromUnrelocated(component);
|
||||
componentConsumer.accept(minecraftComponent);
|
||||
}
|
||||
}
|
@ -45,11 +45,8 @@ public class PaperModernAdvancementListener extends AbstractBukkitAwardListener
|
||||
);
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
public PaperModernAdvancementListener(DiscordSRV discordSRV, IBukkitAwardForwarder forwarder) {
|
||||
super(discordSRV, forwarder);
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@ -62,8 +59,8 @@ public class PaperModernAdvancementListener extends AbstractBukkitAwardListener
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftComponent message = MESSAGE_HANDLE.getComponent(discordSRV, event);
|
||||
MinecraftComponent displayName = DISPLAY_NAME_HANDLE.getComponent(discordSRV, advancement);
|
||||
MinecraftComponent message = MESSAGE_HANDLE.getComponent(event);
|
||||
MinecraftComponent displayName = DISPLAY_NAME_HANDLE.getComponent(advancement);
|
||||
forwarder.publishEvent(event, event.getPlayer(), displayName, message, false);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ package com.discordsrv.bukkit.listener.chat;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.bukkit.component.PaperComponentHandle;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@ -38,17 +37,15 @@ public class PaperChatListener implements Listener {
|
||||
);
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final IBukkitChatForwarder listener;
|
||||
|
||||
public PaperChatListener(DiscordSRV discordSRV, IBukkitChatForwarder listener) {
|
||||
this.discordSRV = discordSRV;
|
||||
public PaperChatListener(IBukkitChatForwarder listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onAsyncChat(AsyncChatEvent event) {
|
||||
MinecraftComponent component = COMPONENT_HANDLE.getComponent(discordSRV, event);
|
||||
MinecraftComponent component = COMPONENT_HANDLE.getComponent(event);
|
||||
listener.publishEvent(event, event.getPlayer(), component, event.isCancelled());
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
package com.discordsrv.bukkit;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.bukkit.command.game.BukkitGameCommandExecutionHelper;
|
||||
import com.discordsrv.bukkit.command.game.handler.AbstractBukkitCommandHandler;
|
||||
import com.discordsrv.bukkit.component.translation.BukkitTranslationLoader;
|
||||
import com.discordsrv.bukkit.config.connection.BukkitConnectionConfig;
|
||||
@ -66,6 +67,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
private final BukkitPluginManager pluginManager;
|
||||
private AbstractBukkitCommandHandler commandHandler;
|
||||
private final BukkitRequiredLinkingListener requiredLinkingListener;
|
||||
private final BukkitGameCommandExecutionHelper autoCompleteHelper;
|
||||
|
||||
private final BukkitConnectionConfigManager connectionConfigManager;
|
||||
private final BukkitConfigManager configManager;
|
||||
@ -94,6 +96,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
load();
|
||||
|
||||
this.requiredLinkingListener = new BukkitRequiredLinkingListener(this);
|
||||
this.autoCompleteHelper = new BukkitGameCommandExecutionHelper(this);
|
||||
}
|
||||
|
||||
public JavaPlugin plugin() {
|
||||
@ -225,4 +228,9 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
requiredLinkingListener.disable();
|
||||
audiences.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitGameCommandExecutionHelper executeHelper() {
|
||||
return autoCompleteHelper;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,111 @@
|
||||
package com.discordsrv.bukkit.command.game;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.bukkit.PaperCommandMap;
|
||||
import com.discordsrv.common.command.game.GameCommandExecutionHelper;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class BukkitGameCommandExecutionHelper implements GameCommandExecutionHelper {
|
||||
|
||||
private final BukkitDiscordSRV discordSRV;
|
||||
|
||||
public BukkitGameCommandExecutionHelper(BukkitDiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<String>> suggestCommands(List<String> parts) {
|
||||
String commandName = !parts.isEmpty() ? parts.remove(0) : null;
|
||||
Command command = commandName != null ? discordSRV.server().getPluginCommand(commandName) : null;
|
||||
if (command == null) {
|
||||
if (parts.size() > 1) {
|
||||
// Command is not known but there are arguments, nothing to auto complete...
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
} else {
|
||||
// List out commands
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
|
||||
if (PaperCommandMap.IS_AVAILABLE) {
|
||||
// If Paper's CommandMap is available we can list out 'root' commands
|
||||
CompletableFuture<List<String>> future = new CompletableFuture<>();
|
||||
discordSRV.scheduler().runOnMainThread(discordSRV.server().getConsoleSender(), () -> {
|
||||
try {
|
||||
for (String cmd : PaperCommandMap.getKnownCommands(discordSRV.server())) {
|
||||
if (commandName == null || cmd.startsWith(commandName)) {
|
||||
suggestions.add(cmd);
|
||||
}
|
||||
}
|
||||
future.complete(suggestions);
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the arguments minus the last one (if any)
|
||||
String prefix = String.join(" ", parts.subList(0, parts.size() - (!parts.isEmpty() ? 1 : 0)));
|
||||
if (!prefix.isEmpty()) {
|
||||
prefix = prefix + " ";
|
||||
}
|
||||
|
||||
CompletableFuture<List<String>> future = new CompletableFuture<>();
|
||||
String finalPrefix = prefix;
|
||||
|
||||
CommandSender commandSender = discordSRV.server().getConsoleSender();
|
||||
discordSRV.scheduler().runOnMainThread(commandSender, () -> {
|
||||
try {
|
||||
List<String> completions = command.tabComplete(commandSender, commandName, parts.toArray(new String[0]));
|
||||
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
for (String suggestion : completions) {
|
||||
suggestions.add(commandName + " " + finalPrefix + suggestion);
|
||||
}
|
||||
future.complete(suggestions);
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAliases(String command) {
|
||||
PluginCommand pluginCommand = discordSRV.server().getPluginCommand(command);
|
||||
if (pluginCommand == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> aliases = new ArrayList<>(pluginCommand.getAliases());
|
||||
aliases.add(pluginCommand.getName());
|
||||
|
||||
String pluginName = pluginCommand.getName().toLowerCase(Locale.ROOT);
|
||||
int originalMax = aliases.size();
|
||||
for (int i = 0; i < originalMax; i++) {
|
||||
// plugin:command
|
||||
aliases.add(pluginName + ":" + aliases.get(i));
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameCommand(String command1, String command2) {
|
||||
PluginCommand pluginCommand1 = discordSRV.server().getPluginCommand(command1);
|
||||
PluginCommand pluginCommand2 = discordSRV.server().getPluginCommand(command2);
|
||||
|
||||
return pluginCommand1 == pluginCommand2;
|
||||
}
|
||||
}
|
@ -21,7 +21,9 @@ package com.discordsrv.bukkit.console.executor;
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.common.command.game.executor.CommandExecutor;
|
||||
import com.discordsrv.common.command.game.executor.CommandExecutorProvider;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@ -33,7 +35,9 @@ public class BukkitCommandExecutorProvider implements CommandExecutorProvider {
|
||||
static {
|
||||
boolean has = false;
|
||||
try {
|
||||
has = PaperCommandExecutor.CREATE_COMMAND_SENDER != null;
|
||||
//noinspection JavaReflectionMemberAccess
|
||||
Server.class.getDeclaredMethod("createCommandSender", Consumer.class);
|
||||
has = true;
|
||||
} catch (Throwable ignored) {}
|
||||
HAS_PAPER_FORWARDING = has;
|
||||
}
|
||||
@ -48,11 +52,15 @@ public class BukkitCommandExecutorProvider implements CommandExecutorProvider {
|
||||
public CommandExecutor getConsoleExecutor(Consumer<Component> componentConsumer) {
|
||||
if (HAS_PAPER_FORWARDING) {
|
||||
try {
|
||||
return new PaperCommandExecutor(discordSRV, componentConsumer);
|
||||
CommandSender sender = new PaperCommandFeedbackExecutor(
|
||||
discordSRV.server(),
|
||||
apiComponent -> componentConsumer.accept(ComponentUtil.fromAPI(apiComponent))
|
||||
).sender();
|
||||
return new CommandSenderExecutor(discordSRV, sender);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
CommandSender commandSender = new BukkitCommandExecutorProxy(discordSRV.server().getConsoleSender(), componentConsumer).getProxy();
|
||||
CommandSender commandSender = new BukkitCommandFeedbackExecutorProxy(discordSRV.server().getConsoleSender(), componentConsumer).getProxy();
|
||||
return new CommandSenderExecutor(discordSRV, commandSender);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.bukkit.console.executor;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("JavaLangInvokeHandleSignature") // PaperAPI that is not included at compile accessed via reflection
|
||||
public class PaperCommandExecutor extends CommandSenderExecutor {
|
||||
|
||||
public static final MethodHandle CREATE_COMMAND_SENDER;
|
||||
|
||||
static {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
MethodHandle handle = null;
|
||||
try {
|
||||
MethodType methodType = MethodType.methodType(CommandSender.class, Consumer.class);
|
||||
handle = lookup.findVirtual(Server.class, "createCommandSender", methodType);
|
||||
} catch (Throwable ignored) {}
|
||||
CREATE_COMMAND_SENDER = handle;
|
||||
}
|
||||
|
||||
public PaperCommandExecutor(BukkitDiscordSRV discordSRV, Consumer<Component> componentConsumer) throws Throwable {
|
||||
super(discordSRV, (CommandSender) CREATE_COMMAND_SENDER.invoke(componentConsumer));
|
||||
}
|
||||
}
|
@ -65,7 +65,7 @@ public class BukkitDeathListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getEntity());
|
||||
MinecraftComponent component = COMPONENT_HANDLE.getComponent(discordSRV, event);
|
||||
MinecraftComponent component = COMPONENT_HANDLE.getComponent(event);
|
||||
|
||||
boolean cancelled = false;
|
||||
if (CANCELLED_HANDLE != null) {
|
||||
|
@ -57,7 +57,7 @@ public class BukkitStatusMessageListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer());
|
||||
MinecraftComponent component = JOIN_HANDLE.getComponent(discordSRV, event);
|
||||
MinecraftComponent component = JOIN_HANDLE.getComponent(event);
|
||||
boolean firstJoin = !event.getPlayer().hasPlayedBefore();
|
||||
|
||||
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
|
||||
@ -68,7 +68,7 @@ public class BukkitStatusMessageListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer());
|
||||
MinecraftComponent component = QUIT_HANDLE.getComponent(discordSRV, event);
|
||||
MinecraftComponent component = QUIT_HANDLE.getComponent(event);
|
||||
|
||||
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
|
||||
new LeaveMessageReceiveEvent(event, player, component, null, false)
|
||||
|
@ -34,7 +34,7 @@ public class BukkitChatForwarder implements IBukkitChatForwarder {
|
||||
// TODO: config option
|
||||
//noinspection ConstantConditions,PointlessBooleanExpression
|
||||
if (1 == 2 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
|
||||
return new PaperChatListener(discordSRV, new BukkitChatForwarder(discordSRV));
|
||||
return new PaperChatListener(new BukkitChatForwarder(discordSRV));
|
||||
}
|
||||
|
||||
return new BukkitChatListener(new BukkitChatForwarder(discordSRV));
|
||||
|
@ -33,7 +33,6 @@ public class BukkitPlayer extends BukkitCommandSender implements IPlayer {
|
||||
|
||||
private static final PaperComponentHandle<Player> DISPLAY_NAME_HANDLE = makeDisplayNameHandle();
|
||||
|
||||
@SuppressWarnings("deprecation") // Paper
|
||||
private static PaperComponentHandle<Player> makeDisplayNameHandle() {
|
||||
return new PaperComponentHandle<>(
|
||||
Player.class,
|
||||
@ -63,7 +62,7 @@ public class BukkitPlayer extends BukkitCommandSender implements IPlayer {
|
||||
|
||||
@Override
|
||||
public @NotNull Component displayName() {
|
||||
return ComponentUtil.fromAPI(DISPLAY_NAME_HANDLE.getComponent(discordSRV, player));
|
||||
return ComponentUtil.fromAPI(DISPLAY_NAME_HANDLE.getComponent(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,6 +23,7 @@ import com.discordsrv.api.module.type.Module;
|
||||
import com.discordsrv.api.placeholder.DiscordPlaceholders;
|
||||
import com.discordsrv.common.bootstrap.IBootstrap;
|
||||
import com.discordsrv.common.channel.ChannelConfigHelper;
|
||||
import com.discordsrv.common.command.game.GameCommandExecutionHelper;
|
||||
import com.discordsrv.common.command.game.handler.ICommandHandler;
|
||||
import com.discordsrv.common.component.ComponentFactory;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
@ -34,8 +35,8 @@ import com.discordsrv.common.debug.data.OnlineMode;
|
||||
import com.discordsrv.common.debug.data.VersionInfo;
|
||||
import com.discordsrv.common.dependency.DiscordSRVDependencyManager;
|
||||
import com.discordsrv.common.discord.api.DiscordAPIImpl;
|
||||
import com.discordsrv.common.discord.connection.jda.JDAConnectionManager;
|
||||
import com.discordsrv.common.discord.connection.details.DiscordConnectionDetailsImpl;
|
||||
import com.discordsrv.common.discord.connection.jda.JDAConnectionManager;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.impl.DiscordSRVLogger;
|
||||
@ -152,4 +153,9 @@ public interface DiscordSRV extends DiscordSRVApi {
|
||||
List<ReloadResult> runReload(Set<ReloadFlag> flags, boolean silent);
|
||||
CompletableFuture<Void> invokeDisable();
|
||||
|
||||
@Nullable
|
||||
default GameCommandExecutionHelper executeHelper() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
package com.discordsrv.common.command.combined.commands;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
@ -43,7 +43,7 @@ public class DebugCommand extends CombinedCommand {
|
||||
|
||||
private static DebugCommand INSTANCE;
|
||||
private static GameCommand GAME;
|
||||
private static Command DISCORD;
|
||||
private static DiscordCommand DISCORD;
|
||||
|
||||
private static DebugCommand getInstance(DiscordSRV discordSRV) {
|
||||
return INSTANCE != null ? INSTANCE : (INSTANCE = new DebugCommand(discordSRV));
|
||||
@ -67,10 +67,10 @@ public class DebugCommand extends CombinedCommand {
|
||||
return GAME;
|
||||
}
|
||||
|
||||
public static Command getDiscord(DiscordSRV discordSRV) {
|
||||
public static DiscordCommand getDiscord(DiscordSRV discordSRV) {
|
||||
if (DISCORD == null) {
|
||||
DebugCommand command = getInstance(discordSRV);
|
||||
DISCORD = Command.chatInput(ComponentIdentifier.of("DiscordSRV", "debug"), "debug", "Create a debug report")
|
||||
DISCORD = DiscordCommand.chatInput(ComponentIdentifier.of("DiscordSRV", "debug"), "debug", "Create a debug report")
|
||||
.addOption(
|
||||
CommandOption.builder(CommandOption.Type.STRING, "format", "The format to generate the debug report")
|
||||
.addChoice(".zip", "zip")
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.discordsrv.common.command.combined.commands;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.abstraction.CombinedCommand;
|
||||
@ -24,7 +24,7 @@ public class ResyncCommand extends CombinedCommand {
|
||||
|
||||
private static ResyncCommand INSTANCE;
|
||||
private static GameCommand GAME;
|
||||
private static Command DISCORD;
|
||||
private static DiscordCommand DISCORD;
|
||||
|
||||
private static ResyncCommand getInstance(DiscordSRV discordSRV) {
|
||||
return INSTANCE != null ? INSTANCE : (INSTANCE = new ResyncCommand(discordSRV));
|
||||
@ -41,10 +41,10 @@ public class ResyncCommand extends CombinedCommand {
|
||||
return GAME;
|
||||
}
|
||||
|
||||
public static Command getDiscord(DiscordSRV discordSRV) {
|
||||
public static DiscordCommand getDiscord(DiscordSRV discordSRV) {
|
||||
if (DISCORD == null) {
|
||||
ResyncCommand command = getInstance(discordSRV);
|
||||
DISCORD = Command.chatInput(ComponentIdentifier.of("DiscordSRV", "resync"), "resync", "Perform group resync for online players")
|
||||
DISCORD = DiscordCommand.chatInput(ComponentIdentifier.of("DiscordSRV", "resync"), "resync", "Perform group resync for online players")
|
||||
.setEventHandler(command)
|
||||
.build();
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
package com.discordsrv.common.command.combined.commands;
|
||||
|
||||
import com.discordsrv.api.color.Color;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.abstraction.CombinedCommand;
|
||||
@ -38,7 +38,7 @@ public class VersionCommand extends CombinedCommand {
|
||||
|
||||
private static VersionCommand INSTANCE;
|
||||
private static GameCommand GAME;
|
||||
private static Command DISCORD;
|
||||
private static DiscordCommand DISCORD;
|
||||
|
||||
private static VersionCommand getInstance(DiscordSRV discordSRV) {
|
||||
return INSTANCE != null ? INSTANCE : (INSTANCE = new VersionCommand(discordSRV));
|
||||
@ -55,10 +55,10 @@ public class VersionCommand extends CombinedCommand {
|
||||
return GAME;
|
||||
}
|
||||
|
||||
public static Command getDiscord(DiscordSRV discordSRV) {
|
||||
public static DiscordCommand getDiscord(DiscordSRV discordSRV) {
|
||||
if (DISCORD == null) {
|
||||
VersionCommand command = getInstance(discordSRV);
|
||||
DISCORD = Command.chatInput(ComponentIdentifier.of("DiscordSRV", "version"), "version", "Get the DiscordSRV version")
|
||||
DISCORD = DiscordCommand.chatInput(ComponentIdentifier.of("DiscordSRV", "version"), "version", "Get the DiscordSRV version")
|
||||
.setEventHandler(command)
|
||||
.build();
|
||||
}
|
||||
|
@ -1,26 +1,36 @@
|
||||
package com.discordsrv.common.command.discord.commands;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.commands.DebugCommand;
|
||||
import com.discordsrv.common.command.combined.commands.ResyncCommand;
|
||||
import com.discordsrv.common.command.combined.commands.VersionCommand;
|
||||
import com.discordsrv.common.command.discord.commands.subcommand.ExecuteCommand;
|
||||
import com.discordsrv.common.config.main.DiscordCommandConfig;
|
||||
|
||||
public class DiscordSRVDiscordCommand {
|
||||
|
||||
private static final ComponentIdentifier IDENTIFIER = ComponentIdentifier.of("DiscordSRV", "discordsrv");
|
||||
|
||||
private static Command INSTANCE;
|
||||
private static DiscordCommand INSTANCE;
|
||||
|
||||
public static Command get(DiscordSRV discordSRV) {
|
||||
public static DiscordCommand get(DiscordSRV discordSRV) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Command.chatInput(IDENTIFIER, "discordsrv", "DiscordSRV related commands")
|
||||
DiscordCommandConfig config = discordSRV.config().discordCommand;
|
||||
|
||||
DiscordCommand.ChatInputBuilder builder = DiscordCommand.chatInput(IDENTIFIER, "discordsrv", "DiscordSRV related commands")
|
||||
.addSubCommand(DebugCommand.getDiscord(discordSRV))
|
||||
.addSubCommand(VersionCommand.getDiscord(discordSRV))
|
||||
.addSubCommand(ResyncCommand.getDiscord(discordSRV))
|
||||
.addSubCommand(ResyncCommand.getDiscord(discordSRV));
|
||||
|
||||
if (config.execute.enabled) {
|
||||
builder = builder.addSubCommand(ExecuteCommand.get(discordSRV));
|
||||
}
|
||||
|
||||
INSTANCE = builder
|
||||
.setGuildOnly(false)
|
||||
.setDefaultPermission(Command.DefaultPermission.ADMINISTRATOR)
|
||||
.setDefaultPermission(DiscordCommand.DefaultPermission.ADMINISTRATOR)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,255 @@
|
||||
package com.discordsrv.common.command.discord.commands.subcommand;
|
||||
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.api.discord.events.interaction.command.DiscordChatInputInteractionEvent;
|
||||
import com.discordsrv.api.discord.events.interaction.command.DiscordCommandAutoCompleteInteractionEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.GameCommandExecutionHelper;
|
||||
import com.discordsrv.common.config.main.DiscordCommandConfig;
|
||||
import com.discordsrv.common.config.main.generic.GameCommandFilterConfig;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent>, DiscordCommand.AutoCompleteHandler {
|
||||
|
||||
private static DiscordCommand INSTANCE;
|
||||
|
||||
public static DiscordCommand get(DiscordSRV discordSRV) {
|
||||
if (INSTANCE == null) {
|
||||
DiscordCommandConfig.ExecuteConfig config = discordSRV.config().discordCommand.execute;
|
||||
|
||||
ExecuteCommand command = new ExecuteCommand(discordSRV);
|
||||
INSTANCE = DiscordCommand.chatInput(ComponentIdentifier.of("DiscordSRV", "execute"), "execute", "Run a Minecraft console command")
|
||||
.addOption(
|
||||
CommandOption.builder(CommandOption.Type.STRING, "command", "The command to execute")
|
||||
.setAutoComplete(config.suggest)
|
||||
.setRequired(true)
|
||||
.build()
|
||||
)
|
||||
.setAutoCompleteHandler(command)
|
||||
.setEventHandler(command)
|
||||
.build();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final GameCommandExecutionHelper helper;
|
||||
private final Logger logger;
|
||||
|
||||
public ExecuteCommand(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.helper = discordSRV.executeHelper();
|
||||
this.logger = new NamedLogger(discordSRV, "EXECUTE_COMMAND");
|
||||
}
|
||||
|
||||
public boolean isNotAcceptableCommand(DiscordGuildMember member, DiscordUser user, String command, boolean suggestions) {
|
||||
DiscordCommandConfig.ExecuteConfig config = discordSRV.config().discordCommand.execute;
|
||||
|
||||
for (GameCommandFilterConfig filter : config.filters) {
|
||||
if (!filter.isAcceptableCommand(member, user, command, suggestions, helper)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(DiscordChatInputInteractionEvent event) {
|
||||
DiscordCommandConfig.ExecuteConfig config = discordSRV.config().discordCommand.execute;
|
||||
if (!config.enabled) {
|
||||
event.asJDA().reply("The execute command is disabled").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
OptionMapping mapping = event.asJDA().getOption("command");
|
||||
if (mapping == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String command = mapping.getAsString();
|
||||
if (isNotAcceptableCommand(event.getMember(), event.getUser(), command, false)) {
|
||||
event.asJDA().reply("You do not have permission to run that command").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean ephemeral = config.ephemeral;
|
||||
event.asJDA().reply("Executing command `" + command + "`")
|
||||
.setEphemeral(ephemeral)
|
||||
.queue(ih -> new ExecutionContext(discordSRV, ih, config.getOutputMode(), ephemeral).run(command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void autoComplete(DiscordCommandAutoCompleteInteractionEvent event) {
|
||||
if (helper == null) {
|
||||
// No suggestions available.
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordCommandConfig.ExecuteConfig config = discordSRV.config().discordCommand.execute;
|
||||
if (!config.suggest) {
|
||||
return;
|
||||
}
|
||||
|
||||
OptionMapping mapping = event.asJDA().getOption("command");
|
||||
if (mapping == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String command = mapping.getAsString();
|
||||
List<String> parts = new ArrayList<>(Arrays.asList(command.split(" ")));
|
||||
|
||||
List<String> suggestions = getSuggestions(parts);
|
||||
if (suggestions == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (suggestions.isEmpty() || suggestions.contains(command)) {
|
||||
parts.add("");
|
||||
|
||||
List<String> newSuggestions = getSuggestions(parts);
|
||||
if (newSuggestions == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
suggestions = new ArrayList<>(newSuggestions);
|
||||
if (suggestions.isEmpty()) {
|
||||
suggestions.add(command);
|
||||
}
|
||||
}
|
||||
|
||||
suggestions.sort((s1, s2) -> {
|
||||
// Options with semicolons (eg. plugin:command) are at the bottom
|
||||
int semi1 = s1.indexOf(':');
|
||||
int semi2 = s2.indexOf(':');
|
||||
if (semi1 > semi2) {
|
||||
return 1;
|
||||
} else if (semi2 > semi1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Otherwise alphabetically sorted
|
||||
return s1.toLowerCase(Locale.ROOT).compareTo(s2.toLowerCase(Locale.ROOT));
|
||||
});
|
||||
for (String suggestion : suggestions) {
|
||||
if (event.getChoices().size() >= 25) {
|
||||
break;
|
||||
}
|
||||
if (config.filterSuggestions && isNotAcceptableCommand(event.getMember(), event.getUser(), suggestion, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
event.addChoice(suggestion, suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getSuggestions(List<String> parts) {
|
||||
try {
|
||||
return helper.suggestCommands(new ArrayList<>(parts)).get(2, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (TimeoutException e) {
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
logger.error("Failed to suggest commands", e.getCause());
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to suggest commands", t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExecutionContext {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final InteractionHook hook;
|
||||
private final DiscordCommandConfig.OutputMode outputMode;
|
||||
private final boolean ephemeral;
|
||||
private ScheduledFuture<?> future;
|
||||
private final Queue<Component> queued = new LinkedBlockingQueue<>();
|
||||
|
||||
public ExecutionContext(
|
||||
DiscordSRV discordSRV,
|
||||
InteractionHook hook,
|
||||
DiscordCommandConfig.OutputMode outputMode,
|
||||
boolean ephemeral
|
||||
) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.hook = hook;
|
||||
this.outputMode = outputMode;
|
||||
this.ephemeral = ephemeral;
|
||||
}
|
||||
|
||||
public void run(String command) {
|
||||
discordSRV.console().commandExecutorProvider()
|
||||
.getConsoleExecutor(this::consumeComponent)
|
||||
.runCommand(command);
|
||||
}
|
||||
|
||||
private void consumeComponent(Component component) {
|
||||
if (outputMode == DiscordCommandConfig.OutputMode.OFF) {
|
||||
return;
|
||||
}
|
||||
synchronized (queued) {
|
||||
queued.offer(component);
|
||||
if (future == null) {
|
||||
future = discordSRV.scheduler().runLater(this::send, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send() {
|
||||
boolean ansi = outputMode == DiscordCommandConfig.OutputMode.ANSI;
|
||||
boolean plainBlock = outputMode == DiscordCommandConfig.OutputMode.PLAIN_BLOCK;
|
||||
String prefix = ansi ? "```ansi\n" : (plainBlock ? "```\n" : "");
|
||||
String suffix = ansi ? "```" : (plainBlock ? "```" : "");
|
||||
|
||||
String delimiter = "\n";
|
||||
StringJoiner joiner = new StringJoiner(delimiter);
|
||||
|
||||
Component component;
|
||||
synchronized (queued) {
|
||||
while ((component = queued.poll()) != null) {
|
||||
String discord;
|
||||
switch (outputMode) {
|
||||
default:
|
||||
case MARKDOWN:
|
||||
discord = discordSRV.componentFactory().discordSerializer().serialize(component);
|
||||
break;
|
||||
case ANSI:
|
||||
discord = discordSRV.componentFactory().ansiSerializer().serialize(component);
|
||||
break;
|
||||
case PLAIN:
|
||||
case PLAIN_BLOCK:
|
||||
discord = discordSRV.componentFactory().plainSerializer().serialize(component);
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefix.length() + suffix.length() + discord.length() + joiner.length() + delimiter.length() > Message.MAX_CONTENT_LENGTH) {
|
||||
hook.sendMessage(prefix + joiner + suffix).setEphemeral(ephemeral).queue();
|
||||
joiner = new StringJoiner(delimiter);
|
||||
}
|
||||
|
||||
joiner.add(discord);
|
||||
}
|
||||
future = null;
|
||||
}
|
||||
hook.sendMessage(prefix + joiner + suffix).setEphemeral(ephemeral).queue();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.discordsrv.common.command.game;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface GameCommandExecutionHelper {
|
||||
|
||||
CompletableFuture<List<String>> suggestCommands(List<String> parts);
|
||||
List<String> getAliases(String command);
|
||||
boolean isSameCommand(String command1, String command2);
|
||||
|
||||
}
|
@ -70,14 +70,14 @@ public final class ComponentUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static Component fromUnrelocated(Object unrelocatedAdventure) {
|
||||
public static MinecraftComponent fromUnrelocated(Object unrelocatedAdventure) {
|
||||
MinecraftComponentImpl component = MinecraftComponentImpl.empty();
|
||||
MinecraftComponent.Adapter<Object> adapter = component.unrelocatedAdapter();
|
||||
if (adapter == null) {
|
||||
throw new IllegalStateException("Could not get unrelocated adventure gson serializer");
|
||||
}
|
||||
adapter.setComponent(unrelocatedAdventure);
|
||||
return fromAPI(component);
|
||||
return component;
|
||||
}
|
||||
|
||||
public static Component join(Component delimiter, Collection<? extends ComponentLike> components) {
|
||||
|
@ -0,0 +1,72 @@
|
||||
package com.discordsrv.common.config.main;
|
||||
|
||||
import com.discordsrv.common.config.main.generic.GameCommandFilterConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@ConfigSerializable
|
||||
public class DiscordCommandConfig {
|
||||
|
||||
public ExecuteConfig execute = new ExecuteConfig();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class ExecuteConfig {
|
||||
|
||||
public ExecuteConfig() {
|
||||
filters.add(
|
||||
new GameCommandFilterConfig(
|
||||
new ArrayList<>(),
|
||||
false,
|
||||
new ArrayList<>(Arrays.asList("say", "/gamemode(?: (?:survival|spectator)(?: .+)?)?/"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment("If the command output should only be visible to the user who ran the command")
|
||||
public boolean ephemeral = true;
|
||||
|
||||
@Comment("The mode for the command output, available options are:\n"
|
||||
+ "- markdown: Regular Discord markdown\n"
|
||||
+ "- ansi: A colored ansi code block\n"
|
||||
+ "- plain: Plain text\n"
|
||||
+ "- codeblock: Plain code block\n"
|
||||
+ "- off: No command output")
|
||||
public String outputMode = "markdown";
|
||||
|
||||
public OutputMode getOutputMode() {
|
||||
switch (outputMode.toLowerCase(Locale.ROOT)) {
|
||||
default:
|
||||
case "markdown": return OutputMode.MARKDOWN;
|
||||
case "ansi": return OutputMode.ANSI;
|
||||
case "plain": return OutputMode.PLAIN;
|
||||
case "codeblock": return OutputMode.PLAIN_BLOCK;
|
||||
case "off": return OutputMode.OFF;
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("At least one condition has to match to allow execution")
|
||||
public List<GameCommandFilterConfig> filters = new ArrayList<>();
|
||||
|
||||
@Comment("If commands should be suggested while typing\n" +
|
||||
"Suggestions go through the server's main thread (on servers with a main thread) to ensure compatability.")
|
||||
public boolean suggest = true;
|
||||
|
||||
@Comment("If suggestions should be filtered based on the \"filters\" option")
|
||||
public boolean filterSuggestions = true;
|
||||
}
|
||||
|
||||
public enum OutputMode {
|
||||
MARKDOWN,
|
||||
ANSI,
|
||||
PLAIN,
|
||||
PLAIN_BLOCK,
|
||||
OFF
|
||||
}
|
||||
}
|
@ -29,7 +29,9 @@ import com.discordsrv.common.config.main.linking.LinkedAccountConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigSerializable
|
||||
public abstract class MainConfig implements Config {
|
||||
@ -79,6 +81,9 @@ public abstract class MainConfig implements Config {
|
||||
@Comment("In-game command configuration")
|
||||
public GameCommandConfig gameCommand = new GameCommandConfig();
|
||||
|
||||
@Comment("Discord command configuration")
|
||||
public DiscordCommandConfig discordCommand = new DiscordCommandConfig();
|
||||
|
||||
@Comment("Configuration for the %discord_invite% placeholder. The below options will be attempted in the order they are in")
|
||||
public DiscordInviteConfig invite = new DiscordInviteConfig();
|
||||
|
||||
|
@ -0,0 +1,116 @@
|
||||
package com.discordsrv.common.config.main.generic;
|
||||
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.common.command.game.GameCommandExecutionHelper;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ConfigSerializable
|
||||
public class GameCommandFilterConfig {
|
||||
|
||||
public GameCommandFilterConfig() {}
|
||||
|
||||
public GameCommandFilterConfig(List<Long> roleAndUserIds, boolean blacklist, List<String> commands) {
|
||||
this.roleAndUserIds = roleAndUserIds;
|
||||
this.blacklist = blacklist;
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
@Comment("The role and user ids which this filter applies to")
|
||||
public List<Long> roleAndUserIds = new ArrayList<>();
|
||||
|
||||
@Comment("true for blacklist (blocking commands), false for whitelist (allowing commands)")
|
||||
public boolean blacklist = true;
|
||||
|
||||
@Comment("The commands and/or patterns that are allowed/blocked.\n" +
|
||||
"The command needs to start with input, this will attempt to normalize command aliases where possible (for the main command)\n" +
|
||||
"If the command start and ends with /, the input will be treated as a regular expression (regex) and it will pass if it matches the entire command")
|
||||
public List<String> commands = new ArrayList<>();
|
||||
|
||||
public static boolean isCommandMatch(String configCommand, String command, boolean suggestions, GameCommandExecutionHelper helper) {
|
||||
if (configCommand.startsWith("/") && configCommand.endsWith("/")) {
|
||||
// Regex handling
|
||||
Pattern pattern = Pattern.compile(configCommand.substring(1, configCommand.length() - 1));
|
||||
|
||||
Matcher matcher = pattern.matcher(command);
|
||||
return matcher.matches() && matcher.start() == 0 && matcher.end() == command.length();
|
||||
}
|
||||
|
||||
// Normal handling
|
||||
configCommand = configCommand.toLowerCase(Locale.ROOT);
|
||||
command = command.toLowerCase(Locale.ROOT);
|
||||
|
||||
List<String> parts = new ArrayList<>(Arrays.asList(configCommand.split(" ")));
|
||||
String rootCommand = parts.remove(0);
|
||||
|
||||
Set<String> rootCommands = new LinkedHashSet<>();
|
||||
rootCommands.add(rootCommand);
|
||||
if (helper != null) {
|
||||
rootCommands.addAll(helper.getAliases(rootCommand));
|
||||
}
|
||||
|
||||
if (suggestions) {
|
||||
// Allow suggesting the commands up to the allowed command
|
||||
for (String rootCmd : rootCommands) {
|
||||
if (command.matches("^" + Pattern.quote(rootCmd) + " ?$")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
StringBuilder built = new StringBuilder(rootCmd);
|
||||
for (String part : parts) {
|
||||
built.append(" ").append(part);
|
||||
if (command.matches("^" + Pattern.quote(built.toString()) + " ?$")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String arguments = String.join(" ", parts);
|
||||
for (String rootCmd : rootCommands) {
|
||||
String joined = rootCmd + (arguments.isEmpty() ? "" : " " + arguments);
|
||||
|
||||
// This part at the end prevents "command list" matching "command listsecrets"
|
||||
if (command.matches("^" + Pattern.quote(joined) + "(?:$| .+)")) {
|
||||
// Make sure it's the same command, the alias may be used by another command
|
||||
return helper == null || helper.isSameCommand(rootCommand, rootCmd);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isAcceptableCommand(DiscordGuildMember member, DiscordUser user, String command, boolean suggestions, GameCommandExecutionHelper helper) {
|
||||
long userId = user.getId();
|
||||
List<Long> roleIds = new ArrayList<>();
|
||||
if (member != null) {
|
||||
for (DiscordRole role : member.getRoles()) {
|
||||
roleIds.add(role.getId());
|
||||
}
|
||||
}
|
||||
|
||||
boolean match = false;
|
||||
for (Long id : roleAndUserIds) {
|
||||
if (id == userId || roleIds.contains(id)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String configCommand : commands) {
|
||||
if (isCommandMatch(configCommand, command, suggestions, helper) != blacklist) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.interaction.DiscordInteractionHook;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandType;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.SubCommandGroup;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.api.discord.events.interaction.DiscordModalInteractionEvent;
|
||||
@ -130,7 +131,7 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
DiscordGuildMember guildMember = member != null ? api().getGuildMember(member) : null;
|
||||
DiscordMessageChannel channel = api().getMessageChannel(event.getMessageChannel());
|
||||
if (event instanceof CommandAutoCompleteInteractionEvent) {
|
||||
com.discordsrv.api.discord.entity.interaction.command.Command command = discordSRV.discordAPI().getActiveCommand(
|
||||
DiscordCommand command = discordSRV.discordAPI().getActiveCommand(
|
||||
((CommandAutoCompleteInteractionEvent) event).isGuildCommand() ? event.getGuild() : null,
|
||||
CommandType.CHAT_INPUT,
|
||||
((CommandAutoCompleteInteractionEvent) event).getName()
|
||||
@ -138,25 +139,30 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
if (command == null) {
|
||||
return;
|
||||
}
|
||||
command = mapCommand(
|
||||
command,
|
||||
((CommandAutoCompleteInteractionEvent) event).getSubcommandGroup(),
|
||||
((CommandAutoCompleteInteractionEvent) event).getSubcommandName()
|
||||
);
|
||||
|
||||
DiscordCommandAutoCompleteInteractionEvent autoComplete = new DiscordCommandAutoCompleteInteractionEvent(
|
||||
(CommandAutoCompleteInteractionEvent) event, command.getId(), user, guildMember, channel);
|
||||
discordSRV.eventBus().publish(autoComplete);
|
||||
Consumer<DiscordCommandAutoCompleteInteractionEvent> autoCompleteHandler = command.getAutoCompleteHandler();
|
||||
DiscordCommand.AutoCompleteHandler autoCompleteHandler = command.getAutoCompleteHandler();
|
||||
if (autoCompleteHandler != null) {
|
||||
autoCompleteHandler.accept(autoComplete);
|
||||
autoCompleteHandler.autoComplete(autoComplete);
|
||||
}
|
||||
|
||||
List<Command.Choice> choices = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : autoComplete.getChoices().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String name = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof String) {
|
||||
choices.add(new Command.Choice(key, (String) value));
|
||||
choices.add(new Command.Choice(name, (String) value));
|
||||
} else if (value instanceof Double || value instanceof Float) {
|
||||
choices.add(new Command.Choice(key, ((Number) value).doubleValue()));
|
||||
choices.add(new Command.Choice(name, ((Number) value).doubleValue()));
|
||||
} else {
|
||||
choices.add(new Command.Choice(key, ((Number) value).longValue()));
|
||||
choices.add(new Command.Choice(name, ((Number) value).longValue()));
|
||||
}
|
||||
}
|
||||
((CommandAutoCompleteInteractionEvent) event).replyChoices(choices).queue();
|
||||
@ -169,7 +175,7 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
Guild guild = ((GenericCommandInteractionEvent) event).isGuildCommand() ? event.getGuild() : null;
|
||||
String name = ((GenericCommandInteractionEvent) event).getName();
|
||||
if (event instanceof MessageContextInteractionEvent) {
|
||||
com.discordsrv.api.discord.entity.interaction.command.Command command = discordSRV.discordAPI()
|
||||
DiscordCommand command = discordSRV.discordAPI()
|
||||
.getActiveCommand(guild, CommandType.MESSAGE, name).orElse(null);
|
||||
if (command == null) {
|
||||
return;
|
||||
@ -190,7 +196,7 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
eventHandler.accept(interactionEvent);
|
||||
}
|
||||
} else if (event instanceof UserContextInteractionEvent) {
|
||||
com.discordsrv.api.discord.entity.interaction.command.Command command = discordSRV.discordAPI()
|
||||
DiscordCommand command = discordSRV.discordAPI()
|
||||
.getActiveCommand(guild, CommandType.USER, name).orElse(null);
|
||||
if (command == null) {
|
||||
return;
|
||||
@ -211,35 +217,16 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
eventHandler.accept(interactionEvent);
|
||||
}
|
||||
} else if (event instanceof SlashCommandInteractionEvent) {
|
||||
com.discordsrv.api.discord.entity.interaction.command.Command command = discordSRV.discordAPI()
|
||||
DiscordCommand command = discordSRV.discordAPI()
|
||||
.getActiveCommand(guild, CommandType.CHAT_INPUT, name).orElse(null);
|
||||
if (command == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String subCommandGroupName = ((SlashCommandInteractionEvent) event).getSubcommandGroup();
|
||||
String subCommandName = ((SlashCommandInteractionEvent) event).getSubcommandName();
|
||||
|
||||
if (subCommandGroupName != null) {
|
||||
for (SubCommandGroup group : command.getSubCommandGroups()) {
|
||||
if (group.getName().equals(subCommandGroupName)) {
|
||||
for (com.discordsrv.api.discord.entity.interaction.command.Command subCommand : group.getCommands()) {
|
||||
if (subCommand.getName().equals(subCommandName)) {
|
||||
command = subCommand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (subCommandName != null) {
|
||||
for (com.discordsrv.api.discord.entity.interaction.command.Command subCommand : command.getSubCommands()) {
|
||||
if (subCommandName.equals(subCommand.getName())) {
|
||||
command = subCommand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
command = mapCommand(
|
||||
command,
|
||||
((SlashCommandInteractionEvent) event).getSubcommandGroup(),
|
||||
((SlashCommandInteractionEvent) event).getSubcommandName()
|
||||
);
|
||||
|
||||
DiscordChatInputInteractionEvent interactionEvent = new DiscordChatInputInteractionEvent(
|
||||
(SlashCommandInteractionEvent) event,
|
||||
@ -283,4 +270,28 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
discordSRV.eventBus().publish(newEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private DiscordCommand mapCommand(DiscordCommand command, String subCommandGroupName, String subCommandName) {
|
||||
if (subCommandGroupName != null) {
|
||||
for (SubCommandGroup group : command.getSubCommandGroups()) {
|
||||
if (group.getName().equals(subCommandGroupName)) {
|
||||
for (DiscordCommand subCommand : group.getCommands()) {
|
||||
if (subCommand.getName().equals(subCommandName)) {
|
||||
command = subCommand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (subCommandName != null) {
|
||||
for (DiscordCommand subCommand : command.getSubCommands()) {
|
||||
if (subCommandName.equals(subCommand.getName())) {
|
||||
command = subCommand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.*;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandType;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.exception.NotReadyException;
|
||||
import com.discordsrv.api.discord.exception.RestErrorResponseException;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
@ -495,16 +495,16 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.RegistrationResult registerCommand(Command command) {
|
||||
public DiscordCommand.RegistrationResult registerCommand(DiscordCommand command) {
|
||||
return commandRegistry.register(command, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommand(Command command) {
|
||||
public void unregisterCommand(DiscordCommand command) {
|
||||
commandRegistry.unregister(command);
|
||||
}
|
||||
|
||||
public Optional<Command> getActiveCommand(@Nullable Guild guild, CommandType type, String name) {
|
||||
public Optional<DiscordCommand> getActiveCommand(@Nullable Guild guild, CommandType type, String name) {
|
||||
return Optional.ofNullable(commandRegistry.getActive(guild != null ? guild.getIdLong() : null, type, name));
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
package com.discordsrv.common.discord.api;
|
||||
|
||||
import com.discordsrv.api.discord.entity.JDAEntity;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.Command;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandType;
|
||||
import com.discordsrv.api.discord.events.interaction.command.CommandRegisterEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
@ -50,7 +50,7 @@ public class DiscordCommandRegistry {
|
||||
CommandRegisterEvent event = new CommandRegisterEvent();
|
||||
discordSRV.eventBus().publish(event);
|
||||
|
||||
List<Command> commands = event.getCommands();
|
||||
List<DiscordCommand> commands = event.getCommands();
|
||||
for (Map<CommandType, Registry> registryMap : registries.values()) {
|
||||
registryMap.values().forEach(registry -> registry.removeIf(reg -> reg.isTemporary() && !commands.contains(reg.getCommand())));
|
||||
}
|
||||
@ -58,27 +58,27 @@ public class DiscordCommandRegistry {
|
||||
commands.forEach(cmd -> register(cmd, true));
|
||||
}
|
||||
|
||||
public Command.RegistrationResult register(Command command, boolean temporary) {
|
||||
public DiscordCommand.RegistrationResult register(DiscordCommand command, boolean temporary) {
|
||||
CommandType type = command.getType();
|
||||
Long guildId = command.getGuildId();
|
||||
Registry registry = registries
|
||||
.computeIfAbsent(guildId != null ? guildId : GLOBAL_ID, key -> new EnumMap<>(CommandType.class))
|
||||
.computeIfAbsent(type, key -> new Registry());
|
||||
if (registry.contains(command)) {
|
||||
return Command.RegistrationResult.ALREADY_REGISTERED;
|
||||
return DiscordCommand.RegistrationResult.ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
boolean first = registry.register(command, temporary);
|
||||
if (!first) {
|
||||
return Command.RegistrationResult.NAME_ALREADY_IN_USE;
|
||||
return DiscordCommand.RegistrationResult.NAME_ALREADY_IN_USE;
|
||||
}
|
||||
if (registry.getInTimeOrder().indexOf(command) >= type.getMaximumCount()) {
|
||||
return Command.RegistrationResult.TOO_MANY_COMMANDS;
|
||||
return DiscordCommand.RegistrationResult.TOO_MANY_COMMANDS;
|
||||
}
|
||||
return Command.RegistrationResult.REGISTERED;
|
||||
return DiscordCommand.RegistrationResult.REGISTERED;
|
||||
}
|
||||
|
||||
public void unregister(Command command) {
|
||||
public void unregister(DiscordCommand command) {
|
||||
Long guildId = command.getGuildId();
|
||||
Registry registry = registries
|
||||
.computeIfAbsent(guildId != null ? guildId : GLOBAL_ID, key -> Collections.emptyMap())
|
||||
@ -90,7 +90,7 @@ public class DiscordCommandRegistry {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Command getActive(Long guildId, CommandType type, String name) {
|
||||
public DiscordCommand getActive(Long guildId, CommandType type, String name) {
|
||||
Registry registry = registries
|
||||
.computeIfAbsent(guildId != null ? guildId : GLOBAL_ID, key -> Collections.emptyMap())
|
||||
.get(type);
|
||||
@ -114,23 +114,23 @@ public class DiscordCommandRegistry {
|
||||
|
||||
for (long guildId : ids) {
|
||||
Map<CommandType, Registry> commandsByType = registries.getOrDefault(guildId, Collections.emptyMap());
|
||||
Map<CommandType, Set<Command>> commandsToRegister = new EnumMap<>(CommandType.class);
|
||||
Map<CommandType, Set<DiscordCommand>> commandsToRegister = new EnumMap<>(CommandType.class);
|
||||
|
||||
boolean updateNeeded = false;
|
||||
for (Map.Entry<CommandType, Registry> entry : commandsByType.entrySet()) {
|
||||
Registry registry = entry.getValue();
|
||||
|
||||
List<Command> commands = registry.getInTimeOrder();
|
||||
Set<Command> currentCommands = new LinkedHashSet<>();
|
||||
List<DiscordCommand> commands = registry.getInTimeOrder();
|
||||
Set<DiscordCommand> currentCommands = new LinkedHashSet<>();
|
||||
int max = Math.min(commands.size(), entry.getKey().getMaximumCount());
|
||||
for (int i = 0; i < max; i++) {
|
||||
Command command = commands.get(i);
|
||||
DiscordCommand command = commands.get(i);
|
||||
currentCommands.add(command);
|
||||
}
|
||||
|
||||
commandsToRegister.put(entry.getKey(), currentCommands);
|
||||
|
||||
Collection<Command> activeCommands = registry.activeCommands.values();
|
||||
Collection<DiscordCommand> activeCommands = registry.activeCommands.values();
|
||||
if (activeCommands.size() != currentCommands.size() || !currentCommands.containsAll(activeCommands)) {
|
||||
updateNeeded = true;
|
||||
}
|
||||
@ -148,7 +148,7 @@ public class DiscordCommandRegistry {
|
||||
action = guild.updateCommands();
|
||||
}
|
||||
|
||||
List<Command> allCommands = new ArrayList<>();
|
||||
List<DiscordCommand> allCommands = new ArrayList<>();
|
||||
commandsToRegister.values().forEach(allCommands::addAll);
|
||||
action.addCommands(allCommands.stream().map(JDAEntity::asJDA).collect(Collectors.toList()))
|
||||
.queue(v -> {
|
||||
@ -166,7 +166,7 @@ public class DiscordCommandRegistry {
|
||||
private static class Registry {
|
||||
|
||||
private final Map<String, List<Registration>> registry = new ConcurrentHashMap<>();
|
||||
private final Map<String, Command> activeCommands = new HashMap<>();
|
||||
private final Map<String, DiscordCommand> activeCommands = new HashMap<>();
|
||||
|
||||
public void removeIf(Predicate<Registration> commandPredicate) {
|
||||
List<String> removeKeys = new ArrayList<>();
|
||||
@ -180,7 +180,7 @@ public class DiscordCommandRegistry {
|
||||
removeKeys.forEach(registry::remove);
|
||||
}
|
||||
|
||||
public boolean contains(@NotNull Command command) {
|
||||
public boolean contains(@NotNull DiscordCommand command) {
|
||||
List<Registration> commands = registry.get(command.getName());
|
||||
if (commands == null) {
|
||||
return false;
|
||||
@ -189,14 +189,14 @@ public class DiscordCommandRegistry {
|
||||
return commands.stream().anyMatch(reg -> reg.getCommand() == command);
|
||||
}
|
||||
|
||||
public boolean register(@NotNull Command command, boolean temporary) {
|
||||
public boolean register(@NotNull DiscordCommand command, boolean temporary) {
|
||||
List<Registration> commands = registry.computeIfAbsent(command.getName(), key -> new CopyOnWriteArrayList<>());
|
||||
boolean empty = commands.isEmpty();
|
||||
commands.add(new Registration(command, temporary));
|
||||
return empty;
|
||||
}
|
||||
|
||||
public void unregister(@NotNull Command command) {
|
||||
public void unregister(@NotNull DiscordCommand command) {
|
||||
List<Registration> commands = registry.get(command.getName());
|
||||
if (commands == null) {
|
||||
return;
|
||||
@ -208,16 +208,16 @@ public class DiscordCommandRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
public void putActiveCommands(Set<Command> commands) {
|
||||
public void putActiveCommands(Set<DiscordCommand> commands) {
|
||||
synchronized (activeCommands) {
|
||||
activeCommands.clear();
|
||||
for (Command command : commands) {
|
||||
for (DiscordCommand command : commands) {
|
||||
activeCommands.put(command.getName(), command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Command> getInTimeOrder() {
|
||||
public List<DiscordCommand> getInTimeOrder() {
|
||||
List<Registration> registrations = registry.values().stream()
|
||||
.map(list -> list.get(0))
|
||||
.collect(Collectors.toList());
|
||||
@ -229,7 +229,7 @@ public class DiscordCommandRegistry {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Command getActive(String name) {
|
||||
public DiscordCommand getActive(String name) {
|
||||
synchronized (activeCommands) {
|
||||
return activeCommands.get(name);
|
||||
}
|
||||
@ -238,17 +238,17 @@ public class DiscordCommandRegistry {
|
||||
|
||||
private static class Registration {
|
||||
|
||||
private final Command command;
|
||||
private final DiscordCommand command;
|
||||
private final long time;
|
||||
private final boolean temporary;
|
||||
|
||||
public Registration(Command command, boolean temporary) {
|
||||
public Registration(DiscordCommand command, boolean temporary) {
|
||||
this.command = command;
|
||||
this.time = System.currentTimeMillis();
|
||||
this.temporary = temporary;
|
||||
}
|
||||
|
||||
public Command getCommand() {
|
||||
public DiscordCommand getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,110 @@
|
||||
package com.discordsrv.common.command.game;
|
||||
|
||||
import com.discordsrv.common.config.main.generic.GameCommandFilterConfig;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class GameCommandFilterTest {
|
||||
|
||||
private final ExecutionHelper helper = new ExecutionHelper();
|
||||
|
||||
@Test
|
||||
public void test1() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("test", "test", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
Assertions.assertFalse(GameCommandFilterConfig.isCommandMatch("test", "tester", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void argumentTest() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("test arg", "test arg", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suggestTest() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("test arg", "test", true, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extraTest() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("test arg", "test arg extra arguments after that", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void argumentOverflowTest1() {
|
||||
Assertions.assertFalse(GameCommandFilterConfig.isCommandMatch("test arg", "test argument", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameCommandTest1() {
|
||||
Assertions.assertFalse(GameCommandFilterConfig.isCommandMatch("plugin1:test", "test", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameCommandTest2() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("plugin2:test", "test", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regexTest1() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("/test/", "test", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regexTest2() {
|
||||
Assertions.assertFalse(GameCommandFilterConfig.isCommandMatch("/test/", "test extra", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regexTest3() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("/test( argument)?/", "test argument", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regexTest4() {
|
||||
Assertions.assertFalse(GameCommandFilterConfig.isCommandMatch("/test( argument)?/", "test fail", false, helper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regexTest5() {
|
||||
Assertions.assertTrue(GameCommandFilterConfig.isCommandMatch("/test( argument)?/", "test", true, helper));
|
||||
}
|
||||
|
||||
public static class ExecutionHelper implements GameCommandExecutionHelper {
|
||||
|
||||
private final List<String> TEST1_USED = Collections.singletonList("plugin1:test");
|
||||
private final List<String> TEST1 = Arrays.asList("test", "plugin1:test");
|
||||
private final List<String> TEST2 = Arrays.asList("test", "plugin2:test");
|
||||
private final List<String> TESTER = Arrays.asList("tester", "plugin2:tester");
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<String>> suggestCommands(List<String> parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAliases(String command) {
|
||||
if (TEST1_USED.contains(command)) {
|
||||
return TEST1;
|
||||
} else if (TEST2.contains(command)) {
|
||||
return TEST2;
|
||||
} else if (TESTER.contains(command)) {
|
||||
return TESTER;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameCommand(String command1, String command2) {
|
||||
return getAliases(command1) == getAliases(command2);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
dependencies {
|
||||
compileOnlyApi(libs.slf4j.api)
|
||||
compileOnlyApi(libs.adventure.api)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.discordsrv.unrelocate.net.kyori.adventure.text;
|
||||
|
||||
@SuppressWarnings("NonExtendableApiUsage")
|
||||
public interface Component extends net.kyori.adventure.text.Component {
|
||||
}
|
@ -18,8 +18,8 @@ dependencyResolutionManagement {
|
||||
// Bukkit
|
||||
version('bukkit_minimum', '1.8.8-R0.1-SNAPSHOT')
|
||||
version('bukkit1_12', '1.12.2-R0.1-SNAPSHOT')
|
||||
version('bukkit_latest', '1.19.4-R0.1-SNAPSHOT')
|
||||
version('folia', '1.19.4-R0.1-SNAPSHOT')
|
||||
version('bukkit_latest', '1.20.1-R0.1-SNAPSHOT')
|
||||
version('folia', '1.20.1-R0.1-SNAPSHOT')
|
||||
library('paperapi', 'io.papermc.paper', 'paper-api').versionRef('bukkit_latest')
|
||||
library('spigotapi', 'org.spigotmc', 'spigot-api').versionRef('bukkit_minimum')
|
||||
library('spigotapi-onetwelve', 'org.spigotmc', 'spigot-api').versionRef('bukkit1_12')
|
||||
|
Loading…
Reference in New Issue
Block a user