Skip to content

Commit

Permalink
Added optional @Description to allow for descriptions on arguments/…
Browse files Browse the repository at this point in the history
…options. Modified `@Instantiate` to be applied on fields as well as methods in the main class.
  • Loading branch information
joshbker committed May 15, 2023
1 parent bdd0bbb commit 3f1bd4a
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 31 deletions.
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ new Neptune.Builder(jda, this)
*Note: If you do not specify any guilds, commands will be registered/unregistered globally (this can take up to 2 hours due to a Discord limitation).*

## Commands
Below is a working example of how to register a slash command. Use the @Command annotation and specify the command name, description and the required permissions.
Below is a working example of how to register a slash command. Use the `@Command` annotation and specify the command name, description and the required permissions.

Attach a method, with any name, with SlashCommandInteractionEvent as the first parameter. This holds all of the slash command event data and where you can respond to the event hook. The following parameters will define the command arguments and their order. Allowed parameter types are Integer, Int (for Kotlin), String, Boolean, Double, User, Role, Channel and Attachment. For an optional argument, annotate with @Optional, and be sure to null check the value when returned. Please remember that Discord requires lower case option names, so in the event you provide anything uppercase Neptune will fix that for you.
Annotate a method with `@Command` (the method can be called anything). The first parameter must be the SlashCommandInteractionEvent, from there you can list your arguments/options as parameters like shown below.

