Skip to content

Commit ec0bbd6

Browse files
committed
Added support for slash command loading into :annotations module
1 parent 19ab3a1 commit ec0bbd6

File tree

6 files changed

+216
-0
lines changed

6 files changed

+216
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.javadiscord.jdi.core;
2+
3+
public enum CommandOptionType {
4+
SUB_COMMAND(1),
5+
SUB_COMMAND_GROUP(2),
6+
STRING(3),
7+
INTEGER(4),
8+
BOOLEAN(5),
9+
USER(6),
10+
CHANNEL(7),
11+
ROLE(8),
12+
MENTIONABLE(9),
13+
NUMBER(10),
14+
ATTACHMENT(11),
15+
;
16+
17+
private final int value;
18+
19+
CommandOptionType(int value) {
20+
this.value = value;
21+
}
22+
23+
public int getValue() {
24+
return value;
25+
}
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.javadiscord.jdi.core.annotations;
2+
3+
import java.lang.annotation.Retention;
4+
import java.lang.annotation.RetentionPolicy;
5+
import java.lang.annotation.Target;
6+
7+
import com.javadiscord.jdi.core.CommandOptionType;
8+
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target({})
11+
public @interface CommandOption {
12+
String name();
13+
14+
String description();
15+
16+
CommandOptionType type();
17+
18+
boolean required() default true;
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.javadiscord.jdi.core.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target({ElementType.METHOD})
10+
public @interface SlashCommand {
11+
String name();
12+
13+
String description();
14+
15+
CommandOption[] options() default {};
16+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.javadiscord.jdi.core.processor;
2+
3+
import java.lang.reflect.Method;
4+
5+
public record SlashCommandClassMethod(Class<?> clazz, Method method) {}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.javadiscord.jdi.core.processor;
2+
3+
import java.io.File;
4+
import java.lang.reflect.Constructor;
5+
import java.lang.reflect.Method;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
import com.javadiscord.jdi.core.annotations.SlashCommand;
10+
11+
import org.apache.logging.log4j.LogManager;
12+
import org.apache.logging.log4j.Logger;
13+
14+
public class SlashCommandLoader {
15+
private static final Logger LOGGER = LogManager.getLogger(SlashCommandLoader.class);
16+
private final Map<String, SlashCommandClassMethod> interactionListeners;
17+
private final SlashCommandValidator validator = new SlashCommandValidator();
18+
19+
public SlashCommandLoader(Map<String, SlashCommandClassMethod> interactionListeners) {
20+
this.interactionListeners = interactionListeners;
21+
loadInteractionListeners();
22+
}
23+
24+
private void loadInteractionListeners() {
25+
List<File> classes = ClassFileUtil.getClassesInClassPath();
26+
for (File classFile : classes) {
27+
try {
28+
29+
String name = ClassFileUtil.getClassName(classFile);
30+
if(name.contains("io.netty")
31+
|| name.contains("org.apache")
32+
|| name.contains("io.vertx")
33+
|| name.contains("com.fasterxml")) {
34+
continue;
35+
}
36+
37+
Class<?> clazz = Class.forName(name);
38+
39+
Method[] methods = clazz.getMethods();
40+
for (Method method : methods) {
41+
if (method.getAnnotation(SlashCommand.class) != null) {
42+
if (validator.validate(method) && hasZeroArgsConstructor(clazz)) {
43+
registerListener(
44+
clazz, method, method.getAnnotation(SlashCommand.class).name()
45+
);
46+
} else {
47+
LOGGER.error("{} failed validation", method.getName());
48+
}
49+
}
50+
}
51+
} catch (Exception | Error ignore) {
52+
/* Ignore */
53+
}
54+
}
55+
}
56+
57+
private void registerListener(Class<?> clazz, Method method, String name) {
58+
try {
59+
if (interactionListeners.containsKey(name)) {
60+
LOGGER.error(
61+
"Failed to register command {} from {} as that name already exists in {}",
62+
name,
63+
clazz.getName(),
64+
interactionListeners.get(name).getClass().getName()
65+
);
66+
return;
67+
}
68+
interactionListeners.put(name, new SlashCommandClassMethod(clazz, method));
69+
LOGGER.info("Found slash command handler {}", clazz.getName());
70+
} catch (Exception e) {
71+
LOGGER.error("Failed to create {} instance", clazz.getName(), e);
72+
}
73+
}
74+
75+
public SlashCommandClassMethod getSlashCommandClassMethod(String name) {
76+
return interactionListeners.get(name);
77+
}
78+
79+
private boolean hasZeroArgsConstructor(Class<?> clazz) {
80+
Constructor<?>[] constructors = clazz.getConstructors();
81+
for (Constructor<?> constructor : constructors) {
82+
if (constructor.getParameterCount() == 0) {
83+
return true;
84+
}
85+
}
86+
LOGGER.error("{} does not have a 0 arg constructor", clazz.getName());
87+
return false;
88+
}
89+
90+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.javadiscord.jdi.core.processor;
2+
3+
import java.lang.annotation.Annotation;
4+
import java.lang.reflect.Method;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import com.javadiscord.jdi.core.annotations.SlashCommand;
9+
10+
import org.apache.logging.log4j.LogManager;
11+
import org.apache.logging.log4j.Logger;
12+
13+
public class SlashCommandValidator {
14+
private static final Logger LOGGER = LogManager.getLogger(SlashCommandValidator.class);
15+
private static final Map<Class<? extends Annotation>, String[]> EXPECTED_PARAM_TYPES_MAP =
16+
new HashMap<>();
17+
18+
static {
19+
EXPECTED_PARAM_TYPES_MAP.put(
20+
SlashCommand.class,
21+
new String[] {
22+
"com.javadiscord.jdi.core.models.guild.Interaction",
23+
"com.javadiscord.jdi.core.Discord",
24+
"com.javadiscord.jdi.core.Guild"
25+
}
26+
);
27+
}
28+
29+
public boolean validate(Method method) {
30+
for (Map.Entry<Class<? extends Annotation>, String[]> entry : EXPECTED_PARAM_TYPES_MAP
31+
.entrySet()) {
32+
Class<? extends Annotation> annotationClass = entry.getKey();
33+
if (method.isAnnotationPresent(annotationClass)) {
34+
String[] expectedParamTypes = entry.getValue();
35+
if (method.getParameterCount() > 0) {
36+
Class<?>[] paramTypes = method.getParameterTypes();
37+
for (Class<?> type : paramTypes) {
38+
boolean isExpectedType = false;
39+
for (String expectedType : expectedParamTypes) {
40+
if (type.getName().equals(expectedType)) {
41+
isExpectedType = true;
42+
break;
43+
}
44+
}
45+
if (!isExpectedType) {
46+
LOGGER.error("Unexpected parameter found: {}", type.getName());
47+
return false;
48+
}
49+
}
50+
} else if (method.getParameterCount() != 0) {
51+
LOGGER.error(
52+
"{} does not have the expected parameter types", method.getName()
53+
);
54+
return false;
55+
}
56+
}
57+
}
58+
return true;
59+
}
60+
}

0 commit comments

Comments
 (0)