Skip to content

Commit

Permalink
Merge pull request #4 from devous/master
Browse files Browse the repository at this point in the history
Go back to using Reflections to fix some weird issues... also add det…
  • Loading branch information
stephendotgg authored Apr 1, 2023
2 parents e5af310 + 08f98e0 commit 4286151
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 124 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ Neptune.start(jda, this, guild);
```
Or for multiple guilds:
```java
Neptune.start(jda, this, guild1, guild2 ...);
Neptune.start(jda, this, guild1, guild2, ...);
```

You can also choose to wipe all registered commands by specifying a boolean after your class reference. For example:
```java
Neptune.start(jda, this, true, ...);
```
Note that if you specify a guild, having this boolean as true will wipe all commands at a guild level. If you do specify any guilds, it will wipe them at a global level.

## Examples
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, String, Boolean, Double, User, Role, Channel and Attachment. For an optional argument, annotate with @Optional, and ensure to null check the value when returned.
Expand All @@ -41,11 +48,13 @@ Once a command is run, the method will return all values. As default slash comma

To unregister a command, simply remove any @Command reference to it and restart your Bot. It will automatically unregister globally/on the guild(s).

You can place your commands in any class within your package. It is important to **not instantiate classes which contain @Command** (except your main class, which we know exists), Neptune will do it. If you need data or outside variables in your command classes, we offer a similar system to Spring Boot. In your main class (the one you passed into Neptune#start) you can setup a variable to be accessable across your project using:
You can place your commands in any class within your package. It is important to **not instantiate classes which contain @Command** (except your main class, which we know exists), Neptune will do it. If you need data or outside variables in your command classes, we offer a similar system to Spring Boot. In your main class (the one you passed into Neptune#start) you can setup a variable to be accessible across your project using:

```java
private final TestClass testClass = new TestClass();