> This example will register "/ban <user> [reason]".
Expand All @@ -29,10 +29,14 @@ Attach a method, with any name, with SlashCommandInteractionEvent as the first p
description = "Ban a member",
permissions = {Permission.MANAGE_CHANNEL, Permission.ADMINISTRATOR}
)
public void onBan(SlashCommandInteractionEvent event, User user, @Optional String reason) {
public void onBan(SlashCommandInteractionEvent event, @Description("The user you wish to mute") User user, @Optional String reason ){

}
```
Parameters can be annotated with the following:
* `@Optional` - Makes the argument not required
* `@Description` - Allows you to specify a description for the argument/option (if not specified will be name of argument)

Once a command is run, the method will return all values. As default slash command behaviour dictates, you will have 3 seconds to respond to the command through SlashCommandInteractionEvent. See the JDA wiki for [more info](https://github.com/DV8FromTheWorld/JDA/wiki/Interactions).

If you want to unregister commands, add the `clearCommands` to the Builder as `true`, and remove the `@Command` annotation from your command.
Expand All @@ -47,11 +51,16 @@ Due to limitations beyond our control, you **cannot create your own instance of

That begs the question: how do you use variables from within a command/listener without being able to pass them through a constructor? Well, Neptune offers a similar system to Spring Boot.

In your main class (the one you started Neptune in) you can setup a variable to be accessible across your project using something like this:
In your main class (the one you started Neptune in) you can setup a variable to be accessible across your project by annotating either a method or field, as shown below:

```java
@Instantiate
public TestClass testClass() { return new TestClass(); }
public TestClass testClass() {
return new TestClass();
}
// or
@Instantiate
private final TestClass testClass = new TestClass();
```
That object will then be accessible across your whole project. To use it elsewhere, create a variable with an identical name to the method and Neptune will beam the value through, as seen below:
```java
Expand Down Expand Up @@ -87,7 +96,7 @@ Maven
</dependency>
```

Gradle
Gradle (Kotlin DSL)
```kt
repositories {
maven("https://jitpack.io")
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/gg/flyte/neptune/annotation/Description.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gg.flyte.neptune.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Description {

String value() default "";

}
1 change: 1 addition & 0 deletions src/main/java/gg/flyte/neptune/annotation/Inject.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
@Target(ElementType.FIELD)
@SuppressWarnings("unused")
public @interface Inject {

}
3 changes: 2 additions & 1 deletion src/main/java/gg/flyte/neptune/annotation/Instantiate.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Instantiate {

}
1 change: 1 addition & 0 deletions src/main/java/gg/flyte/neptune/annotation/Optional.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Optional {

}
28 changes: 17 additions & 11 deletions src/main/java/gg/flyte/neptune/command/CommandDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,20 @@ private void unregisterCommands() {

private @NotNull Map<String, Object> getRequiredInstantiations(@NotNull Object mainClass) throws InvocationTargetException, IllegalAccessException {
Map<String, Object> toInject = new HashMap<>();
// Get all methods
for (Method method : mainClass.getClass().getMethods()) {
if (method.isAnnotationPresent(Instantiate.class)) {
toInject.put(method.getName(), method.invoke(mainClass));
}
}
LOGGER.debug("Found " + toInject.size() + " methods to inject with Neptune: " + toInject.keySet());
// Get all class fields
for (Field field : mainClass.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Instantiate.class)) {
field.setAccessible(true);
toInject.put(field.getName(), field.get(mainClass));
}
}
LOGGER.debug("Found " + toInject.size() + " methods/fields to inject with Neptune: " + toInject.keySet());
return toInject;
}

Expand Down Expand Up @@ -135,16 +143,12 @@ private void unregisterCommands() {
CommandMapping mapping = new CommandMapping(commandMethod, instance);

for (CommandMapping.NamedParameter param : mapping.getParameters())
commandData.addOption(ArgumentConverter.toOptionType(param.type()), lowercaseParameterName(param.name()), param.name(), param.required());
commandData.addOption(ArgumentConverter.toOptionType(param.type()), lowercaseParameterName(param.name()), param.description() == null ? param.name() : param.description(), param.required());

commandManager.addCommand(command.name(), mapping);
if (guilds != null) {
for (Guild guild : guilds) {
guild.upsertCommand(commandData).queue();
}
} else {
jda.upsertCommand(commandData).queue();
}

if (guilds.isEmpty()) jda.upsertCommand(commandData).queue();
else for (Guild guild : guilds) guild.upsertCommand(commandData).queue();

return instance;
}
Expand All @@ -153,8 +157,10 @@ private void unregisterCommands() {
if (registerAllListeners || usesInject(listenerClass)) {
Object listener = listenerClass.newInstance();
jda.addEventListener(listener);
if (registerAllListeners) LOGGER.debug("Automatically registered " + listenerClass.getSimpleName() + " as an EventListener.");
else LOGGER.debug("Registered " + listenerClass.getSimpleName() + " as an EventListener due to its @Inject usage.");
if (registerAllListeners)
LOGGER.debug("Automatically registered " + listenerClass.getSimpleName() + " as an EventListener.");
else
LOGGER.debug("Registered " + listenerClass.getSimpleName() + " as an EventListener due to its @Inject usage.");
return listener;
}
return null;
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/gg/flyte/neptune/command/CommandMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import com.thoughtworks.paranamer.AnnotationParanamer;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import gg.flyte.neptune.annotation.Description;
import gg.flyte.neptune.annotation.Optional;
import gg.flyte.neptune.util.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import static gg.flyte.neptune.Neptune.LOGGER;

public final class CommandMapping {
private final @NotNull Method method;
private final @NotNull Object classInstance;
Expand All @@ -24,11 +29,11 @@ public CommandMapping(@NotNull Method method, @NotNull Object classInstance) {

for (int i = 1; i < paramNames.length; i++) {
Parameter param = method.getParameters()[i];
parameters[i - 1] = new NamedParameter(paramNames[i], param.getType(), !param.isAnnotationPresent(Optional.class));
parameters[i - 1] = new NamedParameter(paramNames[i], param.getType(), !param.isAnnotationPresent(Optional.class), obtainDescription(paramNames[i], param));
}
}

record NamedParameter(@NotNull String name, @NotNull Class<?> type, boolean required) {
record NamedParameter(@NotNull String name, @NotNull Class<?> type, boolean required, @Nullable String description) {

}

Expand All @@ -43,4 +48,12 @@ record NamedParameter(@NotNull String name, @NotNull Class<?> type, boolean requ
public @NotNull Object getClassInstance() {
return classInstance;
}

private @Nullable String obtainDescription(@NotNull String parameterName, @NotNull Parameter parameter) {
if (!parameter.isAnnotationPresent(Description.class)) return null;
String description = parameter.getAnnotation(Description.class).value();
if (description.length() > 100) description = description.substring(0, 100);
LOGGER.debug("We trimmed your description for argument/option \"" + parameterName + "\" as it was more than 100 characters (a Discord limitation).");
return description;
}
}
6 changes: 2 additions & 4 deletions src/main/java/gg/flyte/neptune/util/ArgumentConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,25 @@ private ArgumentConverter() {

public static OptionType toOptionType(@NotNull Class<?> classType) {
if (classType.getName().equalsIgnoreCase("int") || classType == Integer.class) return OptionType.INTEGER;
if (classType == String.class) return OptionType.STRING;
if (classType == Boolean.class) return OptionType.BOOLEAN;
if (classType == Double.class) return OptionType.NUMBER;
if (classType == User.class) return OptionType.USER;
if (classType == Role.class) return OptionType.ROLE;
if (classType == Channel.class) return OptionType.CHANNEL;
if (classType == Message.Attachment.class) return OptionType.ATTACHMENT;
return null;
return OptionType.STRING;
}

public static Object toValue(OptionMapping option) {
return switch (option.getType()) {
case INTEGER -> option.getAsInt();
case STRING -> option.getAsString();
case BOOLEAN -> option.getAsBoolean();
case NUMBER -> option.getAsDouble();
case USER -> option.getAsUser();
case ROLE -> option.getAsRole();
case CHANNEL -> option.getAsChannel();
case ATTACHMENT -> option.getAsAttachment();
default -> null;
default -> option.getAsString();
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/test/java/org/example/MessageListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ public final class MessageListener extends ListenerAdapter {
@Inject
private MuteRegistry muteRegistry;

@Inject
private TestClass testClass;

@Override
public void onMessageReceived(MessageReceivedEvent event) {
testClass.test();
if (muteRegistry.isMuted(event.getAuthor())) event.getMessage().delete().queueAfter(3L, TimeUnit.SECONDS);
}
}
3 changes: 2 additions & 1 deletion src/test/java/org/example/MuteCommand.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.example;

import gg.flyte.neptune.annotation.Command;
import gg.flyte.neptune.annotation.Description;
import gg.flyte.neptune.annotation.Inject;
import gg.flyte.neptune.annotation.Optional;
import net.dv8tion.jda.api.Permission;
Expand All @@ -16,7 +17,7 @@ public final class MuteCommand {
description = "Mutes a user!",
permissions = Permission.MODERATE_MEMBERS
)
public void onMute(SlashCommandInteractionEvent e, User user, @Optional String reason) {
public void onMute(SlashCommandInteractionEvent e, @Description("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") User user, @Optional String reason) {
muteRegistry.mute(user, reason == null ? "No reason given." : reason);
e.reply("Successfully muted " + user.getAsTag() + "!").queue();
}
Expand Down
7 changes: 5 additions & 2 deletions src/test/java/org/example/MyBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
import net.dv8tion.jda.api.requests.GatewayIntent;

public final class MyBot {
@Instantiate
private final TestClass testClass = new TestClass();

public MyBot() throws InterruptedException {
JDA jda = JDABuilder.createDefault("TOKEN")
JDA jda = JDABuilder.createDefault("OTgyNjAwNDk2MTg5Njc3NTY4.G4m6J3.fx7htjmaQgMUFxIPBZhI-tYDgLjw4cTUCjtofM")
.enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.MESSAGE_CONTENT, GatewayIntent.GUILD_MEMBERS)
.build()
.awaitReady();

new Neptune.Builder(jda, this)
.addGuilds(jda.getGuildById("GUILD_ID"))
.addGuilds(jda.getGuildById("1106292483228450937"))
.clearCommands(true)
.registerAllListeners(true)
.create();
Expand Down
7 changes: 3 additions & 4 deletions src/test/java/org/example/TestClass.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.example;

import gg.flyte.neptune.annotation.Inject;

public final class TestClass {
@Inject
private MuteRegistry muteRegistry;
public void test() {
System.out.println("test");
}
}

0 comments on commit 3f1bd4a

Please sign in to comment.