diff --git a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandManager.java b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandManager.java index d73153c8..763f592a 100644 --- a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandManager.java +++ b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandManager.java @@ -46,6 +46,7 @@ import java.util.function.Function; */ public class JDACommandManager extends CommandManager { + private final JDA jda; private final long botId; private final Function<@NonNull C, @NonNull String> prefixMapper; @@ -74,6 +75,7 @@ public class JDACommandManager extends CommandManager { ) throws InterruptedException { super(commandExecutionCoordinator, CommandRegistrationHandler.nullCommandRegistrationHandler()); + this.jda = jda; this.prefixMapper = prefixMapper; this.permissionMapper = permissionMapper; this.commandSenderMapper = commandSenderMapper; @@ -81,6 +83,18 @@ public class JDACommandManager extends CommandManager { jda.addEventListener(new JDACommandListener<>(this)); jda.awaitReady(); this.botId = jda.getSelfUser().getIdLong(); + + /* Register JDA Preprocessor */ + this.registerCommandPreProcessor(new JDACommandPreprocessor<>(this)); + } + + /** + * Get the JDA instance + * + * @return JDA instance + */ + public final @NonNull JDA getJDA() { + return jda; } /** @@ -101,6 +115,15 @@ public class JDACommandManager extends CommandManager { return this.commandSenderMapper; } + /** + * Get the backwards command sender plugin + * + * @return The backwards command sender mapper + */ + public final @NonNull Function<@NonNull C, @NonNull MessageReceivedEvent> getBackwardsCommandSenderMapper() { + return this.backwardsCommandSenderMapper; + } + /** * Get the bots discord id * diff --git a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandPreprocessor.java b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandPreprocessor.java new file mode 100644 index 00000000..0d2fa47f --- /dev/null +++ b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandPreprocessor.java @@ -0,0 +1,59 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// 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. +// +package cloud.commandframework.jda; + +import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; +import cloud.commandframework.execution.preprocessor.CommandPreprocessor; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Command preprocessor which decorates incoming {@link cloud.commandframework.context.CommandContext} + * with Bukkit specific objects + * + * @param + */ +final class JDACommandPreprocessor implements CommandPreprocessor { + + private final JDACommandManager mgr; + + /** + * The JDA Command Preprocessor for storing JDA-specific contexts in the command contexts + * + * @param mgr The JDACommandManager + */ + JDACommandPreprocessor(final @NonNull JDACommandManager mgr) { + this.mgr = mgr; + } + + /** + * Stores the {@link net.dv8tion.jda.api.JDA} + */ + @Override + public void accept(final @NonNull CommandPreprocessingContext context) { + context.getCommandContext().store("MessageReceivedEvent", mgr.getBackwardsCommandSenderMapper().apply( + context.getCommandContext().getSender())); + context.getCommandContext().store("JDA", mgr.getJDA()); + } + +} diff --git a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/ChannelArgument.java b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/ChannelArgument.java new file mode 100644 index 00000000..0942972c --- /dev/null +++ b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/ChannelArgument.java @@ -0,0 +1,287 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// 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. +// +package cloud.commandframework.jda.parsers; + +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.context.CommandContext; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; + +/** + * Command Argument for {@link MessageChannel} + * + * @param Command sender type + */ +@SuppressWarnings("unused") +public final class ChannelArgument extends CommandArgument { + + private final List modes; + + private ChannelArgument( + final boolean required, final @NonNull String name, + final @NonNull List modes + ) { + super(required, name, new MessageParser<>(modes), MessageChannel.class); + this.modes = modes; + } + + /** + * Create a new builder + * + * @param name Name of the component + * @param Command sender type + * @return Created builder + */ + public static @NonNull Builder newBuilder(final @NonNull String name) { + return new Builder<>(name); + } + + /** + * Create a new required command component + * + * @param name Component name + * @param Command sender type + * @return Created component + */ + public static @NonNull CommandArgument of(final @NonNull String name) { + return ChannelArgument.newBuilder(name).asRequired().build(); + } + + /** + * Create a new optional command component + * + * @param name Component name + * @param Command sender type + * @return Created component + */ + public static @NonNull CommandArgument optional(final @NonNull String name) { + return ChannelArgument.newBuilder(name).asOptional().build(); + } + + /** + * Get the modes enabled on the parser + * + * @return List of Modes + */ + public @NotNull List getModes() { + return modes; + } + + + public enum ParserMode { + MENTION, + ID, + NAME + } + + + public static final class Builder extends CommandArgument.Builder { + + private List modes = new ArrayList<>(); + + protected Builder(final @NonNull String name) { + super(MessageChannel.class, name); + } + + /** + * Set the modes for the parsers to use + * + * @param modes List of Modes + * @return Builder instance + */ + public @NonNull Builder withParsers(final @NonNull List modes) { + this.modes = modes; + return this; + } + + /** + * Builder a new example component + * + * @return Constructed component + */ + @Override + public @NonNull ChannelArgument build() { + return new ChannelArgument<>(this.isRequired(), this.getName(), modes); + } + + } + + + public static final class MessageParser implements ArgumentParser { + + private final List modes; + + private MessageParser(final @NonNull List modes) { + this.modes = modes; + } + + @Override + public @NonNull ArgumentParseResult parse( + final @NonNull CommandContext commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + final String input = inputQueue.peek(); + if (input == null) { + return ArgumentParseResult.failure(new NullPointerException("No input was provided")); + } + + final MessageReceivedEvent event = commandContext.get("MessageReceivedEvent"); + Exception exception = null; + + if (modes.contains(ParserMode.MENTION)) { + if (input.startsWith("<#") || modes.size() == 1) { + final String id = input.substring(2, input.length() - 1); + + try { + final ArgumentParseResult channel = this.channelFromId(event, input, id); + inputQueue.remove(); + return channel; + } catch (final ChannelNotFoundException | NumberFormatException e) { + exception = e; + } + } + } + + if (modes.contains(ParserMode.ID)) { + try { + final ArgumentParseResult result = this.channelFromId(event, input, input); + inputQueue.remove(); + return result; + } catch (final ChannelNotFoundException | NumberFormatException e) { + exception = e; + } + } + + if (modes.contains(ParserMode.NAME)) { + final List channels = event.getGuild().getTextChannelsByName(input, true); + + if (channels.size() == 0) { + exception = new ChannelNotFoundException(input); + } else if (channels.size() > 1) { + exception = new TooManyChannelsFoundParseException(input); + } else { + inputQueue.remove(); + return ArgumentParseResult.success(channels.get(0)); + } + } + + assert exception != null; + return ArgumentParseResult.failure(exception); + } + + @Override + public boolean isContextFree() { + return true; + } + + private @NonNull ArgumentParseResult channelFromId( + final @NonNull MessageReceivedEvent event, + final @NonNull String input, + final @NonNull String id + ) + throws ChannelNotFoundException, NumberFormatException { + final MessageChannel channel = event.getGuild().getTextChannelById(id); + + if (channel == null) { + throw new ChannelNotFoundException(input); + } + + return ArgumentParseResult.success(channel); + } + + } + + + public static class ChannelParseException extends IllegalArgumentException { + + private final String input; + + /** + * Construct a new channel parse exception + * + * @param input String input + */ + public ChannelParseException(final @NonNull String input) { + this.input = input; + } + + /** + * Get the users input + * + * @return users input + */ + public final @NonNull String getInput() { + return input; + } + + } + + + public static final class TooManyChannelsFoundParseException extends ChannelParseException { + + /** + * Construct a new channel parse exception + * + * @param input String input + */ + public TooManyChannelsFoundParseException(final @NonNull String input) { + super(input); + } + + @Override + public @NonNull String getMessage() { + return String.format("Too many channels found for '%s'.", getInput()); + } + + } + + + public static final class ChannelNotFoundException extends ChannelParseException { + + /** + * Construct a new channel parse exception + * + * @param input String input + */ + public ChannelNotFoundException(final @NonNull String input) { + super(input); + } + + @Override + public @NonNull String getMessage() { + return String.format("Channel not found for '%s'.", getInput()); + } + + } + +} diff --git a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/UserArgument.java b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/UserArgument.java index 9d91b8ac..6ff192ca 100644 --- a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/UserArgument.java +++ b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/UserArgument.java @@ -48,9 +48,9 @@ public final class UserArgument extends CommandArgument { private UserArgument( final boolean required, final @NonNull String name, - final @NonNull JDA jda, final @NonNull List modes + final @NonNull List modes ) { - super(required, name, new UserParser<>(jda, modes), User.class); + super(required, name, new UserParser<>(modes), User.class); this.modes = modes; } @@ -58,36 +58,33 @@ public final class UserArgument extends CommandArgument { * Create a new builder * * @param name Name of the component - * @param jda JDA instance * @param Command sender type * @return Created builder */ - public static @NonNull Builder newBuilder(final @NonNull String name, final @NonNull JDA jda) { - return new Builder<>(name, jda); + public static @NonNull Builder newBuilder(final @NonNull String name) { + return new Builder<>(name); } /** * Create a new required command component * * @param name Component name - * @param jda JDA instance * @param Command sender type * @return Created component */ - public static @NonNull CommandArgument of(final @NonNull String name, final @NonNull JDA jda) { - return UserArgument.newBuilder(name, jda).asRequired().build(); + public static @NonNull CommandArgument of(final @NonNull String name) { + return UserArgument.newBuilder(name).asRequired().build(); } /** * Create a new optional command component * * @param name Component name - * @param jda JDA instance * @param Command sender type * @return Created component */ - public static @NonNull CommandArgument optional(final @NonNull String name, final @NonNull JDA jda) { - return UserArgument.newBuilder(name, jda).asOptional().build(); + public static @NonNull CommandArgument optional(final @NonNull String name) { + return UserArgument.newBuilder(name).asOptional().build(); } /** @@ -109,12 +106,10 @@ public final class UserArgument extends CommandArgument { public static final class Builder extends CommandArgument.Builder { - private final JDA jda; private List modes = new ArrayList<>(); - protected Builder(final @NonNull String name, final @NonNull JDA jda) { + protected Builder(final @NonNull String name) { super(User.class, name); - this.jda = jda; } /** @@ -135,7 +130,7 @@ public final class UserArgument extends CommandArgument { */ @Override public @NonNull UserArgument build() { - return new UserArgument<>(this.isRequired(), this.getName(), jda, modes); + return new UserArgument<>(this.isRequired(), this.getName(), modes); } } @@ -143,11 +138,9 @@ public final class UserArgument extends CommandArgument { public static final class UserParser implements ArgumentParser { - private final JDA jda; private final List modes; - private UserParser(final @NonNull JDA jda, final @NonNull List modes) { - this.jda = jda; + private UserParser(final @NonNull List modes) { this.modes = modes; } @@ -161,10 +154,11 @@ public final class UserArgument extends CommandArgument { return ArgumentParseResult.failure(new NullPointerException("No input was provided")); } + final JDA jda = commandContext.get("JDA"); Exception exception = null; if (modes.contains(ParserMode.MENTION)) { - if (input.endsWith(">")) { + if (input.endsWith(">") || modes.size() == 1) { final String id; if (input.startsWith("<@!")) { id = input.substring(3, input.length() - 1); @@ -173,7 +167,7 @@ public final class UserArgument extends CommandArgument { } try { - final ArgumentParseResult result = this.userFromId(input, id); + final ArgumentParseResult result = this.userFromId(jda, input, id); inputQueue.remove(); return result; } catch (final UserNotFoundParseException | NumberFormatException e) { @@ -184,7 +178,7 @@ public final class UserArgument extends CommandArgument { if (modes.contains(ParserMode.ID)) { try { - final ArgumentParseResult result = this.userFromId(input, input); + final ArgumentParseResult result = this.userFromId(jda, input, input); inputQueue.remove(); return result; } catch (final UserNotFoundParseException | NumberFormatException e) { @@ -214,7 +208,10 @@ public final class UserArgument extends CommandArgument { return true; } - private @NonNull ArgumentParseResult userFromId(final @NonNull String input, final @NonNull String id) + private @NonNull ArgumentParseResult userFromId( + final @NonNull JDA jda, final @NonNull String input, + final @NonNull String id + ) throws UserNotFoundParseException, NumberFormatException { final User user = jda.getUserById(id); @@ -233,7 +230,7 @@ public final class UserArgument extends CommandArgument { private final String input; /** - * Construct a new UUID parse exception + * Construct a new user parse exception * * @param input String input */ @@ -256,7 +253,7 @@ public final class UserArgument extends CommandArgument { public static final class TooManyUsersFoundParseException extends UserParseException { /** - * Construct a new UUID parse exception + * Construct a new user parse exception * * @param input String input */ @@ -275,7 +272,7 @@ public final class UserArgument extends CommandArgument { public static final class UserNotFoundParseException extends UserParseException { /** - * Construct a new UUID parse exception + * Construct a new user parse exception * * @param input String input */