Skip to content

Commit e2445d5

Browse files
committed
Added logic into :core to register slash commands with discord and invoke slash command handlers
1 parent 842e934 commit e2445d5

File tree

5 files changed

+224
-13
lines changed

5 files changed

+224
-13
lines changed

core/src/main/java/com/javadiscord/jdi/core/Discord.java

Lines changed: 158 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
package com.javadiscord.jdi.core;
22

3+
import java.lang.annotation.Annotation;
34
import java.lang.reflect.Constructor;
4-
import java.lang.reflect.InvocationTargetException;
5+
import java.lang.reflect.Method;
56
import java.lang.reflect.Parameter;
67
import java.net.URI;
78
import java.net.http.HttpClient;
89
import java.net.http.HttpRequest;
910
import java.net.http.HttpResponse;
1011
import java.util.ArrayList;
12+
import java.util.HashMap;
1113
import java.util.List;
14+
import java.util.Map;
1215
import java.util.concurrent.ExecutorService;
1316
import java.util.concurrent.Executors;
1417
import java.util.concurrent.TimeUnit;
1518

19+
import com.javadiscord.jdi.core.api.builders.command.CommandBuilder;
20+
import com.javadiscord.jdi.core.api.builders.command.CommandOption;
21+
import com.javadiscord.jdi.core.api.builders.command.CommandOptionType;
22+
import com.javadiscord.jdi.core.interaction.InteractionEventHandler;
23+
import com.javadiscord.jdi.core.models.ready.ReadyEvent;
1624
import com.javadiscord.jdi.internal.api.DiscordRequest;
1725
import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher;
1826
import com.javadiscord.jdi.internal.api.DiscordResponseFuture;
27+
import com.javadiscord.jdi.internal.api.application_commands.CreateCommandRequest;
1928
import com.javadiscord.jdi.internal.cache.Cache;
2029
import com.javadiscord.jdi.internal.cache.CacheType;
2130
import com.javadiscord.jdi.internal.gateway.*;
@@ -43,10 +52,13 @@ public class Discord {
4352
private final GatewaySetting gatewaySetting;
4453
private final Cache cache;
4554
private final List<Object> annotatedEventListeners = new ArrayList<>();
55+
private final Map<String, Object> loadedSlashCommands = new HashMap<>();
4656
private final List<EventListener> eventListeners = new ArrayList<>();
57+
private final List<CommandBuilder> createInteractionRequests = new ArrayList<>();
4758

4859
private WebSocketManager webSocketManager;
49-
private Object listenerLoader;
60+
private long applicationId;
61+
private boolean started = false;
5062

5163
public Discord(String botToken) {
5264
this(
@@ -101,11 +113,66 @@ public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) {
101113
this.identifyRequest = identifyRequest;
102114
this.cache = cache;
103115
if (annotationLibPresent()) {
104-
LOGGER.info("Annotation lib is present, loading annotations listeners...");
116+
LOGGER.info("Annotation lib is present");
105117
loadAnnotations();
118+
loadSlashCommands();
119+
registerLoadedAnnotationsWithDiscord();
106120
}
107121
}
108122

123+
private void registerLoadedAnnotationsWithDiscord() {
124+
LOGGER.info("Registering slash commands with Discord");
125+
loadedSlashCommands.forEach((commandName, slashCommandClassInstance) -> {
126+
try {
127+
Class<?> slashCommandClassInstanceClass = slashCommandClassInstance.getClass();
128+
Method method = (Method) slashCommandClassInstanceClass
129+
.getMethod("method")
130+
.invoke(slashCommandClassInstance);
131+
132+
Annotation[] annotations = method.getAnnotations();
133+
for (Annotation annotation : annotations) {
134+
if(annotation.annotationType().getName().equals("com.javadiscord.jdi.core.annotations.SlashCommand")) {
135+
Method nameMethod = annotation.annotationType().getMethod("name");
136+
String name = (String) nameMethod.invoke(annotation);
137+
138+
Method descriptionMethod = annotation.annotationType().getMethod("description");
139+
String description = (String) descriptionMethod.invoke(annotation);
140+
141+
Method optionsMethod = annotation.annotationType().getMethod("options");
142+
Object[] options = (Object[]) optionsMethod.invoke(annotation);
143+
144+
CommandBuilder builder = new CommandBuilder(name, description);
145+
146+
for (Object option : options) {
147+
Method optionNameMethod = option.getClass().getMethod("name");
148+
String optionName = (String) optionNameMethod.invoke(option);
149+
150+
Method optionDescriptionMethod = option.getClass().getMethod("description");
151+
String optionDescription = (String) optionDescriptionMethod.invoke(option);
152+
153+
Method optionTypeMethod = option.getClass().getMethod("type");
154+
Enum<?> optionType = (Enum<?>) optionTypeMethod.invoke(option);
155+
String optionTypeValue = optionType.name();
156+
157+
Method optionRequiredMethod = option.getClass().getMethod("required");
158+
boolean optionRequired = (boolean) optionRequiredMethod.invoke(option);
159+
160+
builder.addOption(new CommandOption(
161+
optionName,
162+
optionDescription,
163+
CommandOptionType.fromName(optionTypeValue),
164+
optionRequired));
165+
}
166+
167+
createInteractionRequests.add(builder);
168+
}
169+
}
170+
} catch (Exception e) {
171+
LOGGER.error("Error registering slash command with Discord", e);
172+
}
173+
});
174+
}
175+
109176
private boolean annotationLibPresent() {
110177
try {
111178
Class.forName("com.javadiscord.jdi.core.processor.ListenerLoader");
@@ -116,28 +183,45 @@ private boolean annotationLibPresent() {
116183
}
117184

118185
private void loadAnnotations() {
186+
LOGGER.info("Loading EventListeners");
119187
try {
120188
Class<?> clazz = Class.forName("com.javadiscord.jdi.core.processor.ListenerLoader");
121189
for (Constructor<?> constructor : clazz.getConstructors()) {
122190
if (constructor.getParameterCount() == 1) {
123191
Parameter parameters = constructor.getParameters()[0];
124192
if (parameters.getType().equals(List.class)) {
125-
listenerLoader = constructor.newInstance(annotatedEventListeners);
193+
constructor.newInstance(annotatedEventListeners);
126194
return;
127195
}
128196
}
129197
}
130-
} catch (
131-
ClassNotFoundException
132-
| InstantiationException
133-
| IllegalAccessException
134-
| InvocationTargetException ignore
135-
) {
198+
} catch (Exception | Error e) {
136199
/* Ignore */
137200
}
138201
}
139202

203+
private void loadSlashCommands() {
204+
LOGGER.info("Loading SlashCommands");
205+
try {
206+
Class<?> clazz = Class.forName("com.javadiscord.jdi.core.processor.SlashCommandLoader");
207+
for (Constructor<?> constructor : clazz.getConstructors()) {
208+
if (constructor.getParameterCount() == 1) {
209+
Parameter parameters = constructor.getParameters()[0];
210+
if (parameters.getType().equals(Map.class)) {
211+
eventListeners.add(new InteractionEventHandler(constructor.newInstance(loadedSlashCommands)));
212+
return;
213+
}
214+
return;
215+
}
216+
}
217+
} catch (Exception | Error e) {
218+
LOGGER.error("Failed to load SlashCommands", e);
219+
}
220+
}
221+
140222
public void start() {
223+
started = true;
224+
141225
this.webSocketManager =
142226
new WebSocketManager(
143227
new GatewaySetting().setApiVersion(10).setEncoding(GatewayEncoding.JSON),
@@ -154,11 +238,11 @@ public void start() {
154238
connectionMediator.addObserver(new GatewayEventListenerAnnotations(this));
155239
connectionMediator.addObserver(new GatewayEventListener(this));
156240
webSocketManagerProxy.start(connectionMediator);
157-
158-
EXECUTOR.execute(discordRequestDispatcher);
159241
}
160242

161243
public void stop() {
244+
started = false;
245+
162246
if (this.webSocketManager != null) {
163247
this.webSocketManager.stop();
164248
}
@@ -219,6 +303,32 @@ private static Gateway getGatewayURL(String authentication) {
219303
}
220304
}
221305

306+
public void registerSlashCommand(
307+
String name,
308+
String description,
309+
CommandOption... options
310+
) {
311+
CommandBuilder builder =
312+
new CommandBuilder(
313+
name,
314+
description
315+
);
316+
for (CommandOption option : options) {
317+
builder.addOption(option);
318+
}
319+
builder.applicationId(applicationId);
320+
createInteractionRequests.add(builder);
321+
}
322+
323+
public void registerSlashCommand(CommandBuilder builder) {
324+
builder.applicationId(applicationId);
325+
createInteractionRequests.add(builder);
326+
}
327+
328+
public void deleteSlashCommand(long id) {
329+
330+
}
331+
222332
public DiscordRequestDispatcher getDiscordRequestDispatcher() {
223333
return discordRequestDispatcher;
224334
}
@@ -231,7 +341,43 @@ public List<Object> getAnnotatedEventListeners() {
231341
return annotatedEventListeners;
232342
}
233343

344+
public boolean started() {
345+
return started;
346+
}
347+
234348
public List<EventListener> getEventListeners() {
235349
return eventListeners;
236350
}
351+
352+
public long getApplicationId() {
353+
return applicationId;
354+
}
355+
356+
void handleReadyEvent(ReadyEvent event) {
357+
applicationId = event.application().id();
358+
359+
EXECUTOR.execute(discordRequestDispatcher);
360+
361+
for (CommandBuilder builder : createInteractionRequests) {
362+
builder.applicationId(applicationId);
363+
CreateCommandRequest request = builder.build();
364+
DiscordResponseFuture future = sendRequest(request);
365+
future.onSuccess(res -> {
366+
if (res.status() >= 200 && res.status() < 300) {
367+
LOGGER.info("Registered slash command {} with discord", request.name());
368+
} else {
369+
LOGGER.error(
370+
"Failed to register slash command {} with discord\n{}", request.name(),
371+
res.body()
372+
);
373+
}
374+
});
375+
future.onError(
376+
err -> LOGGER
377+
.error("Failed to register slash command {} with discord", request.name(), err)
378+
);
379+
}
380+
381+
createInteractionRequests.clear();
382+
}
237383
}

core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.javadiscord.jdi.core.models.guild.*;
1111
import com.javadiscord.jdi.core.models.invite.Invite;
1212
import com.javadiscord.jdi.core.models.message.*;
13+
import com.javadiscord.jdi.core.models.ready.ReadyEvent;
1314
import com.javadiscord.jdi.core.models.scheduled_event.EventUser;
1415
import com.javadiscord.jdi.core.models.scheduled_event.ScheduledEvent;
1516
import com.javadiscord.jdi.core.models.stage.Stage;
@@ -73,6 +74,10 @@ static Guild getGuild(Discord discord, Object event) {
7374

7475
@Override
7576
public void receive(EventType eventType, Object event) {
77+
if (eventType == EventType.READY) {
78+
discord.handleReadyEvent((ReadyEvent) event);
79+
}
80+
7681
Guild guild = getGuild(discord, event);
7782

7883
for (EventListener listener : discord.getEventListeners()) {

core/src/main/java/com/javadiscord/jdi/core/Guild.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class Guild {
2323
private final StickerRequest stickerRequest;
2424
private final UserRequest userRequest;
2525
private final VoiceRequest voiceRequest;
26+
private final InteractionRequest interactionRequest;
2627

2728
public Guild(com.javadiscord.jdi.core.models.guild.Guild guild, Cache cache, Discord discord) {
2829
this.metadata = guild;
@@ -51,6 +52,12 @@ public Guild(com.javadiscord.jdi.core.models.guild.Guild guild, Cache cache, Dis
5152
this.stickerRequest = new StickerRequest(discordResponseParser, guildId);
5253
this.userRequest = new UserRequest(discordResponseParser, guildId);
5354
this.voiceRequest = new VoiceRequest(discordResponseParser, guildId);
55+
this.interactionRequest =
56+
new InteractionRequest(discordResponseParser, guildId, discord.getApplicationId());
57+
}
58+
59+
public InteractionRequest interaction() {
60+
return interactionRequest;
5461
}
5562

5663
public ChannelRequest channel() {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.javadiscord.jdi.core.interaction;
2+
3+
import java.lang.reflect.Method;
4+
5+
import com.javadiscord.jdi.core.EventListener;
6+
import com.javadiscord.jdi.core.Guild;
7+
import com.javadiscord.jdi.core.models.guild.Interaction;
8+
import org.apache.logging.log4j.LogManager;
9+
import org.apache.logging.log4j.Logger;
10+
11+
public class InteractionEventHandler implements EventListener {
12+
private static final Logger LOGGER = LogManager.getLogger(InteractionEventHandler.class);
13+
private final Object slashCommandLoader;
14+
15+
public InteractionEventHandler(Object slashCommandLoader) {
16+
this.slashCommandLoader = slashCommandLoader;
17+
}
18+
19+
@Override
20+
public void onInteractionCreate(Interaction interaction, Guild guild) {
21+
String command = interaction.data().name();
22+
23+
try {
24+
Class<?> slashCommandLoaderClass = slashCommandLoader.getClass();
25+
26+
Method getSlashCommandClassMethod =
27+
slashCommandLoaderClass.getMethod("getSlashCommandClassMethod", String.class);
28+
29+
Object commandClassMethodInstance =
30+
getSlashCommandClassMethod.invoke(slashCommandLoader, command);
31+
32+
if(commandClassMethodInstance == null) {
33+
LOGGER.warn("No handler found for /{} command", command);
34+
return;
35+
}
36+
37+
Class<?> handler =
38+
(Class<?>) commandClassMethodInstance.getClass().getMethod("clazz")
39+
.invoke(commandClassMethodInstance);
40+
41+
Method method =
42+
(Method) commandClassMethodInstance.getClass().getMethod("method")
43+
.invoke(commandClassMethodInstance);
44+
45+
Object handlerInstance = handler.getDeclaredConstructor().newInstance();
46+
method.invoke(handlerInstance);
47+
48+
} catch (Exception e) {
49+
LOGGER.error("Failed to invoke handler for /{}", command, e);
50+
}
51+
}
52+
53+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package com.javadiscord.jdi.core.models.ready;
22

3-
public record Application(String id, int flags) {}
3+
public record Application(long id, int flags) {}

0 commit comments

Comments
 (0)