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 a4cc95e9..ed8e542b 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 @@ -28,6 +28,7 @@ import cloud.commandframework.CommandTree; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.internal.CommandRegistrationHandler; import cloud.commandframework.jda.parsers.ChannelArgument; +import cloud.commandframework.jda.parsers.RoleArgument; import cloud.commandframework.jda.parsers.UserArgument; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; @@ -35,6 +36,7 @@ import io.leangen.geantyref.TypeToken; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import org.checkerframework.checker.nullness.qual.NonNull; @@ -111,6 +113,10 @@ public class JDACommandManager extends CommandManager { new ChannelArgument.MessageParser<>( new HashSet<>(Arrays.asList(ChannelArgument.ParserMode.values())) )); + this.getParserRegistry().registerParserSupplier(TypeToken.get(Role.class), parserParameters -> + new RoleArgument.RoleParser<>( + new HashSet<>(Arrays.asList(RoleArgument.ParserMode.values())) + )); } /** diff --git a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/RoleArgument.java b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/RoleArgument.java new file mode 100644 index 00000000..19043104 --- /dev/null +++ b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/parsers/RoleArgument.java @@ -0,0 +1,321 @@ +// +// MIT License +// +// Copyright (c) 2021 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 cloud.commandframework.exceptions.parsing.NoInputProvidedException; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +/** + * Command Argument for {@link net.dv8tion.jda.api.entities.Role} + * + * @param Command sender type + */ +@SuppressWarnings("unused") +public final class RoleArgument extends CommandArgument { + + private final Set modes; + + private RoleArgument( + final boolean required, + final @NonNull String name, + final @NonNull Set modes + ) { + super(required, name, new RoleParser<>(modes), Role.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 RoleArgument.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 RoleArgument.newBuilder(name).asOptional().build(); + } + + /** + * Get the modes enabled on the parser + * + * @return List of Modes + */ + public @NotNull Set getModes() { + return modes; + } + + + public enum ParserMode { + MENTION, + ID, + NAME + } + + + public static final class Builder extends CommandArgument.Builder { + + private Set modes = new HashSet<>(); + + private Builder(final @NonNull String name) { + super(Role.class, name); + } + + /** + * Set the modes for the parsers to use + * + * @param modes List of Modes + * @return Builder instance + */ + public @NonNull Builder withParsers(final @NonNull Set modes) { + this.modes = modes; + return this; + } + + /** + * Builder a new example component + * + * @return Constructed component + */ + @Override + public @NonNull RoleArgument build() { + return new RoleArgument<>(this.isRequired(), this.getName(), modes); + } + + } + + + public static final class RoleParser implements ArgumentParser { + + private final Set modes; + + /** + * Construct a new argument parser for {@link Role} + * + * @param modes List of parsing modes to use when parsing + * @throws IllegalStateException If no parsing modes were provided + */ + public RoleParser(final @NonNull Set modes) { + if (modes.isEmpty()) { + throw new IllegalArgumentException("At least one parsing mode is required"); + } + + 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 NoInputProvidedException( + RoleParser.class, + commandContext + )); + } + + if (!commandContext.contains("MessageReceivedEvent")) { + return ArgumentParseResult.failure(new IllegalStateException( + "MessageReceivedEvent was not in the command context." + )); + } + + final MessageReceivedEvent event = commandContext.get("MessageReceivedEvent"); + Exception exception = null; + + if (!event.isFromGuild()) { + return ArgumentParseResult.failure(new IllegalArgumentException("Role arguments can only be parsed in guilds")); + } + + if (modes.contains(ParserMode.MENTION)) { + if (input.startsWith("<@&") && input.endsWith(">")) { + final String id = input.substring(3, input.length() - 1); + + try { + final ArgumentParseResult role = this.roleFromId(event, input, id); + inputQueue.remove(); + return role; + } catch (final RoleNotFoundException | NumberFormatException e) { + exception = e; + } + } else { + exception = new IllegalArgumentException( + String.format("Input '%s' is not a role mention.", input) + ); + } + } + + if (modes.contains(ParserMode.ID)) { + try { + final ArgumentParseResult result = this.roleFromId(event, input, input); + inputQueue.remove(); + return result; + } catch (final RoleNotFoundException | NumberFormatException e) { + exception = e; + } + } + + if (modes.contains(ParserMode.NAME)) { + final List roles = event.getGuild().getRolesByName(input, true); + + if (roles.size() == 0) { + exception = new RoleNotFoundException(input); + } else if (roles.size() > 1) { + exception = new TooManyRolesFoundParseException(input); + } else { + inputQueue.remove(); + return ArgumentParseResult.success(roles.get(0)); + } + } + + assert exception != null; + return ArgumentParseResult.failure(exception); + } + + @Override + public boolean isContextFree() { + return true; + } + + private @NonNull ArgumentParseResult roleFromId( + final @NonNull MessageReceivedEvent event, + final @NonNull String input, + final @NonNull String id + ) + throws RoleNotFoundException, NumberFormatException { + final Role role = event.getGuild().getRoleById(id); + + if (role == null) { + throw new RoleNotFoundException(input); + } + + return ArgumentParseResult.success(role); + } + + } + + + public static class RoleParseException extends IllegalArgumentException { + + private static final long serialVersionUID = -2451548379508062135L; + private final String input; + + /** + * Construct a new role parse exception + * + * @param input String input + */ + public RoleParseException(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 TooManyRolesFoundParseException extends RoleParseException { + + private static final long serialVersionUID = -8604082973199995006L; + + /** + * Construct a new role parse exception + * + * @param input String input + */ + public TooManyRolesFoundParseException(final @NonNull String input) { + super(input); + } + + @Override + public @NonNull String getMessage() { + return String.format("Too many roles found for '%s'.", getInput()); + } + + } + + + public static final class RoleNotFoundException extends RoleParseException { + + private static final long serialVersionUID = 7931804739792920510L; + + /** + * Construct a new role parse exception + * + * @param input String input + */ + public RoleNotFoundException(final @NonNull String input) { + super(input); + } + + @Override + public @NonNull String getMessage() { + return String.format("Role not found for '%s'.", getInput()); + } + + } + +}