Skip to content

Commit

Permalink
feat(jda): add parsers for JDA types (#1)
Browse files Browse the repository at this point in the history
The parsers wrap the JDA option mappings.
  • Loading branch information
Citymonstret authored Jan 14, 2024
1 parent 2396266 commit 9ce4552
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public void onSlashCommandInteraction(final @NonNull SlashCommandInteractionEven
.guild(event.getGuild())
.replyCallback(event)
.interactionEvent(event)
.addAllOptionMappings(event.getOptions())
.build();
this.commandManager.commandExecutor().executeCommand(
this.commandManager.senderMapper().map(interaction),
Expand All @@ -93,6 +94,7 @@ public void onCommandAutoCompleteInteraction(final @NonNull CommandAutoCompleteI
.guild(event.getGuild())
.replyCallback(null)
.interactionEvent(null)
.addAllOptionMappings(event.getOptions())
.build();
event.replyChoices(
this.commandManager.suggestionFactory().suggestImmediately(
Expand All @@ -119,7 +121,12 @@ public void onCommandAutoCompleteInteraction(final @NonNull CommandAutoCompleteI
private @NonNull String extractCommandName(final @NonNull CommandInteractionPayload payload) {
final StringBuilder command = new StringBuilder(payload.getFullCommandName());
payload.getOptions().forEach(option -> {
command.append(" ").append(option.getAsString());
command.append(" ");
if (JDAOptionType.JDA_TYPES.stream().anyMatch(type -> type.value() == option.getType().getKey())) {
command.append(option.getName());
} else {
command.append(option.getAsString());
}
});
return command.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ public JDA5CommandManager(

this.discordSettings.set(DiscordSetting.AUTO_REGISTER_SLASH_COMMANDS, true);
this.registerDefaultExceptionHandlers();

this.parserRegistry()
.registerParser(JDAParser.userParser())
.registerParser(JDAParser.roleParser())
.registerParser(JDAParser.channelParser())
.registerParser(JDAParser.mentionableParser())
.registerParser(JDAParser.attachmentParser());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
//
package org.incendo.cloud.discord.jda5;

import java.util.List;
import java.util.Optional;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand Down Expand Up @@ -75,6 +78,23 @@ public interface JDAInteraction {
*/
@Nullable IReplyCallback replyCallback();

/**
* Returns the raw JDA option mappings.
*
* @return option mappings
*/
@NonNull List<@NonNull OptionMapping> optionMappings();

/**
* Returns the option mapping with the given {@code key}, if it exists.
*
* @param key mapping key
* @return the mapping
*/
default @NonNull Optional<@NonNull OptionMapping> getOptionMapping(final @NonNull String key) {
return this.optionMappings().stream().filter(mapping -> mapping.getName().equalsIgnoreCase(key)).findFirst();
}


/**
* Maps between {@link JDAInteraction} and {@link C}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// MIT License
//
// Copyright (c) 2024 Incendo
//
// 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 org.incendo.cloud.discord.jda5;

import io.leangen.geantyref.TypeToken;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.Channel;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.discord.slash.DiscordOptionType;

/**
* Extension of {@link DiscordOptionType} for JDA-specific classes.
*
* @since 1.0.0
*/
@API(status = API.Status.STABLE, since = "1.0.0")
public final class JDAOptionType {

public static final @NonNull DiscordOptionType<User> USER = DiscordOptionType.of(
"USER",
6,
TypeToken.get(User.class)
);
public static final @NonNull DiscordOptionType<Channel> CHANNEL = DiscordOptionType.of(
"CHANNEL",
7,
TypeToken.get(Channel.class)
);
public static final @NonNull DiscordOptionType<Role> ROLE = DiscordOptionType.of(
"ROLE",
8,
TypeToken.get(Role.class)
);
public static final @NonNull DiscordOptionType<IMentionable> MENTIONABLE = DiscordOptionType.of(
"MENTIONABLE",
9,
TypeToken.get(IMentionable.class)
);
public static final @NonNull DiscordOptionType<Message.Attachment> ATTACHMENT = DiscordOptionType.of(
"ATTACHMENT",
11,
TypeToken.get(Message.Attachment.class)
);

public static final Collection<@NonNull DiscordOptionType<?>> JDA_TYPES = Collections.unmodifiableCollection(
Arrays.asList(USER, CHANNEL, ROLE, MENTIONABLE, ATTACHMENT)
);

private JDAOptionType() {
}
}
130 changes: 130 additions & 0 deletions cloud-jda5/src/main/java/org/incendo/cloud/discord/jda5/JDAParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//
// MIT License
//
// Copyright (c) 2024 Incendo
//
// 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 org.incendo.cloud.discord.jda5;

import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserDescriptor;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandInput;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;

/**
* A parser which wraps a JDA {@link OptionMapping}.
*
* @param <C> command sender type
* @param <T> JDA type
* @since 1.0.0
*/
@FunctionalInterface
@API(status = API.Status.STABLE, since = "1.0.0")
public interface JDAParser<C, T> extends ArgumentParser<C, T> {

/**
* Returns a parser which extracts a {@link User}.
*
* @param <C> command sender type
* @return the parser
*/
static <C> @NonNull ParserDescriptor<C, User> userParser() {
return ParserDescriptor.of((JDAParser<C, User>) mapping -> ArgumentParseResult.success(mapping.getAsUser()), User.class);
}

/**
* Returns a parser which extracts a {@link Channel}.
*
* @param <C> command sender type
* @return the parser
*/
static <C> @NonNull ParserDescriptor<C, Channel> channelParser() {
return ParserDescriptor.of(
(JDAParser<C, Channel>) mapping -> ArgumentParseResult.success(mapping.getAsChannel()),
Channel.class
);
}

/**
* Returns a parser which extracts a {@link Role}.
*
* @param <C> command sender type
* @return the parser
*/
static <C> @NonNull ParserDescriptor<C, Role> roleParser() {
return ParserDescriptor.of(
(JDAParser<C, Role>) mapping -> ArgumentParseResult.success(mapping.getAsRole()),
Role.class
);
}

/**
* Returns a parser which extracts a {@link IMentionable}.
*
* @param <C> command sender type
* @return the parser
*/
static <C> @NonNull ParserDescriptor<C, IMentionable> mentionableParser() {
return ParserDescriptor.of(
(JDAParser<C, IMentionable>) mapping -> ArgumentParseResult.success(mapping.getAsMentionable()),
IMentionable.class
);
}

/**
* Returns a parser which extracts an {@link Message.Attachment}.
*
* @param <C> command sender type
* @return the parser
*/
static <C> @NonNull ParserDescriptor<C, Message.Attachment> attachmentParser() {
return ParserDescriptor.of(
(JDAParser<C, Message.Attachment>) mapping -> ArgumentParseResult.success(mapping.getAsAttachment()),
Message.Attachment.class
);
}

/**
* Returns the result of extracting the argument from the given {@code mapping}.
*
* @param mapping JDA option mapping
* @return the result
*/
@NonNull ArgumentParseResult<T> extract(@NonNull OptionMapping mapping);

@Override
default @NonNull ArgumentParseResult<@NonNull T> parse(
final @NonNull CommandContext<@NonNull C> commandContext,
final @NonNull CommandInput commandInput
) {
final JDAInteraction interaction = commandContext.get(JDA5CommandManager.CONTEXT_JDA_INTERACTION);
final OptionMapping mapping = interaction.getOptionMapping(commandInput.readString()).orElse(null);
return this.extract(mapping);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ public StandardJDACommandFactory(final @NonNull CommandTree<C> commandTree) {
this.commandTree = commandTree;

final OptionRegistry<C> optionRegistry = new StandardOptionRegistry<>();
optionRegistry
.registerMapping(JDAOptionType.USER, JDAParser.userParser())
.registerMapping(JDAOptionType.CHANNEL, JDAParser.channelParser())
.registerMapping(JDAOptionType.ROLE, JDAParser.roleParser())
.registerMapping(JDAOptionType.MENTIONABLE, JDAParser.mentionableParser())
.registerMapping(JDAOptionType.ATTACHMENT, JDAParser.attachmentParser());

this.discordCommandFactory = new StandardDiscordCommandFactory<>(optionRegistry);

this.nodeProcessor = new NodeProcessor<>(this.commandTree);
Expand Down

0 comments on commit 9ce4552

Please sign in to comment.