@Instantiate
public TestClass testClass() { return new TestClass(); }
public TestClass testClass() { return 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 @@ -75,7 +84,7 @@ Maven
<dependency>
<groupId>com.github.sttephen</groupId>
<artifactId>neptune</artifactId>
<version>1.1</version>
<version>1.2</version>
</dependency>
```

Expand All @@ -86,7 +95,7 @@ repositories {
}

dependencies {
implementation("com.github.sttephen:neptune:1.1")
implementation("com.github.sttephen:neptune:1.2")
}
```

Expand All @@ -101,7 +110,8 @@ This framework was made for the team at [Flyte](https://flyte.gg), but I decided



- Created by [Stephen (sttephen)](https://www.github.com/sttephen)
- Created by [Stephen (sttephen)](https://github.com/sttephen)
- Modified and listener instantiation by [Josh (devous)](https://github.com/devous)


## License
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.0.0-alpha.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
Expand Down
29 changes: 21 additions & 8 deletions src/main/java/gg/stephen/neptune/Neptune.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,49 @@

import gg.stephen.neptune.command.CommandDispatcher;
import gg.stephen.neptune.command.CommandManager;
import gg.stephen.neptune.util.Logger;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;

public class Neptune {

private static JDA jda;
private static CommandManager manager;
public static final Logger LOGGER = new Logger();

private Neptune(JDA jda, Object clazz, Guild... guilds) {
this.jda = jda;
private Neptune(JDA jda, Object clazz, boolean clearCommands, Guild... guilds) {
double start = System.currentTimeMillis();
LOGGER.info("Starting Neptune...");
Neptune.jda = jda;
jda.addEventListener(manager = new CommandManager());
try {
new CommandDispatcher(jda, clazz, manager, guilds);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException | ClassNotFoundException x) {
System.out.println("[Neptune] Error registering commands. Did you read the README.md?");
new CommandDispatcher(jda, clazz, manager, clearCommands, guilds);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException |
ClassNotFoundException x) {
LOGGER.error("Error registering commands. Did you read the README.md?");
x.printStackTrace();
}
LOGGER.info("Finished enabling, registered " + manager.size() + " commands in " + new DecimalFormat("#.###").format((System.currentTimeMillis() - start) / 1000D) + "s.");
}

public static Neptune start(JDA jda, Object clazz) { return new Neptune(jda, clazz); }
public static Neptune start(JDA jda, Object clazz, Guild... guilds) {
return new Neptune(jda, clazz, false, guilds);
}

public static Neptune start(JDA jda, Object clazz, Guild... guilds) { return new Neptune(jda, clazz, guilds); }
public static Neptune start(JDA jda, Object clazz, boolean clearCommands, Guild... guilds) {
return new Neptune(jda, clazz, clearCommands, guilds);
}

public static void terminate() {
LOGGER.info("Terminating Neptune...");
jda.removeEventListener(manager);
LOGGER.info("Removed JDA event listener.");
manager.terminate();
System.gc();
LOGGER.info("Terminated.");
}

}
81 changes: 55 additions & 26 deletions src/main/java/gg/stephen/neptune/command/CommandDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,99 @@
import gg.stephen.neptune.annotation.Inject;
import gg.stephen.neptune.annotation.Instantiate;
import gg.stephen.neptune.util.ArgumentConverter;
import gg.stephen.neptune.util.ClassFinder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

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

public class CommandDispatcher {

public CommandDispatcher(JDA jda, Object clazz, CommandManager manager, Guild... guilds) throws InstantiationException, IllegalAccessException, InvocationTargetException, IOException, ClassNotFoundException {
if (guilds != null) {
for (Guild guild : guilds) {
for (net.dv8tion.jda.api.interactions.commands.Command command : guild.retrieveCommands().complete()) {
public CommandDispatcher(JDA jda, Object clazz, CommandManager manager, boolean clearCommands, Guild... guilds) throws InstantiationException, IllegalAccessException, InvocationTargetException, IOException, ClassNotFoundException {
LOGGER.debug("Instantiating dispatcher...");
if (clearCommands) {
if (guilds != null) {
LOGGER.debug("Specified guilds, clearing all guild commands....");
int amount = 0;
for (Guild guild : guilds) {
for (net.dv8tion.jda.api.interactions.commands.Command command : guild.retrieveCommands().complete()) {
command.delete().complete();
LOGGER.debug("Deleted " + command + " in " + guild + ".");
amount++;
}
}
LOGGER.debug("Cleared " + amount + " commands across " + guilds.length + " guilds.");
} else {
LOGGER.debug("No guilds were specified, clearing all commands globally...");
int amount = 0;
for (net.dv8tion.jda.api.interactions.commands.Command command : jda.retrieveCommands().complete()) {
command.delete().complete();
LOGGER.debug("Deleted " + command + " globally.");
amount++;
}
}
} else {
for (net.dv8tion.jda.api.interactions.commands.Command command : jda.retrieveCommands().complete()) {
command.delete().complete();
LOGGER.debug("Cleared " + amount + " commands globally.");
}
}

Class<?> classType = clazz.getClass();

List<ClassLoader> classLoadersList = new LinkedList<>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

HashMap<String, Object> toInject = new HashMap<>();
for (Method method : classType.getMethods()) {
if (method.isAnnotationPresent(Instantiate.class)) {
toInject.put(method.getName(), method.invoke(clazz, null));
}
}

Class[] classes = ClassFinder.getClasses(classType.getPackage().getName());
for (Class<?> foundClass : classes) {
LOGGER.debug("Found " + toInject.size() + " methods to inject with Neptune: " + toInject.keySet());

String packageName = classType.getPackage().getName();
LOGGER.debug("Scanning classes with related package \"" + packageName + "\".");

Reflections reflections = new Reflections(new ConfigurationBuilder()
.forPackages(packageName)
.setScanners(new SubTypesScanner(false), new ResourcesScanner())
);
Set<Class<?>> objects = reflections.getSubTypesOf(Object.class).stream()
.filter(aClass -> aClass.getPackage().getName().startsWith(packageName))
.collect(Collectors.toSet());
Set<Class<? extends ListenerAdapter>> listeners = reflections.getSubTypesOf(ListenerAdapter.class).stream()
.filter(aClass -> aClass.getPackage().getName().startsWith(packageName))
.collect(Collectors.toSet());

LOGGER.debug("Found " + objects.size() + " objects.");
LOGGER.debug("Found " + listeners.size() + " listeners.");

objects.addAll(listeners);

for (Class<?> foundClass : objects) {
Object instance = foundClass.getName().equals(clazz.getClass().getName()) ? clazz : null;
for (Method method : foundClass.getMethods()) {
if (method.isAnnotationPresent(Command.class)) {
if (instance == null) {
instance = foundClass.newInstance();
}
if (instance == null) instance = foundClass.newInstance();

Command command = method.getAnnotation(Command.class);
SlashCommandData commandData = Commands.slash(command.name(), command.description());
commandData.setDefaultPermissions(DefaultMemberPermissions.enabledFor(command.permissions()));
CommandMapping mapping = new CommandMapping(method, instance);
for (CommandMapping.NamedParameter param : mapping.getParameters()) {

for (CommandMapping.NamedParameter param : mapping.getParameters())
commandData.addOption(ArgumentConverter.toOptionType(param.getType()), param.getName(), param.getName(), param.isRequired());
}


manager.addCommand(command.name(), mapping);
if (guilds != null) {
Expand All @@ -84,21 +112,22 @@ public CommandDispatcher(JDA jda, Object clazz, CommandManager manager, Guild...
for (Field field : foundClass.getDeclaredFields()) {
if (instance == null && field.isAnnotationPresent(Inject.class)) {
instance = foundClass.newInstance();
LOGGER.debug("Instantiated " + instance.getClass().getSimpleName() + " due to it's @Inject annotation usage.");
}
}

if (instance != null) {
for (Field field : instance.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
System.out.println("injected " + field.getName() + " into " + instance.getClass().getSimpleName());
LOGGER.debug("Injected " + field.getName() + " into " + instance.getClass().getSimpleName() + ".");
field.setAccessible(true);
field.set(instance, toInject.get(field.getName()));
}
}

if (instance.getClass().getSuperclass().getName().equals("net.dv8tion.jda.api.hooks.ListenerAdapter")) {
jda.addEventListener(instance);
System.out.println("added " + instance.getClass().getSimpleName() + " as an EventListener");
LOGGER.debug("Automatically registered " + instance.getClass().getSimpleName() + " as an EventListener due to it having an injected dependency.");
}
}
}
Expand Down
20 changes: 15 additions & 5 deletions src/main/java/gg/stephen/neptune/command/CommandManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@
import java.util.HashMap;
import java.util.Map;

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

public class CommandManager extends ListenerAdapter {

private Map<String, CommandMapping> commands = new HashMap<>();
private final Map<String, CommandMapping> commands = new HashMap<>();

public void addCommand(String command, CommandMapping mapping) {
commands.put(command, mapping);
}

public void addCommand(String command, CommandMapping mapping) { commands.put(command, mapping); }
public int size() {
return commands.size();
}

@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent e) {

if (commands.containsKey(e.getName())) {
CommandMapping mapping = commands.get(e.getName());
Object[] paramValues = new Object[mapping.getParameters().length + 1];
Expand All @@ -30,13 +37,16 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent e) {
try {
mapping.getMethod().invoke(mapping.getClassInstance(), paramValues);
} catch (Exception x) {
System.out.println("[Neptune] Error triggering '" + e.getCommandString() + "' command. Did you read the README.md?");
LOGGER.error("Error triggering '" + e.getCommandString() + "' command. Did you read the README.md?");
x.printStackTrace();
}
}

}

public void terminate() { commands.clear(); }
public void terminate() {
commands.clear();
LOGGER.info("Cleared all commands.");
}

}
28 changes: 22 additions & 6 deletions src/main/java/gg/stephen/neptune/command/CommandMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ protected NamedParameter(String name, Class<?> type, boolean required) {
this.required = required;
}

public String getName() { return name; }
public Class<?> getType() { return type; }
public boolean isRequired() { return required; }
public String getName() {
return name;
}

public Class<?> getType() {
return type;
}

public boolean isRequired() {
return required;
}

}

Expand All @@ -43,8 +51,16 @@ public CommandMapping(Method method, Object classInstance) {
}
}

public Method getMethod() { return method; }
public NamedParameter[] getParameters() { return parameters; }
public Object getClassInstance() { return classInstance; }
public Method getMethod() {
return method;
}

public NamedParameter[] getParameters() {
return parameters;
}

public Object getClassInstance() {
return classInstance;
}

}
Loading

0 comments on commit 4286151

Please sign in to comment.