From ec0bbd6fbe82056ed9217f348455df08f989c1e1 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 16:59:16 +0100 Subject: [PATCH 01/57] Added support for slash command loading into `:annotations` module --- .../jdi/core/CommandOptionType.java | 26 ++++++ .../jdi/core/annotations/CommandOption.java | 19 ++++ .../jdi/core/annotations/SlashCommand.java | 16 ++++ .../processor/SlashCommandClassMethod.java | 5 ++ .../core/processor/SlashCommandLoader.java | 90 +++++++++++++++++++ .../core/processor/SlashCommandValidator.java | 60 +++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/CommandOptionType.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/annotations/SlashCommand.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/CommandOptionType.java b/annotations/src/main/java/com/javadiscord/jdi/core/CommandOptionType.java new file mode 100644 index 00000000..4e89d862 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/CommandOptionType.java @@ -0,0 +1,26 @@ +package com.javadiscord.jdi.core; + +public enum CommandOptionType { + SUB_COMMAND(1), + SUB_COMMAND_GROUP(2), + STRING(3), + INTEGER(4), + BOOLEAN(5), + USER(6), + CHANNEL(7), + ROLE(8), + MENTIONABLE(9), + NUMBER(10), + ATTACHMENT(11), + ; + + private final int value; + + CommandOptionType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java new file mode 100644 index 00000000..94e4be49 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java @@ -0,0 +1,19 @@ +package com.javadiscord.jdi.core.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.javadiscord.jdi.core.CommandOptionType; + +@Retention(RetentionPolicy.RUNTIME) +@Target({}) +public @interface CommandOption { + String name(); + + String description(); + + CommandOptionType type(); + + boolean required() default true; +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/SlashCommand.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/SlashCommand.java new file mode 100644 index 00000000..3cbe6d0c --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/SlashCommand.java @@ -0,0 +1,16 @@ +package com.javadiscord.jdi.core.annotations; + +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.METHOD}) +public @interface SlashCommand { + String name(); + + String description(); + + CommandOption[] options() default {}; +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java new file mode 100644 index 00000000..43bddf95 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java @@ -0,0 +1,5 @@ +package com.javadiscord.jdi.core.processor; + +import java.lang.reflect.Method; + +public record SlashCommandClassMethod(Class clazz, Method method) {} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java new file mode 100644 index 00000000..5cff9a50 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java @@ -0,0 +1,90 @@ +package com.javadiscord.jdi.core.processor; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import com.javadiscord.jdi.core.annotations.SlashCommand; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SlashCommandLoader { + private static final Logger LOGGER = LogManager.getLogger(SlashCommandLoader.class); + private final Map interactionListeners; + private final SlashCommandValidator validator = new SlashCommandValidator(); + + public SlashCommandLoader(Map interactionListeners) { + this.interactionListeners = interactionListeners; + loadInteractionListeners(); + } + + private void loadInteractionListeners() { + List classes = ClassFileUtil.getClassesInClassPath(); + for (File classFile : classes) { + try { + + String name = ClassFileUtil.getClassName(classFile); + if(name.contains("io.netty") + || name.contains("org.apache") + || name.contains("io.vertx") + || name.contains("com.fasterxml")) { + continue; + } + + Class clazz = Class.forName(name); + + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.getAnnotation(SlashCommand.class) != null) { + if (validator.validate(method) && hasZeroArgsConstructor(clazz)) { + registerListener( + clazz, method, method.getAnnotation(SlashCommand.class).name() + ); + } else { + LOGGER.error("{} failed validation", method.getName()); + } + } + } + } catch (Exception | Error ignore) { + /* Ignore */ + } + } + } + + private void registerListener(Class clazz, Method method, String name) { + try { + if (interactionListeners.containsKey(name)) { + LOGGER.error( + "Failed to register command {} from {} as that name already exists in {}", + name, + clazz.getName(), + interactionListeners.get(name).getClass().getName() + ); + return; + } + interactionListeners.put(name, new SlashCommandClassMethod(clazz, method)); + LOGGER.info("Found slash command handler {}", clazz.getName()); + } catch (Exception e) { + LOGGER.error("Failed to create {} instance", clazz.getName(), e); + } + } + + public SlashCommandClassMethod getSlashCommandClassMethod(String name) { + return interactionListeners.get(name); + } + + private boolean hasZeroArgsConstructor(Class clazz) { + Constructor[] constructors = clazz.getConstructors(); + for (Constructor constructor : constructors) { + if (constructor.getParameterCount() == 0) { + return true; + } + } + LOGGER.error("{} does not have a 0 arg constructor", clazz.getName()); + return false; + } + +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java new file mode 100644 index 00000000..1d14a85b --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java @@ -0,0 +1,60 @@ +package com.javadiscord.jdi.core.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.javadiscord.jdi.core.annotations.SlashCommand; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SlashCommandValidator { + private static final Logger LOGGER = LogManager.getLogger(SlashCommandValidator.class); + private static final Map, String[]> EXPECTED_PARAM_TYPES_MAP = + new HashMap<>(); + + static { + EXPECTED_PARAM_TYPES_MAP.put( + SlashCommand.class, + new String[] { + "com.javadiscord.jdi.core.models.guild.Interaction", + "com.javadiscord.jdi.core.Discord", + "com.javadiscord.jdi.core.Guild" + } + ); + } + + public boolean validate(Method method) { + for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP + .entrySet()) { + Class annotationClass = entry.getKey(); + if (method.isAnnotationPresent(annotationClass)) { + String[] expectedParamTypes = entry.getValue(); + if (method.getParameterCount() > 0) { + Class[] paramTypes = method.getParameterTypes(); + for (Class type : paramTypes) { + boolean isExpectedType = false; + for (String expectedType : expectedParamTypes) { + if (type.getName().equals(expectedType)) { + isExpectedType = true; + break; + } + } + if (!isExpectedType) { + LOGGER.error("Unexpected parameter found: {}", type.getName()); + return false; + } + } + } else if (method.getParameterCount() != 0) { + LOGGER.error( + "{} does not have the expected parameter types", method.getName() + ); + return false; + } + } + } + return true; + } +} From f9f633d76648f14b8c4d9daedb857bfbdfd51cdd Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:00:20 +0100 Subject: [PATCH 02/57] chore: Removed unused retry handler in WebSocketHandler --- .../com/javadiscord/jdi/internal/gateway/WebSocketHandler.java | 3 --- .../com/javadiscord/jdi/internal/gateway/WebSocketManager.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java index 4afe17c9..56965fc4 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java @@ -24,16 +24,13 @@ public class WebSocketHandler implements Handler { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final Map OPERATION_HANDLER = new HashMap<>(); private final ConnectionMediator connectionMediator; - private final WebSocketRetryHandler retryHandler; private final Cache cache; public WebSocketHandler( ConnectionMediator connectionMediator, - WebSocketRetryHandler retryHandler, Cache cache ) { this.connectionMediator = connectionMediator; - this.retryHandler = retryHandler; this.cache = cache; registerHandlers(); } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java index 37ae128a..87a5a056 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java @@ -57,7 +57,7 @@ public void start(ConnectionMediator connectionMediator) { this.webSocket = webSocket; WebSocketHandler webSocketHandler = - new WebSocketHandler(connectionMediator, retryHandler, cache); + new WebSocketHandler(connectionMediator, cache); webSocketHandler.handle(webSocket); From 842e934c9d6629a5e361d99c8368a63c4728e066 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:01:28 +0100 Subject: [PATCH 03/57] Added slash command request classes to `:api` module --- .../jdi/core/api/InteractionRequest.java | 40 ++++++++++++ .../api/builders/command/CommandBuilder.java | 61 +++++++++++++++++++ .../api/builders/command/CommandOption.java | 56 +++++++++++++++++ .../builders/command/CommandOptionChoice.java | 3 + .../builders/command/CommandOptionType.java | 38 ++++++++++++ .../CreateCommandRequest.java | 46 ++++++++++++++ .../DeleteCommandRequest.java | 20 ++++++ .../EditCommandRequest.java | 12 ++++ .../FetchCommandRequest.java | 12 ++++ .../FetchCommandsRequest.java | 12 ++++ 10 files changed, 300 insertions(+) create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/InteractionRequest.java create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandBuilder.java create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionChoice.java create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionType.java create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandRequest.java create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandsRequest.java diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/InteractionRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/InteractionRequest.java new file mode 100644 index 00000000..fe023719 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/InteractionRequest.java @@ -0,0 +1,40 @@ +package com.javadiscord.jdi.core.api; + +import com.javadiscord.jdi.core.api.builders.command.CommandBuilder; +import com.javadiscord.jdi.core.api.builders.command.CommandOption; + +public class InteractionRequest { + private final DiscordResponseParser responseParser; + private final long guildId; + private final long applicationId; + + public InteractionRequest( + DiscordResponseParser responseParser, long guildId, long applicationId + ) { + this.responseParser = responseParser; + this.guildId = guildId; + this.applicationId = applicationId; + } + + public AsyncResponse createInteraction(CommandBuilder builder) { + return responseParser + .callAndParse(Void.class, builder.applicationId(applicationId).build()); + } + + public AsyncResponse createSlashCommand( + String name, + String description, + CommandOption... options + ) { + CommandBuilder builder = + new CommandBuilder( + name, + description + ); + for (CommandOption option : options) { + builder.addOption(option); + } + builder.applicationId(applicationId); + return createInteraction(builder); + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandBuilder.java new file mode 100644 index 00000000..d381464c --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandBuilder.java @@ -0,0 +1,61 @@ +package com.javadiscord.jdi.core.api.builders.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import com.javadiscord.jdi.internal.api.application_commands.CreateCommandRequest; + +public class CommandBuilder { + private final String name; + private final CommandOptionType type; + private final String description; + private final List options; + + private Optional global; + + private long applicationId; + private long guildId; + + public CommandBuilder( + String name, String description + ) { + this.name = name; + this.type = CommandOptionType.SUB_COMMAND; + this.description = description; + this.options = new ArrayList<>(); + this.global = Optional.empty(); + } + + public CommandBuilder addOption(CommandOption option) { + options.add(option); + return this; + } + + public CommandBuilder global(boolean global) { + this.global = Optional.of(global); + return this; + } + + public CommandBuilder guildId(long guildId) { + this.guildId = guildId; + return this; + } + + public CommandBuilder applicationId(long applicationId) { + this.applicationId = applicationId; + return this; + } + + public CreateCommandRequest build() { + return new CreateCommandRequest( + name, + type, + description, + options, + global, + guildId, + applicationId + ); + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java new file mode 100644 index 00000000..5d2bb20f --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java @@ -0,0 +1,56 @@ +package com.javadiscord.jdi.core.api.builders.command; + +import java.util.ArrayList; +import java.util.List; + +public class CommandOption { + private final String name; + private final String description; + private final CommandOptionType type; + private boolean required; + private final List choices; + + public CommandOption(String name, String description, CommandOptionType type) { + this(name, description, type, true); + } + + public CommandOption( + String name, String description, CommandOptionType type, boolean required + ) { + this.name = name; + this.description = description; + this.type = type; + this.required = required; + this.choices = new ArrayList<>(); + } + + public CommandOption addChoice(String name, String value) { + choices.add(new CommandOptionChoice(name, value)); + return this; + } + + public CommandOption setRequired(boolean required) { + this.required = required; + return this; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public CommandOptionType getType() { + return type; + } + + public boolean isRequired() { + return required; + } + + public List getChoices() { + return choices; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionChoice.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionChoice.java new file mode 100644 index 00000000..1358a324 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionChoice.java @@ -0,0 +1,3 @@ +package com.javadiscord.jdi.core.api.builders.command; + +public record CommandOptionChoice(String name, String value) {} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionType.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionType.java new file mode 100644 index 00000000..81ce8eb1 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOptionType.java @@ -0,0 +1,38 @@ +package com.javadiscord.jdi.core.api.builders.command; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum CommandOptionType { + SUB_COMMAND(1), + SUB_COMMAND_GROUP(2), + STRING(3), + INTEGER(4), + BOOLEAN(5), + USER(6), + CHANNEL(7), + ROLE(8), + MENTIONABLE(9), + NUMBER(10), + ATTACHMENT(11), + ; + + private final int value; + + CommandOptionType(int value) { + this.value = value; + } + + @JsonValue + public int getValue() { + return value; + } + + public static CommandOptionType fromName(String name) { + for (CommandOptionType type : CommandOptionType.values()) { + if (type.name().equalsIgnoreCase(name)) { + return type; + } + } + throw new IllegalArgumentException("Unknown command option type: " + name); + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java new file mode 100644 index 00000000..38bd5b41 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java @@ -0,0 +1,46 @@ +package com.javadiscord.jdi.internal.api.application_commands; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.javadiscord.jdi.core.api.builders.command.CommandOption; +import com.javadiscord.jdi.core.api.builders.command.CommandOptionType; +import com.javadiscord.jdi.internal.api.DiscordRequest; +import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; + +public record CreateCommandRequest( + String name, + CommandOptionType type, + String description, + List options, + Optional global, + long guildId, + long applicationId +) implements DiscordRequest { + + @Override + public DiscordRequestBuilder create() { + + String path = "/applications/%s/commands".formatted(applicationId); + + if (global.isPresent() && global.get()) { + path = "/applications/%s/guilds/%s/commands".formatted(applicationId, guildId); + } + + Map body = new HashMap<>(); + body.put("name", name); + body.put("type", type); + body.put("description", description); + + if (!options.isEmpty()) { + body.put("options", options); + } + + return new DiscordRequestBuilder() + .post() + .body(body) + .path(path); + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java new file mode 100644 index 00000000..a25bbdf4 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java @@ -0,0 +1,20 @@ +package com.javadiscord.jdi.internal.api.application_commands; + +import java.util.Optional; + +import com.javadiscord.jdi.internal.api.DiscordRequest; +import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; + +public record DeleteCommandRequest( + long applicationId, + Optional guildId, + long commandId +) implements DiscordRequest { + + @Override + public DiscordRequestBuilder create() { + // TODO: Implement + // https://discord.com/developers/docs/interactions/application-commands#updating-and-deleting-a-command + return null; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java new file mode 100644 index 00000000..fb74d2d4 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java @@ -0,0 +1,12 @@ +package com.javadiscord.jdi.internal.api.application_commands; + +import com.javadiscord.jdi.internal.api.DiscordRequest; +import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; + +public record EditCommandRequest() implements DiscordRequest { + + @Override + public DiscordRequestBuilder create() { + return null; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandRequest.java new file mode 100644 index 00000000..fc999afa --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandRequest.java @@ -0,0 +1,12 @@ +package com.javadiscord.jdi.internal.api.application_commands; + +import com.javadiscord.jdi.internal.api.DiscordRequest; +import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; + +public record FetchCommandRequest() implements DiscordRequest { + + @Override + public DiscordRequestBuilder create() { + return null; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandsRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandsRequest.java new file mode 100644 index 00000000..232d11ab --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/FetchCommandsRequest.java @@ -0,0 +1,12 @@ +package com.javadiscord.jdi.internal.api.application_commands; + +import com.javadiscord.jdi.internal.api.DiscordRequest; +import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; + +public record FetchCommandsRequest() implements DiscordRequest { + + @Override + public DiscordRequestBuilder create() { + return null; + } +} From e2445d5820d911ba434cecca6b51d4720f407e60 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:03:39 +0100 Subject: [PATCH 04/57] Added logic into `:core` to register slash commands with discord and invoke slash command handlers --- .../com/javadiscord/jdi/core/Discord.java | 170 ++++++++++++++++-- .../jdi/core/GatewayEventListener.java | 5 + .../java/com/javadiscord/jdi/core/Guild.java | 7 + .../interaction/InteractionEventHandler.java | 53 ++++++ .../jdi/core/models/ready/Application.java | 2 +- 5 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 4ba97449..d2ee0f34 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -1,21 +1,30 @@ package com.javadiscord.jdi.core; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import com.javadiscord.jdi.core.api.builders.command.CommandBuilder; +import com.javadiscord.jdi.core.api.builders.command.CommandOption; +import com.javadiscord.jdi.core.api.builders.command.CommandOptionType; +import com.javadiscord.jdi.core.interaction.InteractionEventHandler; +import com.javadiscord.jdi.core.models.ready.ReadyEvent; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; +import com.javadiscord.jdi.internal.api.application_commands.CreateCommandRequest; import com.javadiscord.jdi.internal.cache.Cache; import com.javadiscord.jdi.internal.cache.CacheType; import com.javadiscord.jdi.internal.gateway.*; @@ -43,10 +52,13 @@ public class Discord { private final GatewaySetting gatewaySetting; private final Cache cache; private final List annotatedEventListeners = new ArrayList<>(); + private final Map loadedSlashCommands = new HashMap<>(); private final List eventListeners = new ArrayList<>(); + private final List createInteractionRequests = new ArrayList<>(); private WebSocketManager webSocketManager; - private Object listenerLoader; + private long applicationId; + private boolean started = false; public Discord(String botToken) { this( @@ -101,11 +113,66 @@ public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) { this.identifyRequest = identifyRequest; this.cache = cache; if (annotationLibPresent()) { - LOGGER.info("Annotation lib is present, loading annotations listeners..."); + LOGGER.info("Annotation lib is present"); loadAnnotations(); + loadSlashCommands(); + registerLoadedAnnotationsWithDiscord(); } } + private void registerLoadedAnnotationsWithDiscord() { + LOGGER.info("Registering slash commands with Discord"); + loadedSlashCommands.forEach((commandName, slashCommandClassInstance) -> { + try { + Class slashCommandClassInstanceClass = slashCommandClassInstance.getClass(); + Method method = (Method) slashCommandClassInstanceClass + .getMethod("method") + .invoke(slashCommandClassInstance); + + Annotation[] annotations = method.getAnnotations(); + for (Annotation annotation : annotations) { + if(annotation.annotationType().getName().equals("com.javadiscord.jdi.core.annotations.SlashCommand")) { + Method nameMethod = annotation.annotationType().getMethod("name"); + String name = (String) nameMethod.invoke(annotation); + + Method descriptionMethod = annotation.annotationType().getMethod("description"); + String description = (String) descriptionMethod.invoke(annotation); + + Method optionsMethod = annotation.annotationType().getMethod("options"); + Object[] options = (Object[]) optionsMethod.invoke(annotation); + + CommandBuilder builder = new CommandBuilder(name, description); + + for (Object option : options) { + Method optionNameMethod = option.getClass().getMethod("name"); + String optionName = (String) optionNameMethod.invoke(option); + + Method optionDescriptionMethod = option.getClass().getMethod("description"); + String optionDescription = (String) optionDescriptionMethod.invoke(option); + + Method optionTypeMethod = option.getClass().getMethod("type"); + Enum optionType = (Enum) optionTypeMethod.invoke(option); + String optionTypeValue = optionType.name(); + + Method optionRequiredMethod = option.getClass().getMethod("required"); + boolean optionRequired = (boolean) optionRequiredMethod.invoke(option); + + builder.addOption(new CommandOption( + optionName, + optionDescription, + CommandOptionType.fromName(optionTypeValue), + optionRequired)); + } + + createInteractionRequests.add(builder); + } + } + } catch (Exception e) { + LOGGER.error("Error registering slash command with Discord", e); + } + }); + } + private boolean annotationLibPresent() { try { Class.forName("com.javadiscord.jdi.core.processor.ListenerLoader"); @@ -116,28 +183,45 @@ private boolean annotationLibPresent() { } private void loadAnnotations() { + LOGGER.info("Loading EventListeners"); try { Class clazz = Class.forName("com.javadiscord.jdi.core.processor.ListenerLoader"); for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 1) { Parameter parameters = constructor.getParameters()[0]; if (parameters.getType().equals(List.class)) { - listenerLoader = constructor.newInstance(annotatedEventListeners); + constructor.newInstance(annotatedEventListeners); return; } } } - } catch ( - ClassNotFoundException - | InstantiationException - | IllegalAccessException - | InvocationTargetException ignore - ) { + } catch (Exception | Error e) { /* Ignore */ } } + private void loadSlashCommands() { + LOGGER.info("Loading SlashCommands"); + try { + Class clazz = Class.forName("com.javadiscord.jdi.core.processor.SlashCommandLoader"); + for (Constructor constructor : clazz.getConstructors()) { + if (constructor.getParameterCount() == 1) { + Parameter parameters = constructor.getParameters()[0]; + if (parameters.getType().equals(Map.class)) { + eventListeners.add(new InteractionEventHandler(constructor.newInstance(loadedSlashCommands))); + return; + } + return; + } + } + } catch (Exception | Error e) { + LOGGER.error("Failed to load SlashCommands", e); + } + } + public void start() { + started = true; + this.webSocketManager = new WebSocketManager( new GatewaySetting().setApiVersion(10).setEncoding(GatewayEncoding.JSON), @@ -154,11 +238,11 @@ public void start() { connectionMediator.addObserver(new GatewayEventListenerAnnotations(this)); connectionMediator.addObserver(new GatewayEventListener(this)); webSocketManagerProxy.start(connectionMediator); - - EXECUTOR.execute(discordRequestDispatcher); } public void stop() { + started = false; + if (this.webSocketManager != null) { this.webSocketManager.stop(); } @@ -219,6 +303,32 @@ private static Gateway getGatewayURL(String authentication) { } } + public void registerSlashCommand( + String name, + String description, + CommandOption... options + ) { + CommandBuilder builder = + new CommandBuilder( + name, + description + ); + for (CommandOption option : options) { + builder.addOption(option); + } + builder.applicationId(applicationId); + createInteractionRequests.add(builder); + } + + public void registerSlashCommand(CommandBuilder builder) { + builder.applicationId(applicationId); + createInteractionRequests.add(builder); + } + + public void deleteSlashCommand(long id) { + + } + public DiscordRequestDispatcher getDiscordRequestDispatcher() { return discordRequestDispatcher; } @@ -231,7 +341,43 @@ public List getAnnotatedEventListeners() { return annotatedEventListeners; } + public boolean started() { + return started; + } + public List getEventListeners() { return eventListeners; } + + public long getApplicationId() { + return applicationId; + } + + void handleReadyEvent(ReadyEvent event) { + applicationId = event.application().id(); + + EXECUTOR.execute(discordRequestDispatcher); + + for (CommandBuilder builder : createInteractionRequests) { + builder.applicationId(applicationId); + CreateCommandRequest request = builder.build(); + DiscordResponseFuture future = sendRequest(request); + future.onSuccess(res -> { + if (res.status() >= 200 && res.status() < 300) { + LOGGER.info("Registered slash command {} with discord", request.name()); + } else { + LOGGER.error( + "Failed to register slash command {} with discord\n{}", request.name(), + res.body() + ); + } + }); + future.onError( + err -> LOGGER + .error("Failed to register slash command {} with discord", request.name(), err) + ); + } + + createInteractionRequests.clear(); + } } diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java index 148c7949..2167d4bd 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java @@ -10,6 +10,7 @@ import com.javadiscord.jdi.core.models.guild.*; import com.javadiscord.jdi.core.models.invite.Invite; import com.javadiscord.jdi.core.models.message.*; +import com.javadiscord.jdi.core.models.ready.ReadyEvent; import com.javadiscord.jdi.core.models.scheduled_event.EventUser; import com.javadiscord.jdi.core.models.scheduled_event.ScheduledEvent; import com.javadiscord.jdi.core.models.stage.Stage; @@ -73,6 +74,10 @@ static Guild getGuild(Discord discord, Object event) { @Override public void receive(EventType eventType, Object event) { + if (eventType == EventType.READY) { + discord.handleReadyEvent((ReadyEvent) event); + } + Guild guild = getGuild(discord, event); for (EventListener listener : discord.getEventListeners()) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Guild.java b/core/src/main/java/com/javadiscord/jdi/core/Guild.java index 5b684774..d590a562 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Guild.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Guild.java @@ -23,6 +23,7 @@ public class Guild { private final StickerRequest stickerRequest; private final UserRequest userRequest; private final VoiceRequest voiceRequest; + private final InteractionRequest interactionRequest; public Guild(com.javadiscord.jdi.core.models.guild.Guild guild, Cache cache, Discord discord) { this.metadata = guild; @@ -51,6 +52,12 @@ public Guild(com.javadiscord.jdi.core.models.guild.Guild guild, Cache cache, Dis this.stickerRequest = new StickerRequest(discordResponseParser, guildId); this.userRequest = new UserRequest(discordResponseParser, guildId); this.voiceRequest = new VoiceRequest(discordResponseParser, guildId); + this.interactionRequest = + new InteractionRequest(discordResponseParser, guildId, discord.getApplicationId()); + } + + public InteractionRequest interaction() { + return interactionRequest; } public ChannelRequest channel() { diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java new file mode 100644 index 00000000..46eb0de3 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -0,0 +1,53 @@ +package com.javadiscord.jdi.core.interaction; + +import java.lang.reflect.Method; + +import com.javadiscord.jdi.core.EventListener; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.models.guild.Interaction; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class InteractionEventHandler implements EventListener { + private static final Logger LOGGER = LogManager.getLogger(InteractionEventHandler.class); + private final Object slashCommandLoader; + + public InteractionEventHandler(Object slashCommandLoader) { + this.slashCommandLoader = slashCommandLoader; + } + + @Override + public void onInteractionCreate(Interaction interaction, Guild guild) { + String command = interaction.data().name(); + + try { + Class slashCommandLoaderClass = slashCommandLoader.getClass(); + + Method getSlashCommandClassMethod = + slashCommandLoaderClass.getMethod("getSlashCommandClassMethod", String.class); + + Object commandClassMethodInstance = + getSlashCommandClassMethod.invoke(slashCommandLoader, command); + + if(commandClassMethodInstance == null) { + LOGGER.warn("No handler found for /{} command", command); + return; + } + + Class handler = + (Class) commandClassMethodInstance.getClass().getMethod("clazz") + .invoke(commandClassMethodInstance); + + Method method = + (Method) commandClassMethodInstance.getClass().getMethod("method") + .invoke(commandClassMethodInstance); + + Object handlerInstance = handler.getDeclaredConstructor().newInstance(); + method.invoke(handlerInstance); + + } catch (Exception e) { + LOGGER.error("Failed to invoke handler for /{}", command, e); + } + } + +} diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/ready/Application.java b/models/src/main/java/com/javadiscord/jdi/core/models/ready/Application.java index bff9b7c9..0595aeb2 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/ready/Application.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/ready/Application.java @@ -1,3 +1,3 @@ package com.javadiscord.jdi.core.models.ready; -public record Application(String id, int flags) {} +public record Application(long id, int flags) {} From 8e43b2afcde8b342c5ebbb4542ca4a16bd8f3263 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:04:14 +0100 Subject: [PATCH 05/57] chore: Remove add to cache for an interaction --- .../codec/handlers/interaction/InteractionCreateHandler.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java index 049a12fe..aba3b41e 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java @@ -6,8 +6,7 @@ import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventHandler; public class InteractionCreateHandler implements EventHandler { + @Override - public void handle(Interaction event, ConnectionMediator connectionMediator, Cache cache) { - cache.getCacheForGuild(event.guildId()).add(event.id(), event); - } + public void handle(Interaction event, ConnectionMediator connectionMediator, Cache cache) {} } From c95f8997d97a6b7c972228d58203d9b1698e87d2 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:08:50 +0100 Subject: [PATCH 06/57] Added ExampleSlashCommand --- .../jdi/example/ExampleSlashCommand.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java new file mode 100644 index 00000000..59728704 --- /dev/null +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -0,0 +1,24 @@ +package com.javadiscord.jdi.example; + +import com.javadiscord.jdi.core.CommandOptionType; +import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.SlashCommand; + +@EventListener +public class ExampleSlashCommand { + + @SlashCommand( + name = "quiz", + description = "A fun Java quiz", + options = { + @CommandOption(name = "q1", description = "What is an Integer?", type = CommandOptionType.STRING), + @CommandOption(name = "q2", description = "What package is List in?", type = CommandOptionType.STRING), + @CommandOption(name = "q3", description = "What does JVM stand for?", type = CommandOptionType.STRING), + @CommandOption(name = "q4", description = "Is a String a primitive?", type = CommandOptionType.STRING), + } + ) + public void handle() { + //TODO: Logic to handle the slash command + } +} From 5dd7dd4db2551add12070e1702d09df66035c011 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:09:23 +0100 Subject: [PATCH 07/57] Spotless... --- .../core/processor/SlashCommandLoader.java | 6 ++-- .../com/javadiscord/jdi/core/Discord.java | 30 ++++++++++++++----- .../interaction/InteractionEventHandler.java | 3 +- .../jdi/example/ExampleSlashCommand.java | 24 +++++++++------ 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java index 5cff9a50..8ec426db 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java @@ -27,10 +27,12 @@ private void loadInteractionListeners() { try { String name = ClassFileUtil.getClassName(classFile); - if(name.contains("io.netty") + if ( + name.contains("io.netty") || name.contains("org.apache") || name.contains("io.vertx") - || name.contains("com.fasterxml")) { + || name.contains("com.fasterxml") + ) { continue; } diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index d2ee0f34..ea5ce0a6 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -125,17 +125,22 @@ private void registerLoadedAnnotationsWithDiscord() { loadedSlashCommands.forEach((commandName, slashCommandClassInstance) -> { try { Class slashCommandClassInstanceClass = slashCommandClassInstance.getClass(); - Method method = (Method) slashCommandClassInstanceClass + Method method = + (Method) slashCommandClassInstanceClass .getMethod("method") .invoke(slashCommandClassInstance); Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { - if(annotation.annotationType().getName().equals("com.javadiscord.jdi.core.annotations.SlashCommand")) { + if ( + annotation.annotationType().getName() + .equals("com.javadiscord.jdi.core.annotations.SlashCommand") + ) { Method nameMethod = annotation.annotationType().getMethod("name"); String name = (String) nameMethod.invoke(annotation); - Method descriptionMethod = annotation.annotationType().getMethod("description"); + Method descriptionMethod = + annotation.annotationType().getMethod("description"); String description = (String) descriptionMethod.invoke(annotation); Method optionsMethod = annotation.annotationType().getMethod("options"); @@ -147,8 +152,10 @@ private void registerLoadedAnnotationsWithDiscord() { Method optionNameMethod = option.getClass().getMethod("name"); String optionName = (String) optionNameMethod.invoke(option); - Method optionDescriptionMethod = option.getClass().getMethod("description"); - String optionDescription = (String) optionDescriptionMethod.invoke(option); + Method optionDescriptionMethod = + option.getClass().getMethod("description"); + String optionDescription = + (String) optionDescriptionMethod.invoke(option); Method optionTypeMethod = option.getClass().getMethod("type"); Enum optionType = (Enum) optionTypeMethod.invoke(option); @@ -157,11 +164,14 @@ private void registerLoadedAnnotationsWithDiscord() { Method optionRequiredMethod = option.getClass().getMethod("required"); boolean optionRequired = (boolean) optionRequiredMethod.invoke(option); - builder.addOption(new CommandOption( + builder.addOption( + new CommandOption( optionName, optionDescription, CommandOptionType.fromName(optionTypeValue), - optionRequired)); + optionRequired + ) + ); } createInteractionRequests.add(builder); @@ -208,7 +218,11 @@ private void loadSlashCommands() { if (constructor.getParameterCount() == 1) { Parameter parameters = constructor.getParameters()[0]; if (parameters.getType().equals(Map.class)) { - eventListeners.add(new InteractionEventHandler(constructor.newInstance(loadedSlashCommands))); + eventListeners.add( + new InteractionEventHandler( + constructor.newInstance(loadedSlashCommands) + ) + ); return; } return; diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 46eb0de3..f5bfe906 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -5,6 +5,7 @@ import com.javadiscord.jdi.core.EventListener; import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.models.guild.Interaction; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,7 +30,7 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { Object commandClassMethodInstance = getSlashCommandClassMethod.invoke(slashCommandLoader, command); - if(commandClassMethodInstance == null) { + if (commandClassMethodInstance == null) { LOGGER.warn("No handler found for /{} command", command); return; } diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index 59728704..908ea302 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -9,16 +9,22 @@ public class ExampleSlashCommand { @SlashCommand( - name = "quiz", - description = "A fun Java quiz", - options = { - @CommandOption(name = "q1", description = "What is an Integer?", type = CommandOptionType.STRING), - @CommandOption(name = "q2", description = "What package is List in?", type = CommandOptionType.STRING), - @CommandOption(name = "q3", description = "What does JVM stand for?", type = CommandOptionType.STRING), - @CommandOption(name = "q4", description = "Is a String a primitive?", type = CommandOptionType.STRING), - } + name = "quiz", description = "A fun Java quiz", options = { + @CommandOption( + name = "q1", description = "What is an Integer?", type = CommandOptionType.STRING + ), + @CommandOption( + name = "q2", description = "What package is List in?", type = CommandOptionType.STRING + ), + @CommandOption( + name = "q3", description = "What does JVM stand for?", type = CommandOptionType.STRING + ), + @CommandOption( + name = "q4", description = "Is a String a primitive?", type = CommandOptionType.STRING + ), + } ) public void handle() { - //TODO: Logic to handle the slash command + // TODO: Logic to handle the slash command } } From dd10bfdde3d5d53bc5d179acfa4cd4d1496e4194 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 17:18:07 +0100 Subject: [PATCH 08/57] Removed @EventListener on ExampleSlashCommand --- .../java/com/javadiscord/jdi/example/ExampleSlashCommand.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index 908ea302..76eb2123 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -2,10 +2,8 @@ import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.annotations.CommandOption; -import com.javadiscord.jdi.core.annotations.EventListener; import com.javadiscord.jdi.core.annotations.SlashCommand; -@EventListener public class ExampleSlashCommand { @SlashCommand( From 04dbec373b76ed0ac6e63052c3edbafca6fef5a5 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 19:05:21 +0100 Subject: [PATCH 09/57] Added SlashCommandEvent object for slash commands --- .../core/processor/SlashCommandValidator.java | 3 +- .../com/javadiscord/jdi/core/Discord.java | 7 +-- .../jdi/core/GatewayEventListener.java | 2 +- .../interaction/InteractionEventHandler.java | 38 ++++++++++++-- .../core/interaction/SlashCommandEvent.java | 49 +++++++++++++++++++ .../jdi/example/ExampleSlashCommand.java | 9 +++- .../core/models/guild/InteractionData.java | 4 +- 7 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java index 1d14a85b..ba113edf 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java @@ -21,7 +21,8 @@ public class SlashCommandValidator { new String[] { "com.javadiscord.jdi.core.models.guild.Interaction", "com.javadiscord.jdi.core.Discord", - "com.javadiscord.jdi.core.Guild" + "com.javadiscord.jdi.core.Guild", + "com.javadiscord.jdi.core.interaction.SlashCommandEvent" } ); } diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index ed838eb0..4c5cc62f 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -220,7 +220,8 @@ private void loadSlashCommands() { if (parameters.getType().equals(Map.class)) { eventListeners.add( new InteractionEventHandler( - constructor.newInstance(loadedSlashCommands) + constructor.newInstance(loadedSlashCommands), + this ) ); return; @@ -235,7 +236,7 @@ private void loadSlashCommands() { public void start() { started = true; - + webSocketManager = new WebSocketManager( new GatewaySetting().setApiVersion(10).setEncoding(GatewayEncoding.JSON), @@ -260,7 +261,7 @@ public void stop() { if (this.webSocketManager != null) { this.webSocketManager.stop(); } - + LOGGER.info("Shutdown initiated"); if (webSocketManager != null) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java index 2167d4bd..7168ea49 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java @@ -37,7 +37,7 @@ public GatewayEventListener(Discord discord) { this.discord = discord; } - static Guild getGuild(Discord discord, Object event) { + public static Guild getGuild(Discord discord, Object event) { if (event instanceof com.javadiscord.jdi.core.models.guild.Guild) { return new Guild( (com.javadiscord.jdi.core.models.guild.Guild) event, diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index f5bfe906..af050dda 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -1,8 +1,13 @@ package com.javadiscord.jdi.core.interaction; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; +import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.EventListener; +import com.javadiscord.jdi.core.GatewayEventListener; import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.models.guild.Interaction; @@ -12,9 +17,11 @@ public class InteractionEventHandler implements EventListener { private static final Logger LOGGER = LogManager.getLogger(InteractionEventHandler.class); private final Object slashCommandLoader; + private final Discord discord; - public InteractionEventHandler(Object slashCommandLoader) { + public InteractionEventHandler(Object slashCommandLoader, Discord discord) { this.slashCommandLoader = slashCommandLoader; + this.discord = discord; } @Override @@ -39,9 +46,32 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { (Class) commandClassMethodInstance.getClass().getMethod("clazz") .invoke(commandClassMethodInstance); - Method method = - (Method) commandClassMethodInstance.getClass().getMethod("method") - .invoke(commandClassMethodInstance); + Method method = commandClassMethodInstance.getClass().getMethod("method"); + + List paramOrder = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + for (Parameter parameter : parameters) { + if (parameter.getParameterizedType() == interaction.getClass()) { + paramOrder.add(interaction); + } else if (parameter.getParameterizedType() == Discord.class) { + paramOrder.add(discord); + } else if (parameter.getParameterizedType() == Guild.class) { + paramOrder.add(GatewayEventListener.getGuild(discord, interaction.guild())); + } else if (parameter.getParameterizedType() == SlashCommandEvent.class) { + paramOrder.add(new SlashCommandEvent(interaction, discord)); + } + } + + if (paramOrder.size() != method.getParameterCount()) { + throw new RuntimeException( + "Bound " + + paramOrder.size() + + " parameters but expected " + + method.getParameterCount() + ); + } + + method.invoke(commandClassMethodInstance, paramOrder.toArray()); Object handlerInstance = handler.getDeclaredConstructor().newInstance(); method.invoke(handlerInstance); diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java new file mode 100644 index 00000000..d2057eb9 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java @@ -0,0 +1,49 @@ +package com.javadiscord.jdi.core.interaction; + +import com.javadiscord.jdi.core.Discord; +import com.javadiscord.jdi.core.GatewayEventListener; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; +import com.javadiscord.jdi.core.models.channel.Channel; +import com.javadiscord.jdi.core.models.guild.Interaction; +import com.javadiscord.jdi.core.models.guild.InteractionData; +import com.javadiscord.jdi.core.models.guild.InteractionType; +import com.javadiscord.jdi.core.models.user.User; + +public record SlashCommandEvent(Interaction interaction, Discord discord) { + + public Channel channel() { + return interaction.channel(); + } + + public Guild guild() { + return GatewayEventListener.getGuild(discord, interaction.guild()); + } + + public User user() { + return interaction.user(); + } + + public InteractionType interactionType() { + return interaction.type(); + } + + public InteractionData interactionData() { + return interaction.data(); + } + + public Object option(String name) { + InteractionData interactionData = interaction.data(); + ApplicationCommandOption[] options = interactionData.options(); + for (ApplicationCommandOption option : options) { + if (option.name().equals(name)) { + return option.value(); + } + } + return null; + } + + public ApplicationCommandOption[] options() { + return interaction.data().options(); + } +} diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index 76eb2123..5433bf78 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -3,6 +3,8 @@ import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.annotations.CommandOption; import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.interaction.SlashCommandEvent; +import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; public class ExampleSlashCommand { @@ -22,7 +24,10 @@ public class ExampleSlashCommand { ), } ) - public void handle() { - // TODO: Logic to handle the slash command + public void handle(SlashCommandEvent event) { + ApplicationCommandOption[] options = event.options(); + for (ApplicationCommandOption option : options) { + System.out.println("Received " + option.name() + " value " + option.value()); + } } } diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/guild/InteractionData.java b/models/src/main/java/com/javadiscord/jdi/core/models/guild/InteractionData.java index 1d9861af..ce2ca5fb 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/guild/InteractionData.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/guild/InteractionData.java @@ -12,6 +12,6 @@ public record InteractionData( @JsonProperty("type") int type, @JsonProperty("resolved") ResolvedData resolved, @JsonProperty("options") ApplicationCommandOption[] options, - @JsonProperty("guild_id") Long guildId, - @JsonProperty("target_id") Long targetId + @JsonProperty("guild_id") long guildId, + @JsonProperty("target_id") long targetId ) {} From 30ff380990780510ed20e7564e9e6b24c590f1ce Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 19:49:41 +0100 Subject: [PATCH 10/57] Fix invoking handler --- .../javadiscord/jdi/internal/cache/Cache.java | 4 +++- .../interaction/InteractionEventHandler.java | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cache/src/main/java/com/javadiscord/jdi/internal/cache/Cache.java b/cache/src/main/java/com/javadiscord/jdi/internal/cache/Cache.java index 4d00bab0..59f496ac 100644 --- a/cache/src/main/java/com/javadiscord/jdi/internal/cache/Cache.java +++ b/cache/src/main/java/com/javadiscord/jdi/internal/cache/Cache.java @@ -20,7 +20,9 @@ public CacheInterface getCacheForGuild(long guildId) { if (!cache.containsKey(guildId)) { return cache.put(guildId, getCacheForType()); } - return cache.get(guildId); + return cache.get(guildId) == null + ? cache.put(guildId, getCacheForType()) + : cache.get(guildId); } public boolean isGuildCached(long guildId) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index af050dda..793bb397 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -3,7 +3,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.EventListener; @@ -19,6 +21,8 @@ public class InteractionEventHandler implements EventListener { private final Object slashCommandLoader; private final Discord discord; + private final Map cachedInstances = new HashMap<>(); + public InteractionEventHandler(Object slashCommandLoader, Discord discord) { this.slashCommandLoader = slashCommandLoader; this.discord = discord; @@ -46,7 +50,9 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { (Class) commandClassMethodInstance.getClass().getMethod("clazz") .invoke(commandClassMethodInstance); - Method method = commandClassMethodInstance.getClass().getMethod("method"); + Method method = + (Method) commandClassMethodInstance.getClass().getMethod("method") + .invoke(commandClassMethodInstance); List paramOrder = new ArrayList<>(); Parameter[] parameters = method.getParameters(); @@ -71,10 +77,12 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { ); } - method.invoke(commandClassMethodInstance, paramOrder.toArray()); - - Object handlerInstance = handler.getDeclaredConstructor().newInstance(); - method.invoke(handlerInstance); + if (cachedInstances.containsKey(handler.getName())) { + method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); + } else { + Object handlerInstance = handler.getDeclaredConstructor().newInstance(); + method.invoke(handlerInstance, paramOrder.toArray()); + } } catch (Exception e) { LOGGER.error("Failed to invoke handler for /{}", command, e); From 9904af900a4afd414f3d1e527f97179a0498367e Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 21:09:51 +0100 Subject: [PATCH 11/57] Add helper methods to ApplicationCommandOption --- .../jdi/example/ExampleSlashCommand.java | 5 +++-- .../application/ApplicationCommandOption.java | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index 5433bf78..27532627 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -1,6 +1,7 @@ package com.javadiscord.jdi.example; import com.javadiscord.jdi.core.CommandOptionType; +import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.annotations.CommandOption; import com.javadiscord.jdi.core.annotations.SlashCommand; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; @@ -24,10 +25,10 @@ public class ExampleSlashCommand { ), } ) - public void handle(SlashCommandEvent event) { + public void handle(SlashCommandEvent event, Guild guild) { ApplicationCommandOption[] options = event.options(); for (ApplicationCommandOption option : options) { - System.out.println("Received " + option.name() + " value " + option.value()); + System.out.println("Received " + option.name() + " value " + option.valueAsString()); } } } diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java b/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java index 45a6556c..9860cf80 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java @@ -12,4 +12,21 @@ public record ApplicationCommandOption( @JsonProperty("value") Object value, @JsonProperty("options") List options, @JsonProperty("focused") boolean focused -) {} +) { + + public String valueAsString() { + return String.valueOf(value); + } + + public int valueAsInt() { + return (int) value; + } + + public double valueAsDouble() { + return (double) value; + } + + public boolean valueAsBoolean() { + return (boolean) value; + } +} From f6f36287e541e86409ef604be62ef890e36b022b Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Sun, 26 May 2024 22:30:57 +0100 Subject: [PATCH 12/57] Fix choices in SlashCommand parsing --- .../jdi/core/annotations/CommandOption.java | 2 + .../core/annotations/CommandOptionChoice.java | 13 +++ .../api/builders/command/CommandOption.java | 5 + .../com/javadiscord/jdi/core/Discord.java | 25 ++++- .../core/interaction/SlashCommandEvent.java | 4 +- .../jdi/example/ExampleSlashCommand.java | 91 +++++++++++++++++-- .../application/ApplicationCommandOption.java | 5 + 7 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOptionChoice.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java index 94e4be49..aadce25a 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOption.java @@ -15,5 +15,7 @@ CommandOptionType type(); + CommandOptionChoice[] choices() default {}; + boolean required() default true; } diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOptionChoice.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOptionChoice.java new file mode 100644 index 00000000..8ae2513a --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/CommandOptionChoice.java @@ -0,0 +1,13 @@ +package com.javadiscord.jdi.core.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({}) +public @interface CommandOptionChoice { + String name(); + + String value(); +} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java index 5d2bb20f..42aa7f0d 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CommandOption.java @@ -29,6 +29,11 @@ public CommandOption addChoice(String name, String value) { return this; } + public CommandOption addChoice(List commandOptionChoices) { + choices.addAll(commandOptionChoices); + return this; + } + public CommandOption setRequired(boolean required) { this.required = required; return this; diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 4c5cc62f..4faa1774 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -18,6 +18,7 @@ import com.javadiscord.jdi.core.api.builders.command.CommandBuilder; import com.javadiscord.jdi.core.api.builders.command.CommandOption; +import com.javadiscord.jdi.core.api.builders.command.CommandOptionChoice; import com.javadiscord.jdi.core.api.builders.command.CommandOptionType; import com.javadiscord.jdi.core.interaction.InteractionEventHandler; import com.javadiscord.jdi.core.models.ready.ReadyEvent; @@ -164,13 +165,35 @@ private void registerLoadedAnnotationsWithDiscord() { Method optionRequiredMethod = option.getClass().getMethod("required"); boolean optionRequired = (boolean) optionRequiredMethod.invoke(option); + List choices = new ArrayList<>(); + + Object[] choicesArray = + (Object[]) option.getClass().getMethod("choices").invoke(option); + + for (Object choice : choicesArray) { + Annotation annotation1 = (Annotation) choice; + if ( + annotation1.annotationType().getName().equals( + "com.javadiscord.jdi.core.annotations.CommandOptionChoice" + ) + ) { + Method nameMethod1 = + annotation1.annotationType().getMethod("name"); + Method valueMethod1 = + annotation1.annotationType().getMethod("value"); + String name1 = (String) nameMethod1.invoke(annotation1); + String value1 = (String) valueMethod1.invoke(annotation1); + choices.add(new CommandOptionChoice(value1, name1)); + } + } + builder.addOption( new CommandOption( optionName, optionDescription, CommandOptionType.fromName(optionTypeValue), optionRequired - ) + ).addChoice(choices) ); } diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java index d2057eb9..e2fad556 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java @@ -32,12 +32,12 @@ public InteractionData interactionData() { return interaction.data(); } - public Object option(String name) { + public ApplicationCommandOption option(String name) { InteractionData interactionData = interaction.data(); ApplicationCommandOption[] options = interactionData.options(); for (ApplicationCommandOption option : options) { if (option.name().equals(name)) { - return option.value(); + return option; } } return null; diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index 27532627..536c5eb3 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -3,7 +3,9 @@ import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.CommandOptionChoice; import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; @@ -12,23 +14,98 @@ public class ExampleSlashCommand { @SlashCommand( name = "quiz", description = "A fun Java quiz", options = { @CommandOption( - name = "q1", description = "What is an Integer?", type = CommandOptionType.STRING + name = "q1", description = "What is an Integer?", type = CommandOptionType.STRING, choices = { + @CommandOptionChoice( + name = "option1", value = "An object that represents a number" + ), + @CommandOptionChoice(name = "option2", value = "A class used to store objects"), + @CommandOptionChoice(name = "option3", value = "A primitive data type") + } ), @CommandOption( - name = "q2", description = "What package is List in?", type = CommandOptionType.STRING + name = "q2", description = "In which package is the List interface defined?", type = CommandOptionType.STRING, choices = { + @CommandOptionChoice(name = "option1", value = "java.util"), + @CommandOptionChoice(name = "option2", value = "java.lang"), + @CommandOptionChoice(name = "option3", value = "java.io") + } ), @CommandOption( - name = "q3", description = "What does JVM stand for?", type = CommandOptionType.STRING + name = "q3", description = "What does JVM stand for?", type = CommandOptionType.STRING, choices = { + @CommandOptionChoice(name = "option1", value = "Java Virtual Machine"), + @CommandOptionChoice(name = "option2", value = "Java Verified Module"), + @CommandOptionChoice(name = "option3", value = "Java Variable Method") + } ), @CommandOption( - name = "q4", description = "Is a String a primitive?", type = CommandOptionType.STRING + name = "q4", description = "Is a String a primitive data type?", type = CommandOptionType.STRING, choices = { + @CommandOptionChoice(name = "option1", value = "Yes"), + @CommandOptionChoice(name = "option2", value = "No") + } ), + @CommandOption( + name = "q5", description = "Which of the following is not a Java keyword?", type = CommandOptionType.STRING, choices = { + @CommandOptionChoice(name = "option1", value = "static"), + @CommandOptionChoice(name = "option2", value = "void"), + @CommandOptionChoice(name = "option3", value = "main"), + @CommandOptionChoice(name = "option4", value = "private") + } + ) } ) public void handle(SlashCommandEvent event, Guild guild) { - ApplicationCommandOption[] options = event.options(); - for (ApplicationCommandOption option : options) { - System.out.println("Received " + option.name() + " value " + option.valueAsString()); + ApplicationCommandOption q1 = event.option("q1"); + ApplicationCommandOption q2 = event.option("q2"); + ApplicationCommandOption q3 = event.option("q3"); + ApplicationCommandOption q4 = event.option("q4"); + ApplicationCommandOption q5 = event.option("q5"); + + // Check answers and prepare feedback + StringBuilder feedback = new StringBuilder(); + int score = 0; + + if (q1.toString().equals("option1")) { + score++; + feedback.append("Q1: Correct!\n"); + } else { + feedback.append("Q1: Incorrect\n"); + } + + if (q2.toString().equals("option1")) { + score++; + feedback.append("Q2: Correct!\n"); + } else { + feedback.append("Q1: Incorrect\n"); + } + + if (q3.toString().equals("option1")) { + score++; + feedback.append("Q3: Correct!\n"); + } else { + feedback.append("Q1: Incorrect\n"); } + + if (q4.toString().equals("option2")) { + score++; + feedback.append("Q4: Correct!\n"); + } else { + feedback.append("Q1: Incorrect\n"); + } + + if (q5.toString().equals("option3")) { + score++; + feedback.append("Q5: Correct!\n"); + } else { + feedback.append("Q1: Incorrect\n"); + } + + feedback.append("Your score: ").append(score).append("/5"); + + if (score == 5) { + feedback.append("\nCongratulations! You you all the questions right!\n"); + } + + guild.channel().createMessage( + new CreateMessageBuilder(event.channel().id()).content(feedback.toString()) + ); } } diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java b/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java index 9860cf80..7dc94862 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/application/ApplicationCommandOption.java @@ -29,4 +29,9 @@ public double valueAsDouble() { public boolean valueAsBoolean() { return (boolean) value; } + + @Override + public String toString() { + return String.valueOf(value == null ? "" : value); + } } From 4d32ce6f7c39297b00ffa7152c952f91c3f89b24 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Mon, 27 May 2024 15:07:34 +0100 Subject: [PATCH 13/57] * Added defer/reply for slash command * Fix performance with dynamic loading * General cleanup --- .../jdi/core/processor/ClassFileUtil.java | 21 +++- .../core/processor/SlashCommandLoader.java | 13 +-- .../api/builders/command/CallbackMessage.java | 110 ++++++++++++++++++ .../command/CallbackMessageBuilder.java | 41 +++++++ .../command/CallbackResponseType.java | 22 ++++ .../EditCommandRequest.java | 48 +++++++- .../RespondCommandRequest.java | 32 +++++ .../core/interaction/SlashCommandEvent.java | 102 +++++++++++++++- .../jdi/example/ExampleSlashCommand.java | 100 +++++++++------- .../guild/message/MessageUpdateHandler.java | 4 +- 10 files changed, 430 insertions(+), 63 deletions(-) create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessageBuilder.java create mode 100644 api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackResponseType.java create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/RespondCommandRequest.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java index a03aafbb..8d7f3d57 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java @@ -12,22 +12,37 @@ import javassist.bytecode.ClassFile; public class ClassFileUtil { + private static final List classesInPath = new ArrayList<>(); private ClassFileUtil() {} public static List getClassesInClassPath() { - List classes = new ArrayList<>(); + if (!classesInPath.isEmpty()) { + return classesInPath; + } String classpath = System.getProperty("java.class.path"); String[] classpathEntries = classpath.split(File.pathSeparator); + for (String entry : classpathEntries) { + if ( + entry.contains("io.netty") + || entry.contains("org.apache") + || entry.contains("io.vertx") + || entry.contains("com.fasterxml") + || entry.contains("org.javassist") + || entry.contains("com.github.mizosoft.methanol") + ) { + continue; + } + File file = new File(entry); try { - classes.addAll(getClasses(file)); + classesInPath.addAll(getClasses(file)); } catch (IOException ignore) { /* Ignore */ } } - return classes; + return classesInPath; } public static String getClassName(File file) throws IOException { diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java index 8ec426db..ff3c45cb 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java @@ -25,18 +25,7 @@ private void loadInteractionListeners() { List classes = ClassFileUtil.getClassesInClassPath(); for (File classFile : classes) { try { - - String name = ClassFileUtil.getClassName(classFile); - if ( - name.contains("io.netty") - || name.contains("org.apache") - || name.contains("io.vertx") - || name.contains("com.fasterxml") - ) { - continue; - } - - Class clazz = Class.forName(name); + Class clazz = Class.forName(ClassFileUtil.getClassName(classFile)); Method[] methods = clazz.getMethods(); for (Method method : methods) { diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java new file mode 100644 index 00000000..171d2e8d --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java @@ -0,0 +1,110 @@ +package com.javadiscord.jdi.core.api.builders.command; + +import java.util.List; + +import com.javadiscord.jdi.core.models.channel.AllowedMentions; +import com.javadiscord.jdi.core.models.channel.Attachment; +import com.javadiscord.jdi.core.models.message.Component; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.poll.Poll; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CallbackMessage { + @JsonProperty("tts") + private boolean tts; + + @JsonProperty("content") + private String content; + + @JsonProperty("embeds") + private List embeds; + + @JsonProperty("allowed_mentions") + private AllowedMentions allowedMentions; + + @JsonProperty("flags") + private int flags; + + @JsonProperty("components") + private List components; + + @JsonProperty("attachments") + private List attachments; + + @JsonProperty("poll") + private Poll poll; + + public CallbackMessage() {} + + public boolean isTts() { + return tts; + } + + public void setTts(boolean tts) { + this.tts = tts; + } + + public String getContent() { + return content; + } + + public CallbackMessage setContent(String content) { + this.content = content; + return this; + } + + public List getEmbeds() { + return embeds; + } + + public CallbackMessage setEmbeds(List embeds) { + this.embeds = embeds; + return this; + } + + public AllowedMentions getAllowedMentions() { + return allowedMentions; + } + + public CallbackMessage setAllowedMentions(AllowedMentions allowedMentions) { + this.allowedMentions = allowedMentions; + return this; + } + + public int getFlags() { + return flags; + } + + public CallbackMessage setFlags(int flags) { + this.flags = flags; + return this; + } + + public List getComponents() { + return components; + } + + public CallbackMessage setComponents(List components) { + this.components = components; + return this; + } + + public List getAttachments() { + return attachments; + } + + public CallbackMessage setAttachments(List attachments) { + this.attachments = attachments; + return this; + } + + public Poll getPoll() { + return poll; + } + + public CallbackMessage setPoll(Poll poll) { + this.poll = poll; + return this; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessageBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessageBuilder.java new file mode 100644 index 00000000..5dae2837 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessageBuilder.java @@ -0,0 +1,41 @@ +package com.javadiscord.jdi.core.api.builders.command; + +import java.util.Optional; + +import com.javadiscord.jdi.internal.api.application_commands.EditCommandRequest; +import com.javadiscord.jdi.internal.api.application_commands.RespondCommandRequest; + +public class CallbackMessageBuilder { + private final CallbackResponseType type; + private final long interactionId; + private final String interactionToken; + private Optional message; + private long applicationId; + + public CallbackMessageBuilder( + CallbackResponseType type, long interactionId, String interactionToken + ) { + this.type = type; + this.interactionId = interactionId; + this.interactionToken = interactionToken; + this.message = Optional.empty(); + } + + public CallbackMessageBuilder message(CallbackMessage message) { + this.message = Optional.of(message); + return this; + } + + public CallbackMessageBuilder applicationId(long applicationId) { + this.applicationId = applicationId; + return this; + } + + public RespondCommandRequest build() { + return new RespondCommandRequest(type, message, interactionId, interactionToken); + } + + public EditCommandRequest buildEdit() { + return new EditCommandRequest(type, message, applicationId, interactionToken); + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackResponseType.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackResponseType.java new file mode 100644 index 00000000..429f8264 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackResponseType.java @@ -0,0 +1,22 @@ +package com.javadiscord.jdi.core.api.builders.command; + +public enum CallbackResponseType { + PONG(1), + CHANNEL_MESSAGE_WITH_SOURCE(4), + DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE(5), + DEFERRED_UPDATE_MESSAGE(6), + UPDATE_MESSAGE(7), + APPLICATION_COMMAND_AUTOCOMPLETE_RESULT(8), + MODAL(9), + PREMIUM_REQUIRED(10); + + private final int value; + + CallbackResponseType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java index fb74d2d4..718c362f 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/EditCommandRequest.java @@ -1,12 +1,56 @@ package com.javadiscord.jdi.internal.api.application_commands; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import com.javadiscord.jdi.core.api.builders.command.CallbackMessage; +import com.javadiscord.jdi.core.api.builders.command.CallbackResponseType; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; -public record EditCommandRequest() implements DiscordRequest { +public record EditCommandRequest( + CallbackResponseType type, + Optional message, + long applicationId, + String interactionToken +) implements DiscordRequest { @Override public DiscordRequestBuilder create() { - return null; + Map body = new HashMap<>(); + body.put("type", type.getValue()); + + message.ifPresent(m -> { + body.put("tts", m.isTts()); + body.put("content", m.getContent()); + + if (m.getEmbeds() != null) { + body.put("embeds", m.getEmbeds()); + } + + if (m.getAllowedMentions() != null) { + body.put("allowed_mentions", m.getAllowedMentions()); + } + + body.put("flags", m.getFlags()); + + if (m.getComponents() != null) { + body.put("components", m.getComponents()); + } + + if (m.getAttachments() != null) { + body.put("attachments", m.getAttachments()); + } + + if (m.getPoll() != null) { + body.put("poll", m.getPoll()); + } + }); + + return new DiscordRequestBuilder() + .patch() + .body(body) + .path("/webhooks/%s/%s/messages/@original".formatted(applicationId, interactionToken)); } } diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/RespondCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/RespondCommandRequest.java new file mode 100644 index 00000000..0e57d679 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/RespondCommandRequest.java @@ -0,0 +1,32 @@ +package com.javadiscord.jdi.internal.api.application_commands; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import com.javadiscord.jdi.core.api.builders.command.CallbackMessage; +import com.javadiscord.jdi.core.api.builders.command.CallbackResponseType; +import com.javadiscord.jdi.internal.api.DiscordRequest; +import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; + +public record RespondCommandRequest( + CallbackResponseType type, + Optional message, + long interactionId, + String interactionToken +) implements DiscordRequest { + + @Override + public DiscordRequestBuilder create() { + + Map body = new HashMap<>(); + body.put("type", type.getValue()); + + message.ifPresent(m -> body.put("data", m)); + + return new DiscordRequestBuilder() + .post() + .body(body) + .path("/interactions/%s/%s/callback".formatted(interactionId, interactionToken)); + } +} diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java index e2fad556..7b5c978c 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java @@ -1,16 +1,110 @@ package com.javadiscord.jdi.core.interaction; +import java.util.Optional; + import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.GatewayEventListener; import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.api.AsyncResponse; +import com.javadiscord.jdi.core.api.builders.command.CallbackMessage; +import com.javadiscord.jdi.core.api.builders.command.CallbackMessageBuilder; +import com.javadiscord.jdi.core.api.builders.command.CallbackResponseType; import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; import com.javadiscord.jdi.core.models.channel.Channel; import com.javadiscord.jdi.core.models.guild.Interaction; import com.javadiscord.jdi.core.models.guild.InteractionData; import com.javadiscord.jdi.core.models.guild.InteractionType; import com.javadiscord.jdi.core.models.user.User; +import com.javadiscord.jdi.internal.api.DiscordResponseFuture; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SlashCommandEvent { + private static final Logger LOGGER = LogManager.getLogger(SlashCommandEvent.class); + private final Interaction interaction; + private final Discord discord; + private boolean deferred; + + public SlashCommandEvent(Interaction interaction, Discord discord) { + this.interaction = interaction; + this.discord = discord; + this.deferred = false; + } + + public void deferReply() { + CallbackMessageBuilder builder = + new CallbackMessageBuilder( + CallbackResponseType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, + interaction.id(), + interaction.token() + ); + + DiscordResponseFuture future = discord.sendRequest(builder.build()); + + future.onError(err -> LOGGER.error("Failed to defer response", err)); + deferred = true; + } -public record SlashCommandEvent(Interaction interaction, Discord discord) { + public void deferReplyWithoutSpinner() { + CallbackMessageBuilder builder = + new CallbackMessageBuilder( + CallbackResponseType.DEFERRED_UPDATE_MESSAGE, + interaction.id(), + interaction.token() + ); + + DiscordResponseFuture future = discord.sendRequest(builder.build()); + + future.onError(err -> LOGGER.error("Failed to defer response", err)); + deferred = true; + } + + public AsyncResponse reply(String message) { + AsyncResponse asyncResponse = new AsyncResponse<>(); + + if (!deferred) { + CallbackMessageBuilder builder = + new CallbackMessageBuilder( + CallbackResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + interaction.id(), + interaction.token() + ); + builder.message(new CallbackMessage().setContent(message)); + DiscordResponseFuture future = discord.sendRequest(builder.build()); + future.onSuccess(res -> { + if (res.status() >= 200 && res.status() < 300) { + asyncResponse.setResult(res.body()); + } else { + asyncResponse.setException( + new Exception("Received " + res.status() + "\n" + res.body()) + ); + } + }); + future.onError(asyncResponse::setException); + return asyncResponse; + } + + CallbackMessageBuilder builder = + new CallbackMessageBuilder( + CallbackResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + interaction.id(), + interaction.token() + ); + builder.applicationId(discord.getApplicationId()); + builder.message(new CallbackMessage().setContent(message)); + DiscordResponseFuture future = discord.sendRequest(builder.buildEdit()); + future.onSuccess(res -> { + if (res.status() >= 200 && res.status() < 300) { + asyncResponse.setResult(res.body()); + } else { + asyncResponse + .setException(new Exception("Received " + res.status() + "\n" + res.body())); + } + }); + future.onError(asyncResponse::setException); + return asyncResponse; + } public Channel channel() { return interaction.channel(); @@ -32,15 +126,15 @@ public InteractionData interactionData() { return interaction.data(); } - public ApplicationCommandOption option(String name) { + public Optional option(String name) { InteractionData interactionData = interaction.data(); ApplicationCommandOption[] options = interactionData.options(); for (ApplicationCommandOption option : options) { if (option.name().equals(name)) { - return option; + return Optional.of(option); } } - return null; + return Optional.empty(); } public ApplicationCommandOption[] options() { diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index 536c5eb3..f93d262b 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -1,11 +1,12 @@ package com.javadiscord.jdi.example; +import java.util.Optional; + import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.annotations.CommandOption; import com.javadiscord.jdi.core.annotations.CommandOptionChoice; import com.javadiscord.jdi.core.annotations.SlashCommand; -import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; @@ -53,50 +54,57 @@ public class ExampleSlashCommand { } ) public void handle(SlashCommandEvent event, Guild guild) { - ApplicationCommandOption q1 = event.option("q1"); - ApplicationCommandOption q2 = event.option("q2"); - ApplicationCommandOption q3 = event.option("q3"); - ApplicationCommandOption q4 = event.option("q4"); - ApplicationCommandOption q5 = event.option("q5"); + event.deferReply(); + + Optional q1 = event.option("q1"); + Optional q2 = event.option("q2"); + Optional q3 = event.option("q3"); + Optional q4 = event.option("q4"); + Optional q5 = event.option("q5"); - // Check answers and prepare feedback StringBuilder feedback = new StringBuilder(); - int score = 0; - if (q1.toString().equals("option1")) { - score++; - feedback.append("Q1: Correct!\n"); - } else { - feedback.append("Q1: Incorrect\n"); - } + q1.ifPresent(answer -> { + if (answer.valueAsString().equals("option1")) { + feedback.append("Q1: Correct!\n"); + } else { + feedback.append("Q1: Incorrect\n"); + } + }); - if (q2.toString().equals("option1")) { - score++; - feedback.append("Q2: Correct!\n"); - } else { - feedback.append("Q1: Incorrect\n"); - } + q2.ifPresent(answer -> { + if (answer.valueAsString().equals("option1")) { + feedback.append("Q2: Correct!\n"); + } else { + feedback.append("Q2: Incorrect\n"); + } + }); - if (q3.toString().equals("option1")) { - score++; - feedback.append("Q3: Correct!\n"); - } else { - feedback.append("Q1: Incorrect\n"); - } + q3.ifPresent(answer -> { + if (answer.valueAsString().equals("option1")) { + feedback.append("Q3: Correct!\n"); + } else { + feedback.append("Q3: Incorrect\n"); + } + }); - if (q4.toString().equals("option2")) { - score++; - feedback.append("Q4: Correct!\n"); - } else { - feedback.append("Q1: Incorrect\n"); - } + q4.ifPresent(answer -> { + if (answer.valueAsString().equals("option2")) { + feedback.append("Q4: Correct!\n"); + } else { + feedback.append("Q4: Incorrect\n"); + } + }); - if (q5.toString().equals("option3")) { - score++; - feedback.append("Q5: Correct!\n"); - } else { - feedback.append("Q1: Incorrect\n"); - } + q5.ifPresent(answer -> { + if (answer.valueAsString().equals("option3")) { + feedback.append("Q5: Correct!\n"); + } else { + feedback.append("Q5: Incorrect\n"); + } + }); + + int score = score(feedback.toString()); feedback.append("Your score: ").append(score).append("/5"); @@ -104,8 +112,18 @@ public void handle(SlashCommandEvent event, Guild guild) { feedback.append("\nCongratulations! You you all the questions right!\n"); } - guild.channel().createMessage( - new CreateMessageBuilder(event.channel().id()).content(feedback.toString()) - ); + event.reply(feedback.toString()); + } + + private static int score(String str) { + String word = "Correct"; + int index = 0; + int count = 0; + while ((index = str.indexOf(word, index)) != -1) { + count++; + index += word.length(); + } + return count; } + } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java index 2a21b038..a9e838b5 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java @@ -8,6 +8,8 @@ public class MessageUpdateHandler implements EventHandler { @Override public void handle(Message event, ConnectionMediator connectionMediator, Cache cache) { - cache.getCacheForGuild(event.guildId()).update(event.id(), event); + if (!event.author().bot()) { + cache.getCacheForGuild(event.guildId()).update(event.id(), event); + } } } From 373e85ce69bf62a9a8aba5ef4233f7f5ea13426b Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Mon, 27 May 2024 17:18:23 +0100 Subject: [PATCH 14/57] * Embed builder * Added support for embeds in reply --- .../core/interaction/SlashCommandEvent.java | 67 +++++----- .../jdi/example/ExampleSlashCommand.java | 12 +- .../jdi/core/models/message/embed/Embed.java | 123 +++++++++++++++++- 3 files changed, 170 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java index 7b5c978c..96a35ef7 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java @@ -1,5 +1,6 @@ package com.javadiscord.jdi.core.interaction; +import java.util.List; import java.util.Optional; import com.javadiscord.jdi.core.Discord; @@ -14,6 +15,7 @@ import com.javadiscord.jdi.core.models.guild.Interaction; import com.javadiscord.jdi.core.models.guild.InteractionData; import com.javadiscord.jdi.core.models.guild.InteractionType; +import com.javadiscord.jdi.core.models.message.embed.Embed; import com.javadiscord.jdi.core.models.user.User; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; @@ -60,49 +62,54 @@ public void deferReplyWithoutSpinner() { deferred = true; } - public AsyncResponse reply(String message) { - AsyncResponse asyncResponse = new AsyncResponse<>(); + public AsyncResponse reply(CallbackMessageBuilder builder) { + return sendReply(builder); + } - if (!deferred) { - CallbackMessageBuilder builder = - new CallbackMessageBuilder( - CallbackResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - interaction.id(), - interaction.token() - ); - builder.message(new CallbackMessage().setContent(message)); - DiscordResponseFuture future = discord.sendRequest(builder.build()); - future.onSuccess(res -> { - if (res.status() >= 200 && res.status() < 300) { - asyncResponse.setResult(res.body()); - } else { - asyncResponse.setException( - new Exception("Received " + res.status() + "\n" + res.body()) - ); - } - }); - future.onError(asyncResponse::setException); - return asyncResponse; - } + public AsyncResponse reply(Embed... embed) { + return sendReply( + new CallbackMessageBuilder( + CallbackResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + interaction.id(), + interaction.token() + ) + .message(new CallbackMessage().setEmbeds(List.of(embed))) + ); + } - CallbackMessageBuilder builder = + public AsyncResponse reply(String message) { + return sendReply( new CallbackMessageBuilder( CallbackResponseType.CHANNEL_MESSAGE_WITH_SOURCE, interaction.id(), interaction.token() - ); - builder.applicationId(discord.getApplicationId()); - builder.message(new CallbackMessage().setContent(message)); - DiscordResponseFuture future = discord.sendRequest(builder.buildEdit()); + ) + .message(new CallbackMessage().setContent(message)) + ); + } + + private AsyncResponse sendReply(CallbackMessageBuilder builder) { + AsyncResponse asyncResponse = new AsyncResponse<>(); + DiscordResponseFuture future; + + if (deferred) { + builder.applicationId(discord.getApplicationId()); + future = discord.sendRequest(builder.buildEdit()); + } else { + future = discord.sendRequest(builder.build()); + } + future.onSuccess(res -> { if (res.status() >= 200 && res.status() < 300) { asyncResponse.setResult(res.body()); } else { - asyncResponse - .setException(new Exception("Received " + res.status() + "\n" + res.body())); + asyncResponse.setException( + new Exception("Received " + res.status() + "\n" + res.body()) + ); } }); future.onError(asyncResponse::setException); + return asyncResponse; } diff --git a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java index f93d262b..785feafb 100644 --- a/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java +++ b/example/echo-bot/src/main/java/com/javadiscord/jdi/example/ExampleSlashCommand.java @@ -1,5 +1,6 @@ package com.javadiscord.jdi.example; +import java.awt.*; import java.util.Optional; import com.javadiscord.jdi.core.CommandOptionType; @@ -9,6 +10,7 @@ import com.javadiscord.jdi.core.annotations.SlashCommand; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; +import com.javadiscord.jdi.core.models.message.embed.Embed; public class ExampleSlashCommand { @@ -112,7 +114,15 @@ public void handle(SlashCommandEvent event, Guild guild) { feedback.append("\nCongratulations! You you all the questions right!\n"); } - event.reply(feedback.toString()); + Embed embed = + new Embed.Builder() + .color(Color.CYAN) + .description(feedback.toString()) + .build(); + + event.reply(embed) + .onSuccess(System.out::println) + .onError(System.err::println); } private static int score(String str) { diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/message/embed/Embed.java b/models/src/main/java/com/javadiscord/jdi/core/models/message/embed/Embed.java index d05c607b..fdf4c3f3 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/message/embed/Embed.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/message/embed/Embed.java @@ -1,5 +1,7 @@ package com.javadiscord.jdi.core.models.message.embed; +import java.awt.*; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -24,4 +26,123 @@ public record Embed( @JsonProperty("provider") EmbedProvider provider, @JsonProperty("author") EmbedAuthor author, @JsonProperty("fields") List fields -) {} +) { + public static class Builder { + private String title; + private String type; + private String description; + private String url; + private Date timestamp; + private Integer color; + private EmbedFooter footer; + private EmbedImage image; + private EmbedThumbnail thumbnail; + private EmbedVideo video; + private EmbedProvider provider; + private EmbedAuthor author; + private List fields = new ArrayList<>(); + + public Builder title(String title) { + this.title = title; + return this; + } + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder timestamp(Date timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder color(Color color) { + this.color = color.getRGB() & 0xFFFFFF; + return this; + } + + public Builder footer(EmbedFooter footer) { + this.footer = footer; + return this; + } + + public Builder footer(String content) { + this.footer = new EmbedFooter(content, null, null); + return this; + } + + public Builder image(String imageURL) { + this.image = new EmbedImage(imageURL, null, null, null); + return this; + } + + public Builder image(EmbedImage image) { + this.image = image; + return this; + } + + public Builder thumbnail(String imageURL) { + this.thumbnail = new EmbedThumbnail(imageURL, null, null, null); + return this; + } + + public Builder thumbnail(EmbedThumbnail thumbnail) { + this.thumbnail = thumbnail; + return this; + } + + public Builder video(EmbedVideo video) { + this.video = video; + return this; + } + + public Builder provider(EmbedProvider provider) { + this.provider = provider; + return this; + } + + public Builder author(EmbedAuthor author) { + this.author = author; + return this; + } + + public Builder fields(List fields) { + this.fields = fields; + return this; + } + + public Builder addField(EmbedField field) { + this.fields.add(field); + return this; + } + + public Embed build() { + return new Embed( + title, + type, + description, + url, + timestamp, + color, + footer, + image, + thumbnail, + video, + provider, + author, + fields + ); + } + } +} From 1ee56178e6900d1d8d9bf30e8eedd0e46dbf554a Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Mon, 27 May 2024 19:52:56 +0100 Subject: [PATCH 15/57] Fix and features --- .../com/javadiscord/jdi/core/processor/ClassFileUtil.java | 4 +++- .../jdi/core/GatewayEventListenerAnnotations.java | 2 ++ .../jdi/core/interaction/SlashCommandEvent.java | 2 +- .../java/com/javadiscord/jdi/core/models/user/User.java | 8 +++++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java index 8d7f3d57..0ee7ce82 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java @@ -13,6 +13,7 @@ public class ClassFileUtil { private static final List classesInPath = new ArrayList<>(); + private static boolean loadedParentJar = false; private ClassFileUtil() {} @@ -72,7 +73,8 @@ private static List getClasses(File file) throws IOException { List classFiles = new ArrayList<>(); if (file.isDirectory()) { classFiles.addAll(getClassesFromDirectory(file)); - } else if (isJarFile(file)) { + } else if (isJarFile(file) && !loadedParentJar) { + loadedParentJar = true; classFiles.addAll(getClassesFromJar(file)); } else if (file.getName().endsWith(".class")) { classFiles.add(file); diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java index 3953f356..e07af5ef 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java @@ -254,7 +254,9 @@ public void receive(EventType eventType, Object event) { ); } LOGGER.trace("Invoking method {} with params {}", method.getName(), paramOrder); + method.invoke(listener, paramOrder.toArray()); + } catch (Exception e) { LOGGER.error("Failed to invoke {}", method.getName(), e); throw new RuntimeException(e); diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java index 96a35ef7..7fedf20b 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java @@ -122,7 +122,7 @@ public Guild guild() { } public User user() { - return interaction.user(); + return interaction.member().user(); } public InteractionType interactionType() { diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/user/User.java b/models/src/main/java/com/javadiscord/jdi/core/models/user/User.java index 0880b653..9e3f90e5 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/user/User.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/user/User.java @@ -22,4 +22,10 @@ public record User( @JsonProperty("premium_type") PremiumType premiumType, @JsonProperty("public_flags") int publicFlags, @JsonProperty("avtar_decoration") String avatarDecoration -) {} +) { + + public String asMention() { + return "<@" + id + ">"; + } + +} From 770d25c79d66c6193b6a8952295aae4063080d14 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Mon, 27 May 2024 20:38:08 +0100 Subject: [PATCH 16/57] Use JavaTimeModule module with all OBJECT_MAPPER references --- .../javadiscord/jdi/core/api/DiscordResponseParser.java | 5 ++++- .../jdi/core/api/builders/CreateMessageBuilder.java | 4 ++-- core/src/main/java/com/javadiscord/jdi/core/Discord.java | 5 ++++- .../jdi/internal/gateway/WebSocketHandler.java | 5 ++++- .../events/codec/decoders/AutoModerationDecoder.java | 5 ++++- .../handlers/events/codec/decoders/ChannelDecoder.java | 5 ++++- .../events/codec/decoders/ChannelPinUpdateDecoder.java | 5 ++++- .../events/codec/decoders/EntitlementDecoder.java | 5 ++++- .../handlers/events/codec/decoders/EventUserDecoder.java | 5 ++++- .../handlers/events/codec/decoders/GuildBanDecoder.java | 5 ++++- .../handlers/events/codec/decoders/GuildDecoder.java | 5 ++++- .../events/codec/decoders/GuildInviteDecoder.java | 5 ++++- .../events/codec/decoders/GuildMemberDecoder.java | 5 ++++- .../handlers/events/codec/decoders/GuildRoleDecoder.java | 5 ++++- .../events/codec/decoders/IntegrationUpdateDecoder.java | 5 ++++- .../events/codec/decoders/InteractionCreateDecoder.java | 5 ++++- .../events/codec/decoders/MemberChunkDecoder.java | 5 ++++- .../events/codec/decoders/MessageBulkDeleteDecoder.java | 5 ++++- .../handlers/events/codec/decoders/MessageDecoder.java | 5 ++++- .../events/codec/decoders/MessageReactionDecoder.java | 5 ++++- .../codec/decoders/MessageReactionsRemovedDecoder.java | 5 ++++- .../events/codec/decoders/ReadyEventDecoder.java | 5 ++++- .../events/codec/decoders/ScheduledEventDecoder.java | 5 ++++- .../handlers/events/codec/decoders/StageDecoder.java | 5 ++++- .../events/codec/decoders/StickerUpdateDecoder.java | 5 ++++- .../handlers/events/codec/decoders/ThreadDecoder.java | 5 ++++- .../events/codec/decoders/ThreadListSyncDecoder.java | 5 ++++- .../events/codec/decoders/ThreadMemberDecoder.java | 5 ++++- .../events/codec/decoders/ThreadMemberUpdateDecoder.java | 5 ++++- .../events/codec/decoders/TypingStartDecoder.java | 5 ++++- .../handlers/events/codec/decoders/UserDecoder.java | 5 ++++- .../events/codec/decoders/VoiceServerDecoder.java | 5 ++++- .../events/codec/decoders/VoiceStateDecoder.java | 5 ++++- .../handlers/events/codec/decoders/WebhookDecoder.java | 5 ++++- .../handlers/guild/message/MessageUpdateHandler.java | 3 ++- .../handlers/heartbeat/HelloOperationHandler.java | 5 ++++- .../jdi/core/models/channel/ThreadMember.java | 7 ++----- .../jdi/core/models/channel/ThreadMetadata.java | 9 +++++++-- 38 files changed, 149 insertions(+), 44 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java b/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java index 7f188669..d8b0c2b5 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java @@ -10,9 +10,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class DiscordResponseParser { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); private final DiscordRequestDispatcher dispatcher; public DiscordResponseParser(DiscordRequestDispatcher dispatcher) { diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java index ac64c441..30ccc8c9 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java @@ -59,8 +59,8 @@ public CreateMessageBuilder tts(boolean tts) { return this; } - public CreateMessageBuilder embeds(List embeds) { - this.embeds = Optional.of(embeds); + public CreateMessageBuilder embeds(Embed... embeds) { + this.embeds = Optional.of(List.of(embeds)); return this; } diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 4faa1774..28719e91 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -32,13 +32,16 @@ import com.javadiscord.jdi.internal.gateway.identify.IdentifyRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Discord { private static final Logger LOGGER = LogManager.getLogger(Discord.class); private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); private static final String WEBSITE = "https://javadiscord.com/"; private static final String BASE_URL = diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java index 56965fc4..e8ecfd20 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java @@ -13,6 +13,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.WebSocket; @@ -21,7 +23,8 @@ public class WebSocketHandler implements Handler { private static final Logger LOGGER = LogManager.getLogger(WebSocketHandler.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); private static final Map OPERATION_HANDLER = new HashMap<>(); private final ConnectionMediator connectionMediator; private final Cache cache; diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/AutoModerationDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/AutoModerationDecoder.java index 5977f84d..61c84285 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/AutoModerationDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/AutoModerationDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class AutoModerationDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public AutoModerationRule decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelDecoder.java index a34b838f..746fb808 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ChannelDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Channel decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelPinUpdateDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelPinUpdateDecoder.java index 9580bee2..f42fb6a2 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelPinUpdateDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ChannelPinUpdateDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ChannelPinUpdateDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public MessagePin decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EntitlementDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EntitlementDecoder.java index 364bc3fd..95d8cd8b 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EntitlementDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EntitlementDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class EntitlementDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Entitlement decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EventUserDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EventUserDecoder.java index e96c72cb..7101fc00 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EventUserDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/EventUserDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class EventUserDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public EventUser decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildBanDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildBanDecoder.java index 70f53c23..2c2afc8d 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildBanDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildBanDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class GuildBanDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public GuildBan decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java index e38bc128..c94ba450 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class GuildDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Guild decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildInviteDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildInviteDecoder.java index 8de1624b..39852683 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildInviteDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildInviteDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class GuildInviteDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Invite decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildMemberDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildMemberDecoder.java index 414d4dec..a9a8f0bb 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildMemberDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildMemberDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class GuildMemberDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Member decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildRoleDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildRoleDecoder.java index 54d6e98f..8508b153 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildRoleDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildRoleDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class GuildRoleDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Role decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/IntegrationUpdateDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/IntegrationUpdateDecoder.java index a6c290de..37abf3a1 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/IntegrationUpdateDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/IntegrationUpdateDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class IntegrationUpdateDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public IntegrationUpdate decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/InteractionCreateDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/InteractionCreateDecoder.java index 9f9d137d..201cf285 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/InteractionCreateDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/InteractionCreateDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class InteractionCreateDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Interaction decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MemberChunkDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MemberChunkDecoder.java index 5bac2ad2..9b962ca4 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MemberChunkDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MemberChunkDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class MemberChunkDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public MemberChunk decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageBulkDeleteDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageBulkDeleteDecoder.java index cbb3305f..09d08fbf 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageBulkDeleteDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageBulkDeleteDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class MessageBulkDeleteDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public MessageBulkDelete decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageDecoder.java index 70562234..c24b8d40 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class MessageDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Message decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionDecoder.java index f222cfba..3ea9870c 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class MessageReactionDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public MessageReaction decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionsRemovedDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionsRemovedDecoder.java index 224567f9..f90e8a6c 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionsRemovedDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/MessageReactionsRemovedDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class MessageReactionsRemovedDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public MessageReactionsRemoved decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ReadyEventDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ReadyEventDecoder.java index 3523b042..ff78e4bf 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ReadyEventDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ReadyEventDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ReadyEventDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public ReadyEvent decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ScheduledEventDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ScheduledEventDecoder.java index 93d81284..1fa1c0a1 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ScheduledEventDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ScheduledEventDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ScheduledEventDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public ScheduledEvent decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StageDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StageDecoder.java index aa1a02a1..569c69c3 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StageDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StageDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class StageDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Stage decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StickerUpdateDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StickerUpdateDecoder.java index d663f2c6..39e303d9 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StickerUpdateDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/StickerUpdateDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class StickerUpdateDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public StickerUpdate decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadDecoder.java index 8a533030..34ddb514 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ThreadDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public Thread decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadListSyncDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadListSyncDecoder.java index 3276a7c0..3f2c89f5 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadListSyncDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadListSyncDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ThreadListSyncDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public ThreadSync decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberDecoder.java index c7913a68..8e46110b 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ThreadMemberDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public ThreadMember decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberUpdateDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberUpdateDecoder.java index a822d8fe..6219c813 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberUpdateDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/ThreadMemberUpdateDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class ThreadMemberUpdateDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public ThreadMemberUpdate decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/TypingStartDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/TypingStartDecoder.java index 2b8b51cb..b89c242c 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/TypingStartDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/TypingStartDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class TypingStartDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public TypingStart decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/UserDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/UserDecoder.java index 281b1d06..6a186388 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/UserDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/UserDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class UserDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public User decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceServerDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceServerDecoder.java index da353049..6b40acde 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceServerDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceServerDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class VoiceServerDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public VoiceServer decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceStateDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceStateDecoder.java index 5523876d..c2b676ca 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceStateDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/VoiceStateDecoder.java @@ -6,9 +6,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class VoiceStateDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public VoiceState decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java index d09a2f26..5c487a44 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java @@ -5,9 +5,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class WebhookDecoder implements EventDecoder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override public WebhookDecoder decode(GatewayEvent gatewayEvent) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java index a9e838b5..50b41680 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/message/MessageUpdateHandler.java @@ -6,9 +6,10 @@ import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventHandler; public class MessageUpdateHandler implements EventHandler { + @Override public void handle(Message event, ConnectionMediator connectionMediator, Cache cache) { - if (!event.author().bot()) { + if (event.author() != null && !event.author().bot()) { cache.getCacheForGuild(event.guildId()).update(event.id(), event); } } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HelloOperationHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HelloOperationHandler.java index a087d4ac..53c9e081 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HelloOperationHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HelloOperationHandler.java @@ -9,12 +9,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class HelloOperationHandler implements GatewayOperationHandler { private static final Logger LOGGER = LogManager.getLogger(HelloOperationHandler.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = + JsonMapper.builder().addModule(new JavaTimeModule()).build(); private final HeartbeatService heartbeatService; public HelloOperationHandler(HeartbeatService heartbeatService) { diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMember.java b/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMember.java index e6ac211d..e25c59c4 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMember.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMember.java @@ -1,10 +1,7 @@ package com.javadiscord.jdi.core.models.channel; -import java.time.OffsetDateTime; - import com.javadiscord.jdi.core.models.guild.GuildMember; -import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -12,9 +9,9 @@ public record ThreadMember( @JsonProperty("id") long threadId, @JsonProperty("user_id") long userId, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssX") @JsonProperty( + @JsonProperty( "join_timestamp" - ) OffsetDateTime joinTime, + ) String joinTime, @JsonProperty("flags") int flags, @JsonProperty("member") GuildMember guildMember ) {} diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMetadata.java b/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMetadata.java index 85a6b815..b1423ee4 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMetadata.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/channel/ThreadMetadata.java @@ -1,5 +1,6 @@ package com.javadiscord.jdi.core.models.channel; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,8 +8,12 @@ public record ThreadMetadata( @JsonProperty("archived") boolean archived, @JsonProperty("auto_archive_duration") int autoArchiveDuration, - @JsonProperty("archive_timestamp") long archiveTimestamp, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssX") @JsonProperty( + "archive_timestamp" + ) String archiveTimestamp, @JsonProperty("locked") boolean locked, @JsonProperty("invitable") boolean invitable, - @JsonProperty("create_timestamp") String createTimestamp + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssX") @JsonProperty( + "create_timestamp" + ) String createTimestamp ) {} From 02492ce636825ed16d122d434849bc837aa05f08 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Mon, 27 May 2024 20:38:42 +0100 Subject: [PATCH 17/57] Port over lj-discord-bot into examples --- example/lj-discord-bot/build.gradle | 21 + .../main/java/com/javadiscord/bot/Main.java | 10 + .../bot/commands/slash/ChatGPTCommand.java | 60 + .../bot/commands/slash/PingSlashCommand.java | 14 + .../commands/slash/jshell/JShellCommand.java | 117 ++ .../commands/slash/jshell/JShellResponse.java | 14 + .../commands/slash/jshell/JShellService.java | 65 + .../commands/slash/jshell/JShellSnippet.java | 11 + .../bot/commands/text/TextCommand.java | 8 + .../commands/text/TextCommandRepository.java | 22 + .../text/impl/ClearChannelCommand.java | 39 + .../bot/commands/text/impl/MuteCommand.java | 21 + .../bot/commands/text/impl/SayCommand.java | 14 + .../commands/text/impl/SayEmbedCommand.java | 18 + .../bot/commands/text/impl/UnmuteCommand.java | 21 + .../listeners/RolePlayMessageListener.java | 75 + .../bot/listeners/SlashCommandListener.java | 27 + .../bot/listeners/SpamListener.java | 41 + .../bot/listeners/SuggestionListener.java | 33 + .../bot/listeners/TextCommandListener.java | 45 + .../com/javadiscord/bot/utils/CurseWords.java | 1241 +++++++++++++++++ .../com/javadiscord/bot/utils/Executor.java | 22 + .../java/com/javadiscord/bot/utils/Tenor.java | 49 + .../bot/utils/chatgpt/ChatGPT.java | 114 ++ .../utils/chatgpt/ChatGPTResponseParser.java | 69 + settings.gradle | 2 + 26 files changed, 2173 insertions(+) create mode 100644 example/lj-discord-bot/build.gradle create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/PingSlashCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/ClearChannelCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/MuteCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayEmbedCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/UnmuteCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SlashCommandListener.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/TextCommandListener.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java diff --git a/example/lj-discord-bot/build.gradle b/example/lj-discord-bot/build.gradle new file mode 100644 index 00000000..99e06c01 --- /dev/null +++ b/example/lj-discord-bot/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +application { + mainClass = 'com.javadiscord.bot.Main' +} + +dependencies { + implementation 'com.github.docker-java:docker-java:3.3.6' + implementation 'com.theokanning.openai-gpt3-java:service:0.18.2' + implementation 'com.rometools:rome:2.1.0' +} + +shadowJar { + archiveBaseName.set('lj-discord-bot') + archiveClassifier.set('') + archiveVersion.set('') +} \ No newline at end of file diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java new file mode 100644 index 00000000..48fb75c8 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java @@ -0,0 +1,10 @@ +package com.javadiscord.bot; + +import com.javadiscord.jdi.core.Discord; + +public class Main { + public static void main(String[] args) { + Discord discord = new Discord(System.getenv("BOT_TOKEN")); + discord.start(); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java new file mode 100644 index 00000000..a99b7717 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java @@ -0,0 +1,60 @@ +package com.javadiscord.bot.commands.slash; + +import java.awt.*; + +import com.javadiscord.bot.utils.chatgpt.ChatGPT; +import com.javadiscord.jdi.core.CommandOptionType; +import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.interaction.SlashCommandEvent; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; + +public class ChatGPTCommand { + private final ChatGPT chatGPT = new ChatGPT(); + + @SlashCommand( + name = "chatgpt", description = "Ask ChatGPT a question", options = { + @CommandOption( + name = "message", description = "What would you like to ask?", type = CommandOptionType.STRING + ) + } + ) + public void handle(SlashCommandEvent event) { + event.deferReply(); + Thread.ofVirtual().start(() -> handleCommand(event)); + } + + private void handleCommand(SlashCommandEvent event) { + event.option("message").ifPresent(msg -> { + StringBuilder answer = new StringBuilder(); + answer.append(event.user().asMention()); + answer.append(" asked:\n"); + answer.append(msg.valueAsString()); + answer.append("\n"); + answer.append("───────────────\n"); + + chatGPT.ask(msg.valueAsString()) + .ifPresentOrElse( + strings -> { + for (String string : strings) { + answer.append(string).append("\n"); + } + }, + () -> sendChatGptUnavailableMessage(event) + ); + + Embed embed = + new Embed.Builder().color(Color.CYAN) + .description(answer.toString()) + .author( + new EmbedAuthor("", "https://chat.openai.com/favicon-32x32.png", null, null) + ).build(); + event.reply(embed); + }); + } + + private void sendChatGptUnavailableMessage(SlashCommandEvent event) { + event.reply("ChatGPT is currently unavailable."); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/PingSlashCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/PingSlashCommand.java new file mode 100644 index 00000000..3983ad08 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/PingSlashCommand.java @@ -0,0 +1,14 @@ +package com.javadiscord.bot.commands.slash; + +import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.interaction.SlashCommandEvent; + +public class PingSlashCommand { + + @SlashCommand( + name = "ping", description = "Pong!" + ) + public void ping(SlashCommandEvent event) { + event.reply("Pong!"); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java new file mode 100644 index 00000000..2df085c8 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java @@ -0,0 +1,117 @@ +package com.javadiscord.bot.commands.slash.jshell; + +import java.awt.*; + +import com.javadiscord.jdi.core.CommandOptionType; +import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.interaction.SlashCommandEvent; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; +import com.javadiscord.jdi.core.models.user.User; + +public class JShellCommand { + private final JShellService jShellService = new JShellService(); + + @SlashCommand( + name = "jshell", description = "Run Java code using JShell", options = { + @CommandOption( + name = "code", description = "The code you would like to execute", type = CommandOptionType.STRING + ) + } + ) + public void handle(SlashCommandEvent event) { + event.deferReply(); + Thread.ofVirtual().start(() -> handleJShell(event)); + } + + private void handleJShell(SlashCommandEvent event) { + User user = event.user(); + + long start = System.currentTimeMillis(); + + event.option("code").ifPresent(msg -> { + JShellResponse response = jShellService.sendRequest(msg.valueAsString()); + if (response == null) { + String reply = "Failed to execute the provided code, was it bad?"; + Embed embed = + new Embed.Builder() + .author(new EmbedAuthor(user.asMention(), user.avatar(), null, null)) + .description(reply) + .color(Color.ORANGE) + .build(); + event.reply(embed); + return; + } + + if (response.error() != null && !response.error().isEmpty()) { + String reply = + """ + An error occurred while executing command: + + ```java + %s + ``` + + %s + """ + .formatted(msg.valueAsString(), response.error()); + Embed embed = + new Embed.Builder() + .author(new EmbedAuthor(user.asMention(), user.avatar(), null, null)) + .description(reply) + .color(Color.RED) + .build(); + event.reply(embed); + return; + } + + StringBuilder sb = new StringBuilder(); + sb.append("## Snippets\n"); + for (JShellSnippet snippet : response.events()) { + sb.append("`"); + sb.append(snippet.statement()); + sb.append("`\n\n"); + sb.append("**Status**: "); + sb.append(snippet.status()); + sb.append("\n"); + + if (snippet.value() != null && !snippet.value().isEmpty()) { + sb.append("**Output**\n"); + sb.append("```java\n"); + sb.append(snippet.value()); + sb.append("```\n"); + } + } + + if (!response.outputStream().isEmpty()) { + sb.append("## Console Output\n"); + sb.append("```java\n"); + sb.append(response.outputStream()); + sb.append("```\n"); + } + + if (response.errorStream() != null && !response.errorStream().isEmpty()) { + sb.append("## Error Output\n"); + sb.append("```java\n"); + sb.append(response.errorStream()); + sb.append("```\n"); + } + + Embed.Builder embed = + new Embed.Builder() + .author(new EmbedAuthor(user.asMention(), null, null, null)); + + if (sb.length() > 4000) { + embed.description(sb.substring(0, 4000)); + } else { + embed.description(sb.toString()); + } + + embed.color(Color.GREEN); + embed.footer("Time taken: " + (System.currentTimeMillis() - start) + "ms"); + event.reply(embed.build()).onError(System.err::println); + }); + } + +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java new file mode 100644 index 00000000..32944371 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java @@ -0,0 +1,14 @@ +package com.javadiscord.bot.commands.slash.jshell; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record JShellResponse( + @JsonProperty("errorStream") String errorStream, + @JsonProperty("outputStream") String outputStream, + @JsonProperty("events") List events, + @JsonProperty("error") String error +) {} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java new file mode 100644 index 00000000..eb9b7a54 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java @@ -0,0 +1,65 @@ +package com.javadiscord.bot.commands.slash.jshell; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class JShellService { + private static final Logger LOGGER = LogManager.getLogger(JShellService.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String API_URL = System.getenv("JSHELL_API_URL"); + + private final Map> history = new HashMap<>(); + + public JShellService() {} + + public JShellResponse sendRequest(String code) { + record Request(String code) {} + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(API_URL)) + .setHeader("Content-Type", "application/json") + .POST( + HttpRequest.BodyPublishers.ofString( + OBJECT_MAPPER.writeValueAsString(new Request(code)) + ) + ) + .build(); + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + return OBJECT_MAPPER.readValue(response.body(), JShellResponse.class); + } catch (JsonProcessingException e) { + LOGGER.error("Failed to parse data received from JShell API", e); + } catch (IOException | InterruptedException e) { + LOGGER.error("Failed to send request to JShell API", e); + } + return null; + } + + public void updateHistory(long userId, String snippet) { + if (history.containsKey(userId)) { + history.get(userId).add(snippet); + } else { + history.put(userId, new ArrayList<>()); + } + } + + public List getHistory(long userId) { + if (history.containsKey(userId)) { + return history.get(userId); + } + return new ArrayList<>(); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java new file mode 100644 index 00000000..1508d38f --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java @@ -0,0 +1,11 @@ +package com.javadiscord.bot.commands.slash.jshell; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record JShellSnippet( + @JsonProperty("statement") String statement, + @JsonProperty("value") String value, + @JsonProperty("status") String status +) {} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommand.java new file mode 100644 index 00000000..4de04381 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommand.java @@ -0,0 +1,8 @@ +package com.javadiscord.bot.commands.text; + +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.models.message.Message; + +public interface TextCommand { + void handle(Guild guild, Message message, String input); +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java new file mode 100644 index 00000000..ee308d7e --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java @@ -0,0 +1,22 @@ +package com.javadiscord.bot.commands.text; + +import java.util.HashMap; +import java.util.Map; + +import com.javadiscord.bot.commands.text.impl.*; + +public class TextCommandRepository { + private static final Map COMMANDS = new HashMap<>(); + + static { + COMMANDS.put("clear", new ClearChannelCommand()); + COMMANDS.put("mute", new MuteCommand()); + COMMANDS.put("unmute", new UnmuteCommand()); + COMMANDS.put("say", new SayCommand()); + COMMANDS.put("embed", new SayEmbedCommand()); + } + + public static TextCommand get(String key) { + return COMMANDS.get(key); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/ClearChannelCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/ClearChannelCommand.java new file mode 100644 index 00000000..3b34dab9 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/ClearChannelCommand.java @@ -0,0 +1,39 @@ +package com.javadiscord.bot.commands.text.impl; + +import java.util.List; + +import com.javadiscord.bot.commands.text.TextCommand; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.api.builders.FetchChannelMessagesBuilder; +import com.javadiscord.jdi.core.models.message.Message; + +public class ClearChannelCommand implements TextCommand { + @Override + public void handle(Guild guild, Message message, String input) { + int limit; + try { + limit = Integer.parseInt(input); + if (limit <= 1) { + limit = 2; + } + if (limit > 100) { + limit = 100; + } + } catch (Exception e) { + limit = 10; + } + + guild.channel() + .fetchChannelMessages(new FetchChannelMessagesBuilder(message.channelId(), limit)) + .onSuccess(messages -> { + + List ids = + messages.stream() + .map(Message::id) + .toList(); + + guild.channel().bulkDeleteMessages(message.channelId(), ids); + }); + + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/MuteCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/MuteCommand.java new file mode 100644 index 00000000..e3ee603c --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/MuteCommand.java @@ -0,0 +1,21 @@ +package com.javadiscord.bot.commands.text.impl; + +import com.javadiscord.bot.commands.text.TextCommand; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.models.guild.Role; +import com.javadiscord.jdi.core.models.message.Message; + +public class MuteCommand implements TextCommand { + + @Override + public void handle(Guild guild, Message message, String input) { + guild.guild().guildRoles().onSuccess(roles -> { + for (Role role : roles) { + if (role.name().equals("Muted")) { + guild.guild().addGuildMemberRole(message.author().id(), role.id()); + break; + } + } + }); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayCommand.java new file mode 100644 index 00000000..c9907556 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayCommand.java @@ -0,0 +1,14 @@ +package com.javadiscord.bot.commands.text.impl; + +import com.javadiscord.bot.commands.text.TextCommand; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; +import com.javadiscord.jdi.core.models.message.Message; + +public class SayCommand implements TextCommand { + + @Override + public void handle(Guild guild, Message message, String input) { + guild.channel().createMessage(new CreateMessageBuilder(message.channelId()).content(input)); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayEmbedCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayEmbedCommand.java new file mode 100644 index 00000000..a16a13c5 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/SayEmbedCommand.java @@ -0,0 +1,18 @@ +package com.javadiscord.bot.commands.text.impl; + +import com.javadiscord.bot.commands.text.TextCommand; +import com.javadiscord.bot.listeners.TextCommandListener; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; +import com.javadiscord.jdi.core.models.message.Message; + +public class SayEmbedCommand implements TextCommand { + + @Override + public void handle(Guild guild, Message message, String input) { + guild.channel().createMessage( + new CreateMessageBuilder(message.channelId()) + .embeds(TextCommandListener.create("", input, "")) + ); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/UnmuteCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/UnmuteCommand.java new file mode 100644 index 00000000..a7601910 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/impl/UnmuteCommand.java @@ -0,0 +1,21 @@ +package com.javadiscord.bot.commands.text.impl; + +import com.javadiscord.bot.commands.text.TextCommand; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.models.guild.Role; +import com.javadiscord.jdi.core.models.message.Message; + +public class UnmuteCommand implements TextCommand { + + @Override + public void handle(Guild guild, Message message, String input) { + guild.guild().guildRoles().onSuccess(roles -> { + for (Role role : roles) { + if (role.name().equals("Muted")) { + guild.guild().removeGuildMemberRole(message.author().id(), role.id()); + break; + } + } + }); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java new file mode 100644 index 00000000..44a2955c --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java @@ -0,0 +1,75 @@ +package com.javadiscord.bot.listeners; + +import java.awt.*; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import com.javadiscord.bot.utils.Tenor; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.MessageCreate; +import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; +import com.javadiscord.jdi.core.models.message.Message; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.user.User; + +import com.fasterxml.jackson.databind.JsonNode; + +@EventListener +public class RolePlayMessageListener { + + @MessageCreate + public void handleRolePlay(Message message, Guild guild) { + if (containsRolePlayAction(message.content())) { + String content = message.content(); + String[] split = content.split("-"); + String action = split[1].trim(); + + List mentions = message.mentions(); + + if (!mentions.isEmpty()) { + String from = message.author().asMention(); + StringBuilder names = new StringBuilder(); + mentions.forEach( + m -> { + names.append(m.asMention()); + names.append(" "); + } + ); + + if (names.toString().trim().equals("**")) { + return; + } + + String searchTerm = action.replaceAll(" ", "%20") + "ing%20anime"; + JsonNode json = Tenor.search(searchTerm, 50); + + if (json != null && json.has("results")) { + JsonNode results = json.get("results"); + JsonNode result = + results.get(ThreadLocalRandom.current().nextInt(results.size())); + JsonNode media = result.get("media").get(0); + JsonNode gif = media.get("gif"); + String url = gif.get("url").asText(); + + Embed embed = + new Embed.Builder() + .description("**" + from + "** " + action + " **" + names + "**") + .image(url) + .color(Color.RED) + .build(); + + guild.channel().createMessage( + new CreateMessageBuilder(message.channelId()) + .embeds(embed) + ); + } + } + + } + } + + private static boolean containsRolePlayAction(String message) { + return message.matches(".*-.*-.*") && message.startsWith("-"); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SlashCommandListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SlashCommandListener.java new file mode 100644 index 00000000..399f11bd --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SlashCommandListener.java @@ -0,0 +1,27 @@ +package com.javadiscord.bot.listeners; + +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.InteractionCreate; +import com.javadiscord.jdi.core.models.guild.Interaction; +import com.javadiscord.jdi.core.models.user.Member; +import com.javadiscord.jdi.core.models.user.User; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@EventListener +public class SlashCommandListener { + private static final Logger LOGGER = LogManager.getLogger(SlashCommandListener.class); + + @InteractionCreate + public void slashCommandLogger(Interaction interaction) { + Member member = interaction.member(); + User user = member.user(); + + LOGGER.info( + "{} used /{} in {}", user.displayName(), interaction.data().name(), + interaction.channel().name() + ); + } + +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java new file mode 100644 index 00000000..679e5387 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java @@ -0,0 +1,41 @@ +package com.javadiscord.bot.listeners; + +import com.javadiscord.bot.utils.CurseWords; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.MessageCreate; +import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; +import com.javadiscord.jdi.core.models.message.Message; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@EventListener +public class SpamListener { + private static final Logger LOGGER = LogManager.getLogger(SlashCommandListener.class); + + @MessageCreate + public void onMessage(Message message, Guild guild) { + if (!message.author().bot() && CurseWords.containsCurseWord(message.content())) { + guild.channel().deleteMessage(message.channelId(), message.id()); + guild.user() + .createDM(message.id()) + .onSuccess( + channel -> guild.channel() + .createMessage( + new CreateMessageBuilder(channel.id()).content( + """ + Your message has been removed for containing words blacklisted by this server! + Please avoid sending such messages in the future. Thank you. + + The message you sent: + > %s + """ + .formatted(message.content()) + ) + ).onError(System.err::println) + ); + } + } + +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java new file mode 100644 index 00000000..dc52ef03 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java @@ -0,0 +1,33 @@ +package com.javadiscord.bot.listeners; + +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.MessageCreate; +import com.javadiscord.jdi.core.api.builders.StartThreadWithoutMessageBuilder; +import com.javadiscord.jdi.core.models.message.Message; + +@EventListener +public class SuggestionListener { + + @MessageCreate + public void onMessage(Message message, Guild guild) { + if (message.author().bot()) { + return; + } + + if (message.channelId() != 1244690778505216154L) { + return; + } + + guild.channel().createReaction(message.channelId(), message.id(), "thumbup"); + guild.channel().createReaction(message.channelId(), message.id(), "thumbsdown"); + + guild.channel().startThreadWithoutMessage( + new StartThreadWithoutMessageBuilder( + message.channelId(), + "Suggestion!" + ) + ); + } + +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/TextCommandListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/TextCommandListener.java new file mode 100644 index 00000000..0354057c --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/TextCommandListener.java @@ -0,0 +1,45 @@ +package com.javadiscord.bot.listeners; + +import java.awt.*; + +import com.javadiscord.bot.commands.text.TextCommand; +import com.javadiscord.bot.commands.text.TextCommandRepository; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.MessageCreate; +import com.javadiscord.jdi.core.models.message.Message; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; + +@EventListener +public class TextCommandListener { + + @MessageCreate + public void handleTextCommand(Message message, Guild guild) { + if (!message.author().bot()) { + String msg = message.content(); + if (msg.startsWith("!")) { + String cmd = msg.split(" ")[0].replace("!", "").trim(); + String input = msg.replace(String.format("!%s", cmd), "").trim(); + TextCommand command = TextCommandRepository.get(cmd); + if (command != null) { + command.handle(guild, message, input); + guild.channel().deleteMessage(message.channelId(), message.id()); + } else { + System.err.println("Command not found."); + } + } + } + } + + public static Embed create(String title, String caption, String imageURL) { + Embed.Builder eb = new Embed.Builder(); + if (!imageURL.isEmpty()) { + eb.image(imageURL); + } + eb.color(Color.RED); + eb.author(new EmbedAuthor(title, null, null, null)); + eb.description(caption); + return eb.build(); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java new file mode 100644 index 00000000..147f7035 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java @@ -0,0 +1,1241 @@ +package com.javadiscord.bot.utils; + +import java.util.Arrays; +import java.util.List; + +public class CurseWords { + public static boolean containsCurseWord(String text) { + for (String s : curseWords) { + if (text.toLowerCase().contains(s.toLowerCase())) { + return true; + } + } + return false; + } + + private static final List curseWords = + Arrays.asList( + "fuck", + "shit", + "asshole", + "bitch", + "bastard", + "damn", + "hell", + "dick", + "pussy", + "cunt", + "ass", + "cock", + "motherfucker", + "bullshit", + "son of a bitch", + "allah", + "anal", + "anus", + "aroused", + "arse", + "arsehole", + "ass", + "assassinate", + "assassination", + "babe", + "babies", + "balllicker", + "ballsack", + "banging", + "baptist", + "barelylegal", + "bastard ", + "beastality", + "beastial", + "beastiality", + "beatyourmeat", + "bestial", + "bestiality", + "bi", + "biatch", + "bicurious", + "bigass", + "bigbastard", + "bigbutt", + "bigger", + "bisexual", + "bi-sexual", + "bitch", + "blow", + "blowjob", + "bollick", + "bollock", + "bondage", + "boner", + "bong", + "boob", + "boong", + "boonga", + "boonie", + "booty", + "bootycall", + "bountybar", + "bra", + "brea5t", + "breast", + "breastjob", + "breastlover", + "breastman", + "brothel", + "bugger", + "buggered", + "buggery", + "bullcrap", + "bulldike", + "bulldyke", + "bullshit", + "bumblefuck", + "bumfuck", + "bunga", + "bunghole", + "buried", + "burn", + "butchbabes", + "butchdike", + "butchdyke", + "buttbang", + "butt-bang", + "buttface", + "buttfuck", + "butt-fuck", + "buttfucker", + "butt-fucker", + "buttfuckers", + "butt-fuckers", + "butthead", + "buttman", + "buttmunch", + "buttmuncher", + "buttpirate", + "buttplug", + "buttstain", + "byatch", + "cameltoe", + "cancer", + "carpetmuncher", + "carruth", + "catholic", + "catholics", + "cemetery", + "cherrypopper", + "chinaman", + "chinamen", + "chinese", + "chink", + "chinky", + "choad", + "chode", + "cigarette", + "cigs", + "clamdigger", + "clamdiver", + "clit", + "clitoris", + "clogwog", + "cocaine", + "cock", + "cockblock", + "cockblocker", + "cockcowboy", + "cockfight", + "cockhead", + "cockknob", + "cocklicker", + "cocklover", + "cocknob", + "cockqueen", + "cockrider", + "cocksman", + "cocksmith", + "cocksmoker", + "cocksucer", + "cocksuck ", + "cocksucked ", + "cocksucker", + "cocksucking", + "cocktail", + "cocktease", + "cocky", + "cohee", + "coitus", + "color", + "colored", + "coloured", + "commie", + "communist", + "condom", + "conservative", + "conspiracy", + "coolie", + "cooly", + "coon", + "coondog", + "copulate", + "cornhole", + "corruption", + "cra5h", + "crabs", + "crack", + "crackpipe", + "crackwhore", + "crack-whore", + "crap", + "crapola", + "crapper", + "crappy", + "crash", + "creamy", + "crime", + "crimes", + "criminal", + "criminals", + "crotch", + "crotchjockey", + "crotchmonkey", + "crotchrot", + "cum", + "cumbubble", + "cumfest", + "cumjockey", + "cumm", + "cummer", + "cumming", + "cumquat", + "cumqueen", + "cumshot", + "cunilingus", + "cunillingus", + "cunn", + "cunnilingus", + "cunntt", + "cunt", + "cunteyed", + "cuntfuck", + "cuntfucker", + "cuntlick ", + "cuntlicker ", + "cuntlicking ", + "cuntsucker", + "cybersex", + "damnation", + "nigga", + "dead", + "deapthroat", + "death", + "deepthroat", + "defecate", + "dego", + "demon", + "deposit", + "desire", + "destroy", + "deth", + "devil", + "devilworshipper", + "dick", + "dickbrain", + "dickforbrains", + "dickhead", + "dickless", + "dicklick", + "dicklicker", + "dickman", + "dickwad", + "dickweed", + "diddle", + "dike", + "dildo", + "dingleberry", + "dink", + "dipshit", + "dipstick", + "dirty", + "disease", + "diseases", + "disturbed", + "dive", + "dix", + "dixiedike", + "dixiedyke", + "doggiestyle", + "doggystyle", + "dripdick", + "drug", + "dumbbitch", + "dumbfuck", + "dyefly", + "dyke", + "easyslut", + "eatballs", + "eatme", + "eatpussy", + "ecstacy", + "ejaculate", + "ejaculated", + "ejaculating ", + "ejaculation", + "erect", + "erection", + "escort", + "ethiopian", + "ethnic", + "european", + "explosion", + "facefucker", + "faeces", + "fag", + "fagging", + "faggot", + "fagot", + "failed", + "failure", + "fairies", + "fairy", + "faith", + "fannyfucker", + "fart", + "farted ", + "farting ", + "farty ", + "fastfuck", + "fatah", + "fatass", + "fatfuck", + "fatfucker", + "fatso", + "fckcum", + "feces", + "felatio ", + "felch", + "felcher", + "felching", + "fellatio", + "feltch", + "feltcher", + "feltching", + "fetish", + "fingerfood", + "fingerfuck ", + "fingerfucked ", + "fingerfucker ", + "fingerfuckers", + "fingerfucking ", + "fister", + "fistfuck", + "fistfucked ", + "fistfucker ", + "fistfucking ", + "fisting", + "flange", + "flasher", + "flatulence", + "floo", + "flydie", + "flydye", + "fok", + "fondle", + "footaction", + "footfuck", + "footfucker", + "footlicker", + "footstar", + "fore", + "foreskin", + "forni", + "fornicate", + "foursome", + "fourtwenty", + "fraud", + "freakfuck", + "freakyfucker", + "freefuck", + "fu", + "fubar", + "fuc", + "fucck", + "fuck", + "fucka", + "fuckable", + "fuckbag", + "fuckbuddy", + "fucked", + "fuckedup", + "fucker", + "fuckers", + "fuckface", + "fuckfest", + "fuckfreak", + "fuckfriend", + "fuckhead", + "fuckher", + "fuckin", + "fuckina", + "fucking", + "fuckingbitch", + "fuckinnuts", + "fuckinright", + "fuckit", + "fuckknob", + "fuckme ", + "fuckmehard", + "fuckmonkey", + "fuckoff", + "fuckpig", + "fucks", + "fucktard", + "fuckwhore", + "fuckyou", + "fudgepacker", + "fugly", + "fuk", + "fuks", + "funeral", + "funfuck", + "fungus", + "fuuck", + "gangbang", + "gangbanged ", + "gangbanger", + "gangsta", + "gatorbait", + "gay", + "gaymuthafuckinwhore", + "gaysex ", + "geez", + "geezer", + "geni", + "genital", + "german", + "getiton", + "gin", + "ginzo", + "gipp", + "girls", + "givehead", + "glazeddonut", + "gob", + "god", + "godammit", + "goddamit", + "goddammit", + "goddamn", + "goddamned", + "goddamnes", + "goddamnit", + "goddamnmuthafucker", + "goldenshower", + "gonorrehea", + "gonzagas", + "gook", + "gotohell", + "goy", + "goyim", + "greaseball", + "gringo", + "groe", + "gross", + "grostulation", + "gubba", + "gummer", + "gun", + "gyp", + "gypo", + "gypp", + "gyppie", + "gyppo", + "gyppy", + "hamas", + "handjob", + "hapa", + "harder", + "hardon", + "harem", + "headfuck", + "headlights", + "hebe", + "heeb", + "hell", + "henhouse", + "heroin", + "herpes", + "heterosexual", + "hijack", + "hijacker", + "hijacking", + "hillbillies", + "hindoo", + "hiscock", + "hitler", + "hitlerism", + "hitlerist", + "hiv", + "ho", + "hobo", + "hodgie", + "hoes", + "hole", + "holestuffer", + "homicide", + "homo", + "homobangers", + "homosexual", + "honger", + "honk", + "honkers", + "honkey", + "honky", + "hook", + "hooker", + "hookers", + "hooters", + "hore", + "hork", + "horn", + "horney", + "horniest", + "horny", + "horseshit", + "hosejob", + "hoser", + "hostage", + "hotdamn", + "hotpussy", + "hottotrot", + "hummer", + "husky", + "hussy", + "hustler", + "hymen", + "hymie", + "iblowu", + "idiot", + "ikey", + "illegal", + "incest", + "insest", + "intercourse", + "interracial", + "intheass", + "inthebuff", + "israel", + "israeli", + "israel's", + "italiano", + "itch", + "jackass", + "jackoff", + "jackshit", + "jacktheripper", + "jade", + "jap", + "japanese", + "japcrap", + "jebus", + "jeez", + "jerkoff", + "jesus", + "jesuschrist", + "jew", + "jewish", + "jiga", + "jigaboo", + "jigg", + "jigga", + "jiggabo", + "jigger ", + "jiggy", + "jihad", + "jijjiboo", + "jimfish", + "jism", + "jiz ", + "jizim", + "jizjuice", + "jizm ", + "jizz", + "jizzim", + "jizzum", + "joint", + "juggalo", + "jugs", + "junglebunny", + "kaffer", + "kaffir", + "kaffre", + "kafir", + "kanake", + "kid", + "kigger", + "kike", + "kill", + "killed", + "killer", + "killing", + "kills", + "kink", + "kinky", + "kissass", + "kkk", + "knife", + "knockers", + "kock", + "kondum", + "koon", + "kotex", + "krap", + "krappy", + "kraut", + "kum", + "kumbubble", + "kumbullbe", + "kummer", + "kumming", + "kumquat", + "kums", + "kunilingus", + "kunnilingus", + "kunt", + "ky", + "kyke", + "lactate", + "laid", + "lapdance", + "latin", + "lesbain", + "lesbayn", + "lesbian", + "lesbin", + "lesbo", + "lez", + "lezbe", + "lezbefriends", + "lezbo", + "lezz", + "lezzo", + "liberal", + "libido", + "licker", + "lickme", + "lies", + "limey", + "limpdick", + "limy", + "lingerie", + "liquor", + "livesex", + "loadedgun", + "lolita", + "looser", + "loser", + "lotion", + "lovebone", + "lovegoo", + "lovegun", + "lovejuice", + "lovemuscle", + "lovepistol", + "loverocket", + "lowlife", + "lsd", + "lubejob", + "lucifer", + "luckycammeltoe", + "lugan", + "lynch", + "macaca", + "mad", + "mafia", + "magicwand", + "mams", + "manhater", + "manpaste", + "marijuana", + "mastabate", + "mastabater", + "masterbate", + "masterblaster", + "mastrabator", + "masturbate", + "masturbating", + "mattressprincess", + "meatbeatter", + "meatrack", + "meth", + "mexican", + "mgger", + "mggor", + "mickeyfinn", + "mideast", + "milf", + "minority", + "mockey", + "mockie", + "mocky", + "mofo", + "moky", + "moles", + "molest", + "molestation", + "molester", + "molestor", + "moneyshot", + "mooncricket", + "mormon", + "moron", + "moslem", + "mosshead", + "mothafuck", + "mothafucka", + "mothafuckaz", + "mothafucked ", + "mothafucker", + "mothafuckin", + "mothafucking ", + "mothafuckings", + "motherfuck", + "motherfucked", + "motherfucker", + "motherfuckin", + "motherfucking", + "motherfuckings", + "motherlovebone", + "muff", + "muffdive", + "muffdiver", + "muffindiver", + "mufflikcer", + "mulatto", + "muncher", + "munt", + "murder", + "murderer", + "muslim", + "naked", + "narcotic", + "nasty", + "nastybitch", + "nastyho", + "nastyslut", + "nastywhore", + "nazi", + "necro", + "negro", + "negroes", + "negroid", + "negro's", + "nig", + "niger", + "nigerian", + "nigerians", + "nigg", + "nigga", + "niggah", + "niggaracci", + "niggard", + "niggarded", + "niggarding", + "niggardliness", + "niggardliness's", + "niggardly", + "niggards", + "niggard's", + "niggaz", + "nigger", + "niggerhead", + "niggerhole", + "niggers", + "nigger's", + "niggle", + "niggled", + "niggles", + "niggling", + "nigglings", + "niggor", + "niggur", + "niglet", + "nignog", + "nigr", + "nigra", + "nigre", + "nip", + "nipple", + "nipplering", + "nittit", + "nlgger", + "nlggor", + "nofuckingway", + "nook", + "nookey", + "nookie", + "noonan", + "nooner", + "nude", + "nudger", + "nuke", + "nutfucker", + "nymph", + "ontherag", + "oral", + "orga", + "orgasim ", + "orgasm", + "orgies", + "orgy", + "osama", + "paki", + "palesimian", + "palestinian", + "pansies", + "pansy", + "panti", + "panties", + "payo", + "pearlnecklace", + "peck", + "pecker", + "peckerwood", + "pee", + "peehole", + "pee-pee", + "peepshow", + "peepshpw", + "pendy", + "penetration", + "peni5", + "penile", + "penis", + "penises", + "penthouse", + "period", + "perv", + "phonesex", + "phuk", + "phuked", + "phuking", + "phukked", + "phukking", + "phungky", + "phuq", + "pi55", + "picaninny", + "piccaninny", + "pickaninny", + "piker", + "pikey", + "piky", + "pimp", + "pimped", + "pimper", + "pimpjuic", + "pimpjuice", + "pimpsimp", + "pindick", + "piss", + "pissed", + "pisser", + "pisses ", + "pisshead", + "pissin ", + "pissing", + "pissoff ", + "pistol", + "pixie", + "pixy", + "playboy", + "playgirl", + "pocha", + "pocho", + "pocketpool", + "pohm", + "polack", + "pom", + "pommie", + "pommy", + "poo", + "poon", + "poontang", + "poop", + "pooper", + "pooperscooper", + "pooping", + "poorwhitetrash", + "popimp", + "porchmonkey", + "porn", + "pornflick", + "pornking", + "porno", + "pornography", + "pornprincess", + "pot", + "poverty", + "premature", + "pric", + "prick", + "prickhead", + "primetime", + "propaganda", + "pros", + "prostitute", + "protestant", + "pu55i", + "pu55y", + "pube", + "pubic", + "pubiclice", + "pud", + "pudboy", + "pudd", + "puddboy", + "puke", + "puntang", + "purinapricness", + "puss", + "pussie", + "pussies", + "pussy", + "pussycat", + "pussyeater", + "pussyfucker", + "pussylicker", + "pussylips", + "pussylover", + "pussypounder", + "pusy", + "quashie", + "queef", + "queer", + "quickie", + "quim", + "ra8s", + "rabbi", + "racial", + "racist", + "radical", + "radicals", + "raghead", + "randy", + "rape", + "raped", + "raper", + "rapist", + "rearend", + "rearentry", + "rectum", + "redlight", + "redneck", + "reefer", + "reestie", + "refugee", + "reject", + "remains", + "rentafuck", + "republican", + "rere", + "retard", + "retarded", + "ribbed", + "rigger", + "rimjob", + "rimming", + "roach", + "robber", + "roundeye", + "rump", + "russki", + "russkie", + "sadis", + "sadom", + "samckdaddy", + "sandm", + "sandnigger", + "satan", + "scag", + "scallywag", + "scat", + "schlong", + "screw", + "screwyou", + "scrotum", + "scum", + "semen", + "seppo", + "servant", + "sex", + "sexed", + "sexfarm", + "sexhound", + "sexhouse", + "sexing", + "sexkitten", + "sexpot", + "sexslave", + "sextogo", + "sextoy", + "sextoys", + "sexual", + "sexually", + "sexwhore", + "sexy", + "sexymoma", + "sexy-slim", + "shag", + "shaggin", + "shagging", + "shat", + "shav", + "shawtypimp", + "sheeney", + "shhit", + "shinola", + "shit", + "shitcan", + "shitdick", + "shite", + "shiteater", + "shited", + "shitface", + "shitfaced", + "shitfit", + "shitforbrains", + "shitfuck", + "shitfucker", + "shitfull", + "shithapens", + "shithappens", + "shithead", + "shithouse", + "shiting", + "shitlist", + "shitola", + "shitoutofluck", + "shits", + "shitstain", + "shitted", + "shitter", + "shitting", + "shitty ", + "shoot", + "shooting", + "shortfuck", + "showtime", + "sick", + "sissy", + "sixsixsix", + "sixtynine", + "sixtyniner", + "skank", + "skankbitch", + "skankfuck", + "skankwhore", + "skanky", + "skankybitch", + "skankywhore", + "skinflute", + "skum", + "skumbag", + "slant", + "slanteye", + "slapper", + "slaughter", + "slav", + "slave", + "slavedriver", + "sleezebag", + "sleezeball", + "slideitin", + "slime", + "slimeball", + "slimebucket", + "slopehead", + "slopey", + "slopy", + "slut", + "sluts", + "slutt", + "slutting", + "slutty", + "slutwear", + "slutwhore", + "smack", + "smackthemonkey", + "smut", + "snatch", + "snatchpatch", + "snigger", + "sniggered", + "sniggering", + "sniggers", + "snigger's", + "sniper", + "snot", + "snowback", + "snownigger", + "sob", + "sodom", + "sodomise", + "sodomite", + "sodomize", + "sodomy", + "sonofabitch", + "sonofbitch", + "sooty", + "sos", + "soviet", + "spaghettibender", + "spaghettinigger", + "spank", + "spankthemonkey", + "sperm", + "spermacide", + "spermbag", + "spermhearder", + "spermherder", + "spic", + "spick", + "spig", + "spigotty", + "spik", + "spit", + "spitter", + "splittail", + "spooge", + "spreadeagle", + "spunk", + "spunky", + "squaw", + "stagg", + "stiffy", + "strapon", + "stringer", + "stripclub", + "dick", + "suicide", + "swallow", + "swastika", + "syphilis", + "taboo", + "tampon", + "tang", + "tantra", + "tarbaby", + "tard", + "teat", + "terror", + "terrorist", + "teste", + "testicle", + "testicles", + "thicklips", + "thirdeye", + "thirdleg", + "threesome", + "threeway", + "timbernigger", + "tinkle", + "tit", + "titbitnipply", + "titfuck", + "titfucker", + "titfuckin", + "titjob", + "titlicker", + "titlover", + "tits", + "tittie", + "titties", + "titty", + "tnt", + "toilet", + "tongethruster", + "tonguethrust", + "tonguetramp", + "tortur", + "torture", + "tosser", + "towelhead", + "trailertrash", + "tramp", + "trannie", + "tranny", + "transexual", + "transsexual", + "transvestite", + "triplex", + "trisexual", + "trojan", + "trots", + "tuckahoe", + "tunneloflove", + "turd", + "turnon", + "twat", + "twink", + "twinkie", + "twobitwhore", + "uck", + "uk", + "unfuckable", + "upskirt", + "uptheass", + "upthebutt", + "urinary", + "urinate", + "urine", + "usama", + "uterus", + "vagina", + "vaginal", + "vatican", + "vibr", + "vibrater", + "vibrator", + "vietcong", + "violence", + "virgin", + "vulva", + "wank", + "wanker", + "wanking", + "waysted", + "weapon", + "weenie", + "weewee", + "welcher", + "welfare", + "wetb", + "wetback", + "wetspot", + "whacker", + "whash", + "whigger", + "whitenigger", + "whitetrash", + "whitey", + "whiz", + "whop", + "whore", + "wigger", + "willie", + "williewanker", + "willy", + "wn", + "wog", + "wop", + "wuss", + "wuzzie", + "xtc", + "xxx", + "yankee", + "yellowman", + "zigabo", + "zipperhead" + ); +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java new file mode 100644 index 00000000..a8c06316 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java @@ -0,0 +1,22 @@ +package com.javadiscord.bot.utils; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class Executor { + private static final ScheduledExecutorService EXECUTOR_SERVICE = + Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); + + public static void execute(Runnable runnable) { + EXECUTOR_SERVICE.submit(runnable); + } + + public static void run(Runnable runnable, int period, TimeUnit timeUnit) { + EXECUTOR_SERVICE.scheduleAtFixedRate(runnable, 0, period, timeUnit); + } + + public static void run(Runnable runnable, int delay, int period, TimeUnit timeUnit) { + EXECUTOR_SERVICE.scheduleAtFixedRate(runnable, delay, period, timeUnit); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java new file mode 100644 index 00000000..c74c4e51 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java @@ -0,0 +1,49 @@ +package com.javadiscord.bot.utils; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class Tenor { + private static final Logger logger = LogManager.getLogger(Tenor.class); + private static final String API_KEY = System.getenv("TENOR_API_KEY"); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + + public static JsonNode search(String searchTerm, int limit) { + final String url = + String.format( + "https://api.tenor.com/v1/search?q=%1$s&key=%2$s&limit=%3$s", + searchTerm, API_KEY, limit + ); + + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .build(); + + try { + HttpResponse response = + HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + return OBJECT_MAPPER.readTree(response.body()); + } else { + System.err.println("HTTP Code: " + response.statusCode() + " from " + url); + } + } catch (IOException | InterruptedException e) { + logger.error("Error making a request to Tenor", e); + } + + return null; + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java new file mode 100644 index 00000000..59c38324 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java @@ -0,0 +1,114 @@ +package com.javadiscord.bot.utils.chatgpt; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import com.theokanning.openai.OpenAiHttpException; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.completion.chat.ChatMessageRole; +import com.theokanning.openai.service.OpenAiService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ChatGPT { + private static final Logger logger = LogManager.getLogger(ChatGPT.class); + private static final String API_KEY = System.getenv("CHATGPT_API_KEY"); + private static final Duration TIMEOUT = Duration.ofMinutes(3); + private static final String AI_MODEL = "gpt-3.5-turbo"; + private final OpenAiService openAiService; + + private static final int MAX_TOKENS = 2000; + + /** + * This parameter reduces the likelihood of the AI repeating itself. A higher + * frequency penalty makes the model less likely to repeat the same lines + * verbatim. It helps in generating more diverse and varied responses. + */ + private static final double FREQUENCY_PENALTY = 0.5; + + /** + * This parameter controls the randomness of the AI's responses. A higher + * temperature results in more varied, unpredictable, and creative responses. + * Conversely, a lower temperature makes the model's responses more + * deterministic and conservative. + */ + private static final double TEMPERATURE = 0.8; + + /** + * n: This parameter specifies the number of responses to generate for each + * prompt. If n is more than 1, the AI will generate multiple different + * responses to the same prompt, each one being a separate iteration based on + * the input. + */ + private static final int MAX_NUMBER_OF_RESPONSES = 1; + + public ChatGPT() { + openAiService = new OpenAiService(API_KEY, TIMEOUT); + + ChatMessage setupMessage = + new ChatMessage( + ChatMessageRole.SYSTEM.value(), + """ + Please answer questions in 2000 characters or less. Remember to count spaces in the + character limit. The context is Java Programming:\s""" + ); + + ChatCompletionRequest systemSetupRequest = + ChatCompletionRequest.builder() + .model(AI_MODEL) + .messages(List.of(setupMessage)) + .frequencyPenalty(FREQUENCY_PENALTY) + .temperature(TEMPERATURE) + .maxTokens(50) + .n(MAX_NUMBER_OF_RESPONSES) + .build(); + + openAiService.createChatCompletion(systemSetupRequest); + } + + public Optional ask(String question) { + try { + ChatMessage chatMessage = + new ChatMessage(ChatMessageRole.USER.value(), Objects.requireNonNull(question)); + + ChatCompletionRequest chatCompletionRequest = + ChatCompletionRequest.builder() + .model(AI_MODEL) + .messages(List.of(chatMessage)) + .frequencyPenalty(FREQUENCY_PENALTY) + .temperature(TEMPERATURE) + .maxTokens(MAX_TOKENS) + .n(MAX_NUMBER_OF_RESPONSES) + .build(); + + String response = + openAiService + .createChatCompletion(chatCompletionRequest) + .getChoices() + .getFirst() + .getMessage() + .getContent(); + + return Optional.ofNullable(ChatGPTResponseParser.parse(response)); + } catch (OpenAiHttpException openAiHttpException) { + logger.warn( + String.format( + "There was an error using the OpenAI API: %s Code: %s Type: %s Status" + + " Code: %s", + openAiHttpException.getMessage(), + openAiHttpException.code, + openAiHttpException.type, + openAiHttpException.statusCode + ) + ); + } catch (RuntimeException runtimeException) { + logger.warn( + "There was an error using the OpenAI API: " + runtimeException.getMessage() + ); + } + return Optional.empty(); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java new file mode 100644 index 00000000..1b88f643 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java @@ -0,0 +1,69 @@ +package com.javadiscord.bot.utils.chatgpt; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ChatGPTResponseParser { + private static final Logger logger = LogManager.getLogger(ChatGPTResponseParser.class); + private static final int RESPONSE_LENGTH_LIMIT = 2000; + + private ChatGPTResponseParser() {} + + public static String[] parse(String response) { + String[] partedResponse = new String[] {response}; + if (response.length() > RESPONSE_LENGTH_LIMIT) { + logger.debug("Response to parse:\n" + response); + partedResponse = partitionAiResponse(response); + } + return partedResponse; + } + + private static String[] partitionAiResponse(String response) { + List responseChunks = new ArrayList<>(); + String[] splitResponseOnMarks = response.split("```"); + for (int i = 0; i < splitResponseOnMarks.length; i++) { + String split = splitResponseOnMarks[i]; + List chunks = new ArrayList<>(); + chunks.add(split); + + // Check each chunk for correct length. If over the length, split in two and + // check + // again. + while (!chunks.stream().allMatch(s -> s.length() < RESPONSE_LENGTH_LIMIT)) { + for (int j = 0; j < chunks.size(); j++) { + String chunk = chunks.get(j); + if (chunk.length() > RESPONSE_LENGTH_LIMIT) { + int midpointNewline = chunk.lastIndexOf("\n", chunk.length() / 2); + chunks.set(j, chunk.substring(0, midpointNewline)); + chunks.add(j + 1, chunk.substring(midpointNewline)); + } + } + } + + // Given the splitting on ```, the odd numbered entries need to have code marks + // restored. + if (i % 2 != 0) { + // We assume that everything after the ``` on the same line is the language + // declaration. Could be empty. + String lang = split.substring(0, split.indexOf(System.lineSeparator())); + chunks = + chunks.stream() + .map(s -> ("```" + lang).concat(s).concat("```")) + // Handle case of doubling language declaration + .map(s -> s.replaceFirst("```" + lang + lang, "```" + lang)) + .toList(); + } + + responseChunks.addAll(filterEmptyStrings(chunks)); + } // end of for loop. + + return responseChunks.toArray(new String[0]); + } + + private static List filterEmptyStrings(List chunks) { + return chunks.stream().filter(string -> !string.isEmpty()).toList(); + } +} diff --git a/settings.gradle b/settings.gradle index 46019308..03f66d72 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,6 @@ include 'annotations' include 'cache' include 'example:echo-bot' findProject(':example:echo-bot')?.name = 'echo-bot' +include 'example:lj-discord-bot' +findProject(':example:lj-discord-bot')?.name = 'lj-discord-bot' From c1acab7c548b3baf2e207e8aa5f6e056d4274ced Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 13:43:37 +0100 Subject: [PATCH 18/57] Added @Inject and @Component --- .../jdi/core/annotations/Component.java | 10 ++ .../jdi/core/annotations/Inject.java | 10 ++ .../processor/loader/ComponentLoader.java | 92 +++++++++++++++++++ .../{ => loader}/ListenerLoader.java | 8 +- .../{ => loader}/SlashCommandLoader.java | 9 +- .../validator/ComponentValidator.java | 35 +++++++ .../EventListenerValidator.java | 2 +- .../SlashCommandValidator.java | 2 +- .../processor/EventListenerValidatorTest.java | 1 + .../com/javadiscord/jdi/core/Discord.java | 22 ++++- .../interaction/InteractionEventHandler.java | 15 ++- .../main/java/com/javadiscord/bot/Main.java | 7 ++ .../bot/commands/slash/ChatGPTCommand.java | 5 +- 13 files changed, 205 insertions(+), 13 deletions(-) create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/annotations/Component.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java rename annotations/src/main/java/com/javadiscord/jdi/core/processor/{ => loader}/ListenerLoader.java (86%) rename annotations/src/main/java/com/javadiscord/jdi/core/processor/{ => loader}/SlashCommandLoader.java (89%) create mode 100644 annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java rename annotations/src/main/java/com/javadiscord/jdi/core/processor/{ => validator}/EventListenerValidator.java (99%) rename annotations/src/main/java/com/javadiscord/jdi/core/processor/{ => validator}/SlashCommandValidator.java (97%) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Component.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Component.java new file mode 100644 index 00000000..6004f9fb --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Component.java @@ -0,0 +1,10 @@ +package com.javadiscord.jdi.core.annotations; + +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.METHOD}) +public @interface Component {} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java new file mode 100644 index 00000000..67aebb96 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java @@ -0,0 +1,10 @@ +package com.javadiscord.jdi.core.annotations; + +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.FIELD}) +public @interface Inject {} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java new file mode 100644 index 00000000..567722ac --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java @@ -0,0 +1,92 @@ +package com.javadiscord.jdi.core.processor.loader; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.javadiscord.jdi.core.annotations.Component; +import com.javadiscord.jdi.core.annotations.Inject; +import com.javadiscord.jdi.core.processor.ClassFileUtil; +import com.javadiscord.jdi.core.processor.validator.ComponentValidator; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ComponentLoader { + private static final Logger LOGGER = LogManager.getLogger(ComponentLoader.class); + private static final Map, Object> COMPONENTS = new HashMap<>(); + private final ComponentValidator componentValidator = new ComponentValidator(); + + public ComponentLoader() { + try { + loadComponents(); + } catch (Exception e) { + LOGGER.error("An error occurred while loading components classes", e); + } + } + + private void loadComponents() { + List classes = ClassFileUtil.getClassesInClassPath(); + for (File classFile : classes) { + try { + Class clazz = Class.forName(ClassFileUtil.getClassName(classFile)); + if (componentValidator.validate(clazz)) { + for (Method method : clazz.getMethods()) { + if (method.isAnnotationPresent(Component.class)) { + if (!COMPONENTS.containsKey(method.getReturnType())) { + COMPONENTS.put(method.getReturnType(), method.invoke(null)); + LOGGER + .info("Loaded component {}", method.getReturnType().getName()); + } else { + LOGGER.error( + "Component {} already loaded", method.getReturnType().getName() + ); + } + } + } + } else { + LOGGER.error("{} failed validation", clazz.getName()); + } + } catch (Exception | Error ignore) { + /* Ignore */ + } + } + } + + public static void injectComponents(Object component) { + try { + Class clazz = component.getClass(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class)) { + if (COMPONENTS.containsKey(field.getType())) { + Object dependency = COMPONENTS.get(field.getType()); + if (dependency != null) { + field.setAccessible(true); + try { + field.set(component, dependency); + LOGGER.info( + "Injected component {} into {}", + dependency.getClass().getName(), field.getType() + ); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Failed to inject dependency into field: " + field.getName(), + e + ); + } + } + } else { + LOGGER.error( + "No object {} was found in field {}", field.getType(), field.getName() + ); + } + } + } + } catch (Exception | Error ignore) { + /* Ignore */ + } + } +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ListenerLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ListenerLoader.java similarity index 86% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/ListenerLoader.java rename to annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ListenerLoader.java index b7d9a79d..de4b6a5b 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ListenerLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ListenerLoader.java @@ -1,10 +1,12 @@ -package com.javadiscord.jdi.core.processor; +package com.javadiscord.jdi.core.processor.loader; import java.io.File; import java.lang.reflect.Constructor; import java.util.List; import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.processor.ClassFileUtil; +import com.javadiscord.jdi.core.processor.validator.EventListenerValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -43,7 +45,9 @@ public void loadListeners() { private void registerListener(Class clazz) { try { - eventListeners.add(getZeroArgConstructor(clazz).newInstance()); + Object instance = getZeroArgConstructor(clazz).newInstance(); + ComponentLoader.injectComponents(instance); + eventListeners.add(instance); LOGGER.info("Registered listener {}", clazz.getName()); } catch (Exception e) { LOGGER.error("Failed to create {} instance", clazz.getName(), e); diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java similarity index 89% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java rename to annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java index ff3c45cb..6b828981 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor; +package com.javadiscord.jdi.core.processor.loader; import java.io.File; import java.lang.reflect.Constructor; @@ -7,6 +7,9 @@ import java.util.Map; import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.processor.ClassFileUtil; +import com.javadiscord.jdi.core.processor.SlashCommandClassMethod; +import com.javadiscord.jdi.core.processor.validator.SlashCommandValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -78,4 +81,8 @@ private boolean hasZeroArgsConstructor(Class clazz) { return false; } + public void injectComponents(Object object) { + ComponentLoader.injectComponents(object); + } + } diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java new file mode 100644 index 00000000..b6d47c94 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java @@ -0,0 +1,35 @@ +package com.javadiscord.jdi.core.processor.validator; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import com.javadiscord.jdi.core.annotations.Component; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ComponentValidator { + private static final Logger LOGGER = LogManager.getLogger(ComponentValidator.class); + + public boolean validate(Class clazz) { + return validateMethods(clazz); + } + + private boolean validateMethods(Class clazz) { + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(Component.class)) { + if (method.getParameterCount() != 0) { + LOGGER.error("Methods annotated with @Component requires 0 parameters"); + return false; + } + if (!Modifier.isStatic(method.getModifiers())) { + LOGGER.error("Methods annotated with @Component must be static"); + return false; + } + } + } + return true; + } + +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/EventListenerValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java similarity index 99% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/EventListenerValidator.java rename to annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java index 06e66dc4..d4a86fc7 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/EventListenerValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor; +package com.javadiscord.jdi.core.processor.validator; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java similarity index 97% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java rename to annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java index ba113edf..f74f5e4a 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor; +package com.javadiscord.jdi.core.processor.validator; import java.lang.annotation.Annotation; import java.lang.reflect.Method; diff --git a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java index 49b03d65..bdf9ae51 100644 --- a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java +++ b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java @@ -10,6 +10,7 @@ import com.javadiscord.jdi.core.models.channel.Channel; import com.javadiscord.jdi.core.models.message.Message; +import com.javadiscord.jdi.core.processor.validator.EventListenerValidator; import org.junit.jupiter.api.Test; class EventListenerValidatorTest { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 28719e91..40a6a548 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -118,6 +118,7 @@ public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) { this.cache = cache; if (annotationLibPresent()) { LOGGER.info("Annotation lib is present"); + loadComponents(); loadAnnotations(); loadSlashCommands(); registerLoadedAnnotationsWithDiscord(); @@ -211,17 +212,31 @@ private void registerLoadedAnnotationsWithDiscord() { private boolean annotationLibPresent() { try { - Class.forName("com.javadiscord.jdi.core.processor.ListenerLoader"); + Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader"); return true; } catch (Exception e) { return false; } } + private void loadComponents() { + LOGGER.info("Loading Components"); + try { + Class clazz = + Class.forName("com.javadiscord.jdi.core.processor.loader.ComponentLoader"); + for (Constructor constructor : clazz.getConstructors()) { + constructor.newInstance(); + } + } catch (Exception | Error e) { + /* Ignore */ + } + } + private void loadAnnotations() { LOGGER.info("Loading EventListeners"); try { - Class clazz = Class.forName("com.javadiscord.jdi.core.processor.ListenerLoader"); + Class clazz = + Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader"); for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 1) { Parameter parameters = constructor.getParameters()[0]; @@ -239,7 +254,8 @@ private void loadAnnotations() { private void loadSlashCommands() { LOGGER.info("Loading SlashCommands"); try { - Class clazz = Class.forName("com.javadiscord.jdi.core.processor.SlashCommandLoader"); + Class clazz = + Class.forName("com.javadiscord.jdi.core.processor.loader.SlashCommandLoader"); for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 1) { Parameter parameters = constructor.getParameters()[0]; diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 793bb397..8bc669cf 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -2,10 +2,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.EventListener; @@ -81,6 +78,7 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); } else { Object handlerInstance = handler.getDeclaredConstructor().newInstance(); + injectComponents(handlerInstance); method.invoke(handlerInstance, paramOrder.toArray()); } @@ -89,4 +87,13 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { } } + private void injectComponents(Object object) throws Exception { + Class slashCommandLoaderClass = slashCommandLoader.getClass(); + + Method injectComponentsMethod = + slashCommandLoaderClass.getMethod("injectComponents", Object.class); + + injectComponentsMethod.invoke(slashCommandLoader, object); + } + } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java index 48fb75c8..d560cfa2 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java @@ -1,10 +1,17 @@ package com.javadiscord.bot; +import com.javadiscord.bot.utils.chatgpt.ChatGPT; import com.javadiscord.jdi.core.Discord; +import com.javadiscord.jdi.core.annotations.Component; public class Main { public static void main(String[] args) { Discord discord = new Discord(System.getenv("BOT_TOKEN")); discord.start(); } + + @Component + public static ChatGPT chatGpt() { + return new ChatGPT(); + } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java index a99b7717..6132f566 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java @@ -5,13 +5,16 @@ import com.javadiscord.bot.utils.chatgpt.ChatGPT; import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.Inject; import com.javadiscord.jdi.core.annotations.SlashCommand; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; import com.javadiscord.jdi.core.models.message.embed.Embed; import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; public class ChatGPTCommand { - private final ChatGPT chatGPT = new ChatGPT(); + + @Inject + private ChatGPT chatGPT; @SlashCommand( name = "chatgpt", description = "Ask ChatGPT a question", options = { From 011f09a688745a9d1c93638e039b38b1f751ab1a Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 18:07:16 +0100 Subject: [PATCH 19/57] Added @Inject and @Component for JShell --- .../javadiscord/jdi/core/api/ChannelRequest.java | 13 +++++++++++++ .../src/main/java/com/javadiscord/bot/Main.java | 6 ++++++ .../bot/commands/slash/jshell/JShellCommand.java | 5 ++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java index 408dd6ad..14eff15d 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java @@ -9,6 +9,7 @@ import com.javadiscord.jdi.core.models.invite.Invite; import com.javadiscord.jdi.core.models.message.Message; import com.javadiscord.jdi.core.models.message.MessageReaction; +import com.javadiscord.jdi.core.models.message.embed.Embed; import com.javadiscord.jdi.core.models.user.User; import com.javadiscord.jdi.internal.api.channel.*; @@ -49,6 +50,18 @@ public AsyncResponse createMessage(CreateMessageBuilder builder) { return responseParser.callAndParse(Message.class, builder.build()); } + public AsyncResponse sendMessage(long channelId, String message) { + return responseParser.callAndParse( + Message.class, new CreateMessageBuilder(channelId).content(message).build() + ); + } + + public AsyncResponse sendEmbed(long channelId, Embed... embeds) { + return responseParser.callAndParse( + Message.class, new CreateMessageBuilder(channelId).embeds(embeds).build() + ); + } + public AsyncResponse createReaction( long channelId, long messageId, diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java index d560cfa2..217bc9b1 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java @@ -1,5 +1,6 @@ package com.javadiscord.bot; +import com.javadiscord.bot.commands.slash.jshell.JShellService; import com.javadiscord.bot.utils.chatgpt.ChatGPT; import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.annotations.Component; @@ -14,4 +15,9 @@ public static void main(String[] args) { public static ChatGPT chatGpt() { return new ChatGPT(); } + + @Component + public static JShellService jShellService() { + return new JShellService(); + } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java index 2df085c8..51b98360 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java @@ -4,6 +4,7 @@ import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.Inject; import com.javadiscord.jdi.core.annotations.SlashCommand; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; import com.javadiscord.jdi.core.models.message.embed.Embed; @@ -11,7 +12,9 @@ import com.javadiscord.jdi.core.models.user.User; public class JShellCommand { - private final JShellService jShellService = new JShellService(); + + @Inject + private JShellService jShellService; @SlashCommand( name = "jshell", description = "Run Java code using JShell", options = { From ba04843b4c5cedbdfc6ff7e022ce6af0f3d69848 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 19:07:48 +0100 Subject: [PATCH 20/57] Finish doing lj-bot example --- .../validator/EventListenerValidator.java | 2 +- .../jdi/core/api/ChannelRequest.java | 3 +- .../api/channel/CreateReactionRequest.java | 3 +- .../main/java/com/javadiscord/bot/Main.java | 2 +- .../slash/{jshell => }/JShellCommand.java | 5 +- .../bot/listeners/QuestionListener.java | 93 +++++++++++++++++++ .../bot/listeners/SpamListener.java | 2 +- .../bot/listeners/SuggestionListener.java | 24 +++-- .../jshell/JShellResponse.java | 2 +- .../slash => utils}/jshell/JShellService.java | 2 +- .../slash => utils}/jshell/JShellSnippet.java | 2 +- .../events/codec/decoders/WebhookDecoder.java | 7 +- .../events/codec/models/channel/Thread.java | 4 +- .../jdi/core/models/message/Message.java | 11 ++- 14 files changed, 139 insertions(+), 23 deletions(-) rename example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/{jshell => }/JShellCommand.java (95%) create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/QuestionListener.java rename example/lj-discord-bot/src/main/java/com/javadiscord/bot/{commands/slash => utils}/jshell/JShellResponse.java (89%) rename example/lj-discord-bot/src/main/java/com/javadiscord/bot/{commands/slash => utils}/jshell/JShellService.java (97%) rename example/lj-discord-bot/src/main/java/com/javadiscord/bot/{commands/slash => utils}/jshell/JShellSnippet.java (86%) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java index d4a86fc7..b7fa39ce 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java @@ -308,7 +308,7 @@ public class EventListenerValidator { EXPECTED_PARAM_TYPES_MAP.put( ThreadCreate.class, new String[] { - "com.javadiscord.jdi.core.gateway.handlers.events.codec.models.channel.Thread", + "com.javadiscord.jdi.internal.gateway.handlers.events.codec.models.channel.Thread", "com.javadiscord.jdi.core.Discord", "com.javadiscord.jdi.core.Guild" } diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java index 14eff15d..b7ac8de7 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java @@ -6,6 +6,7 @@ import com.javadiscord.jdi.core.api.builders.*; import com.javadiscord.jdi.core.models.channel.Channel; import com.javadiscord.jdi.core.models.channel.ThreadMember; +import com.javadiscord.jdi.core.models.emoji.Emoji; import com.javadiscord.jdi.core.models.invite.Invite; import com.javadiscord.jdi.core.models.message.Message; import com.javadiscord.jdi.core.models.message.MessageReaction; @@ -65,7 +66,7 @@ Message.class, new CreateMessageBuilder(channelId).embeds(embeds).build() public AsyncResponse createReaction( long channelId, long messageId, - String emoji + Emoji emoji ) { return responseParser.callAndParse( MessageReaction.class, new CreateReactionRequest(channelId, messageId, emoji) diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java index c0223985..e200c81a 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java @@ -1,12 +1,13 @@ package com.javadiscord.jdi.internal.api.channel; +import com.javadiscord.jdi.core.models.emoji.Emoji; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; public record CreateReactionRequest( long channelId, long messageId, - String emoji + Emoji emoji ) implements DiscordRequest { @Override public DiscordRequestBuilder create() { diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java index 217bc9b1..7642f6c6 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java @@ -1,7 +1,7 @@ package com.javadiscord.bot; -import com.javadiscord.bot.commands.slash.jshell.JShellService; import com.javadiscord.bot.utils.chatgpt.ChatGPT; +import com.javadiscord.bot.utils.jshell.JShellService; import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.annotations.Component; diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java similarity index 95% rename from example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java rename to example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java index 51b98360..aff56ce4 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellCommand.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java @@ -1,7 +1,10 @@ -package com.javadiscord.bot.commands.slash.jshell; +package com.javadiscord.bot.commands.slash; import java.awt.*; +import com.javadiscord.bot.utils.jshell.JShellResponse; +import com.javadiscord.bot.utils.jshell.JShellService; +import com.javadiscord.bot.utils.jshell.JShellSnippet; import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.annotations.CommandOption; import com.javadiscord.jdi.core.annotations.Inject; diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/QuestionListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/QuestionListener.java new file mode 100644 index 00000000..fe8b4469 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/QuestionListener.java @@ -0,0 +1,93 @@ +package com.javadiscord.bot.listeners; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +import com.javadiscord.bot.utils.chatgpt.ChatGPT; +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.annotations.EventListener; +import com.javadiscord.jdi.core.annotations.Inject; +import com.javadiscord.jdi.core.annotations.MessageCreate; +import com.javadiscord.jdi.core.annotations.ThreadCreate; +import com.javadiscord.jdi.core.models.message.Message; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; +import com.javadiscord.jdi.internal.gateway.handlers.events.codec.models.channel.Thread; + +@EventListener +public class QuestionListener { + private final List channelsThatNeedChatGpt = new ArrayList<>(); + + @Inject + private ChatGPT chatGPT; + + @ThreadCreate + public void onQuestionCreate(Thread thread, Guild guild) { + if (thread.newlyCreated() && thread.parentId() == 1245064991275618511L) { + + guild.channel().sendEmbed( + thread.id(), new Embed.Builder() + .description( + """ + # Important + Please make sure your question has enough details for a helper to understand the problem. + + * If you are asking for help with code, please use a code block. + * If you are asking for help with an error, please include the full error message. + * Screenshots may also be useful. Please do not post screenshots of code, however. + """ + ) + .image( + "https://media.tenor.com/LoNa2zOMxoAAAAAC/its-very-important-it-matters.gif" + ) + .build() + ); + + guild.channel().sendEmbed( + thread.id(), new Embed.Builder() + .description( + """ + Once your question has been answered, please close this thread by doing `/close`. + """ + ).build() + ); + + channelsThatNeedChatGpt.add(thread.id()); + } + } + + @MessageCreate + public void sendChatGptAnswer(Message message, Guild guild) { + if (channelsThatNeedChatGpt.contains(message.id())) { + channelsThatNeedChatGpt.remove(message.id()); + + StringBuilder answer = new StringBuilder(); + answer.append("## Here is an attempted answer by ChatGPT\n\n"); + + chatGPT.ask(message.content()) + .ifPresentOrElse( + strings -> { + for (String string : strings) { + answer.append(string).append("\n"); + } + }, + () -> guild.channel().sendMessage( + message.channelId(), + "ChatGPT is currently unavailable." + ) + ); + + Embed embed = + new Embed.Builder() + .author( + new EmbedAuthor("", null, "https://chat.openai.com/favicon-32x32.png", null) + ) + .color(Color.CYAN) + .description(answer.toString()) + .build(); + + guild.channel().sendEmbed(message.channelId(), embed); + } + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java index 679e5387..8958804d 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java @@ -16,7 +16,7 @@ public class SpamListener { @MessageCreate public void onMessage(Message message, Guild guild) { - if (!message.author().bot() && CurseWords.containsCurseWord(message.content())) { + if (message.fromUser() && CurseWords.containsCurseWord(message.content())) { guild.channel().deleteMessage(message.channelId(), message.id()); guild.user() .createDM(message.id()) diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java index dc52ef03..8d7d9ceb 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java @@ -3,7 +3,7 @@ import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.annotations.EventListener; import com.javadiscord.jdi.core.annotations.MessageCreate; -import com.javadiscord.jdi.core.api.builders.StartThreadWithoutMessageBuilder; +import com.javadiscord.jdi.core.api.builders.StartThreadFromMessageBuilder; import com.javadiscord.jdi.core.models.message.Message; @EventListener @@ -11,7 +11,7 @@ public class SuggestionListener { @MessageCreate public void onMessage(Message message, Guild guild) { - if (message.author().bot()) { + if (message.fromBot()) { return; } @@ -19,15 +19,23 @@ public void onMessage(Message message, Guild guild) { return; } - guild.channel().createReaction(message.channelId(), message.id(), "thumbup"); - guild.channel().createReaction(message.channelId(), message.id(), "thumbsdown"); + // guild.channel().createReaction(message.channelId(), message.id(), "thumbup"); + // guild.channel().createReaction(message.channelId(), message.id(), + // "thumbsdown"); - guild.channel().startThreadWithoutMessage( - new StartThreadWithoutMessageBuilder( + String title = + message.content().length() > 60 + ? message.content().substring(0, 60) + : message.content(); + + guild.channel().startThreadFromMessage( + new StartThreadFromMessageBuilder( message.channelId(), - "Suggestion!" + message.id(), + title ) - ); + ).onError(System.err::println) + .onSuccess(System.out::println); } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellResponse.java similarity index 89% rename from example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java rename to example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellResponse.java index 32944371..64b36eeb 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellResponse.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellResponse.java @@ -1,4 +1,4 @@ -package com.javadiscord.bot.commands.slash.jshell; +package com.javadiscord.bot.utils.jshell; import java.util.List; diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java similarity index 97% rename from example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java rename to example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java index eb9b7a54..4e9a670d 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellService.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java @@ -1,4 +1,4 @@ -package com.javadiscord.bot.commands.slash.jshell; +package com.javadiscord.bot.utils.jshell; import java.io.IOException; import java.net.URI; diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellSnippet.java similarity index 86% rename from example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java rename to example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellSnippet.java index 1508d38f..f6551ce1 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/jshell/JShellSnippet.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellSnippet.java @@ -1,4 +1,4 @@ -package com.javadiscord.bot.commands.slash.jshell; +package com.javadiscord.bot.utils.jshell; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java index 5c487a44..245f482f 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/WebhookDecoder.java @@ -1,5 +1,6 @@ package com.javadiscord.jdi.internal.gateway.handlers.events.codec.decoders; +import com.javadiscord.jdi.core.models.webhook.Webhook; import com.javadiscord.jdi.internal.gateway.GatewayEvent; import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventDecoder; @@ -8,14 +9,14 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -public class WebhookDecoder implements EventDecoder { +public class WebhookDecoder implements EventDecoder { private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override - public WebhookDecoder decode(GatewayEvent gatewayEvent) { + public Webhook decode(GatewayEvent gatewayEvent) { try { - return OBJECT_MAPPER.readValue(gatewayEvent.data().toString(), WebhookDecoder.class); + return OBJECT_MAPPER.readValue(gatewayEvent.data().toString(), Webhook.class); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/models/channel/Thread.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/models/channel/Thread.java index e69c8fe2..5d8e0fa1 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/models/channel/Thread.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/models/channel/Thread.java @@ -11,8 +11,8 @@ public record Thread( @JsonProperty("total_message_sent") int totalMessageSent, @JsonProperty("thread_metadata") ThreadMetadata threadMetadata, @JsonProperty("rate_limit_per_user") int rateLimitPerUser, - @JsonProperty("parent_id") String parentId, - @JsonProperty("owner_id") String ownerId, + @JsonProperty("parent_id") long parentId, + @JsonProperty("owner_id") long ownerId, @JsonProperty("newly_created") boolean newlyCreated, @JsonProperty("name") String name, @JsonProperty("message_count") int messageCount, diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/message/Message.java b/models/src/main/java/com/javadiscord/jdi/core/models/message/Message.java index 7debd554..509ff608 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/message/Message.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/message/Message.java @@ -49,4 +49,13 @@ public record Message( @JsonProperty("position") int position, @JsonProperty("role_subscription_data") RoleSubscriptionData roleSubscriptionData, @JsonProperty("resolved") ResolvedData resolved -) {} +) { + + public boolean fromBot() { + return author.bot(); + } + + public boolean fromUser() { + return !author.bot(); + } +} From f98b3b7f2d2afb7f043c95a3efe27a9fa74016c6 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 19:27:40 +0100 Subject: [PATCH 21/57] Added docker command to lj-example --- .../bot/commands/slash/LinuxCommand.java | 169 ++++++++++++++++++ .../utils/docker/ContainerCleanupTask.java | 30 ++++ .../bot/utils/docker/DockerCommandRunner.java | 37 ++++ .../utils/docker/DockerContainerCreator.java | 65 +++++++ .../bot/utils/docker/DockerSessions.java | 56 ++++++ .../javadiscord/bot/utils/docker/Session.java | 39 ++++ 6 files changed, 396 insertions(+) create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/ContainerCleanupTask.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerCommandRunner.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerContainerCreator.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerSessions.java create mode 100644 example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/Session.java diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java new file mode 100644 index 00000000..771a6929 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java @@ -0,0 +1,169 @@ +package com.javadiscord.bot.commands.slash; + +import java.awt.*; +import java.io.OutputStream; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import com.javadiscord.bot.utils.docker.*; +import com.javadiscord.jdi.core.CommandOptionType; +import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.SlashCommand; +import com.javadiscord.jdi.core.interaction.SlashCommandEvent; +import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; +import com.javadiscord.jdi.core.models.message.embed.Embed; +import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; +import com.javadiscord.jdi.core.models.user.User; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.CreateContainerResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class LinuxCommand { + private static final Logger LOGGER = LogManager.getLogger(LinuxCommand.class); + private static final ScheduledExecutorService EXECUTOR_SERVICE = + Executors.newSingleThreadScheduledExecutor(); + + private final DockerClient dockerClient; + public final DockerSessions dockerSessions; + private final DockerCommandRunner commandRunner; + + public LinuxCommand( + DockerClient dockerClient, + DockerSessions dockerSessions, + DockerCommandRunner commandRunner + ) { + this.dockerClient = dockerClient; + this.dockerSessions = dockerSessions; + this.commandRunner = commandRunner; + + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> dockerSessions + .getSessions() + .forEach(dockerSessions::stopContainer) + ) + ); + + EXECUTOR_SERVICE.scheduleAtFixedRate( + new ContainerCleanupTask(dockerSessions), 0, 5, TimeUnit.MINUTES + ); + } + + @SlashCommand( + name = "linux", description = "Run commands in your very own Linux session", options = { + @CommandOption( + name = "code", description = "The command you would like to run", type = CommandOptionType.STRING + ) + } + ) + public void handle(SlashCommandEvent event) { + event.deferReply(); + + Optional codeOption = event.option("code"); + User user = event.user(); + + codeOption.ifPresent(option -> { + LOGGER.info("Running command {}", option.valueAsString()); + + Thread.ofVirtual().start(() -> handleLinuxCommand(event, option.valueAsString(), user)); + }); + } + + private void handleLinuxCommand(SlashCommandEvent event, String command, User member) { + String memberId = String.valueOf(member.id()); + Session session = getSessionForUser(memberId); + try (OutputStream output = commandRunner.sendCommand(session, command)) { + String reply = + """ + Ran command: + ``` + $ %s + ``` + + ```java + %s + ``` + + Session expires in %s + """ + .formatted(command, output, getSessionExpiry(session)); + + Embed embed = + new Embed.Builder() + .author(new EmbedAuthor(member.asMention(), null, null, null)) + .description(shortenOutput(reply)) + .color(Color.RED) + .build(); + + event.reply(embed); + } catch (Exception e) { + LOGGER.error(e); + event.reply("An error occurred: " + e.getMessage()); + } + } + + private String getSessionExpiry(Session session) { + Instant expiry = session.getStartTime().plusSeconds(TimeUnit.MINUTES.toSeconds(5)); + long epochSeconds = expiry.getEpochSecond(); + return ""; + } + + private String shortenOutput(String input) { + String concatMessage = "\n**Rest of the output as been removed as it was too long**\n"; + if (input.length() > 4096) { + input = input.substring(0, 4096 - concatMessage.length()) + concatMessage; + } + StringBuilder sb = new StringBuilder(); + String[] parts = input.split("\n"); + if (parts.length > 50) { + for (int i = 50; i > 0; i--) { + sb.append(parts[i]).append("\n"); + } + sb.append(concatMessage); + } else { + sb.append(input); + } + return sb.toString(); + } + + private Session getSessionForUser(String name) { + if (!dockerSessions.hasSession(name)) { + + LOGGER.info("Creating new session for {}", name); + + DockerContainerCreator containerCreator = new DockerContainerCreator(dockerClient); + + CreateContainerResponse createContainerResponse = + containerCreator.createContainerStarted( + "session-" + ThreadLocalRandom.current().nextInt(), + "ubuntu:latest", + mb(256), + mb(256), + 512, + 100000, + cpuQuota(100000, 0.5) + ); + + return dockerSessions.createSession(name, createContainerResponse.getId()); + } + + LOGGER.info("Found existing session for {}", name); + + return dockerSessions.getSessionForUser(name); + } + + public static long mb(long megabytes) { + return megabytes * 1024 * 1024; + } + + public static long cpuQuota(int cpuPeriod, double percentage) { + return (long) (cpuPeriod * (percentage / 10.0)); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/ContainerCleanupTask.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/ContainerCleanupTask.java new file mode 100644 index 00000000..748c4235 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/ContainerCleanupTask.java @@ -0,0 +1,30 @@ +package com.javadiscord.bot.utils.docker; + +import java.time.Instant; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; + +public class ContainerCleanupTask implements Runnable { + private static final long CONTAINER_DURATION = TimeUnit.MINUTES.toSeconds(5); + + private final DockerSessions dockerSessions; + + public ContainerCleanupTask(DockerSessions dockerSessions) { + this.dockerSessions = dockerSessions; + } + + @Override + public void run() { + Instant now = Instant.now(); + Iterator it = dockerSessions.getSessions().iterator(); + while (it.hasNext()) { + Session session = it.next(); + Instant sessionStart = session.getStartTime(); + boolean sessionExpired = sessionStart.plusSeconds(CONTAINER_DURATION).isAfter(now); + if (sessionExpired) { + dockerSessions.stopContainer(session); + it.remove(); + } + } + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerCommandRunner.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerCommandRunner.java new file mode 100644 index 00000000..7bac950a --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerCommandRunner.java @@ -0,0 +1,37 @@ +package com.javadiscord.bot.utils.docker; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.ExecCreateCmdResponse; +import com.github.dockerjava.core.command.ExecStartResultCallback; + +public class DockerCommandRunner { + private final DockerClient dockerClient; + + public DockerCommandRunner(DockerClient dockerClient) { + this.dockerClient = dockerClient; + } + + public OutputStream sendCommand(Session session, String command) throws InterruptedException { + session.updateHistory(command); + + OutputStream output = new ByteArrayOutputStream(); + + ExecCreateCmdResponse execCreateCmdResponse = + dockerClient + .execCreateCmd(session.getContainerId()) + .withAttachStdout(true) + .withAttachStderr(true) + .withCmd("bash", "-c", command.trim()) + .exec(); + + dockerClient + .execStartCmd(execCreateCmdResponse.getId()) + .exec(new ExecStartResultCallback(output, output)) + .awaitCompletion(); + + return output; + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerContainerCreator.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerContainerCreator.java new file mode 100644 index 00000000..3bfe9b29 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerContainerCreator.java @@ -0,0 +1,65 @@ +package com.javadiscord.bot.utils.docker; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.dockerjava.api.model.HostConfig; + +public class DockerContainerCreator { + private final DockerClient dockerClient; + + public DockerContainerCreator(DockerClient dockerClient) { + this.dockerClient = dockerClient; + } + + public CreateContainerResponse createContainerStarted( + String name, + String image, + long memoryLimit, + long memorySwapLimit, + int cpuShares, + long cpuPeriod, + long cpuQuota + ) { + + CreateContainerResponse createContainerResponse = + createContainer( + name, image, memoryLimit, memorySwapLimit, cpuShares, cpuPeriod, cpuQuota + ); + + startContainer(createContainerResponse.getId()); + + return createContainerResponse; + } + + public CreateContainerResponse createContainer( + String name, + String image, + long memoryLimit, + long memorySwapLimit, + int cpuShares, + long cpuPeriod, + long cpuQuota + ) { + + HostConfig hostConfig = + HostConfig.newHostConfig() + .withAutoRemove(true) + .withInit(true) + .withMemory(memoryLimit) + .withMemorySwap(memorySwapLimit) + .withCpuShares(cpuShares) + .withCpuPeriod(cpuPeriod) + .withCpuQuota(cpuQuota); + + return dockerClient + .createContainerCmd(image) + .withHostConfig(hostConfig) + .withStdinOpen(true) + .withName(name) + .exec(); + } + + public void startContainer(String containerId) { + dockerClient.startContainerCmd(containerId).exec(); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerSessions.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerSessions.java new file mode 100644 index 00000000..f61b593e --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/DockerSessions.java @@ -0,0 +1,56 @@ +package com.javadiscord.bot.utils.docker; + +import java.util.ArrayList; +import java.util.List; + +import com.github.dockerjava.api.DockerClient; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class DockerSessions { + private static final Logger LOGGER = LogManager.getLogger(); + private final DockerClient dockerClient; + private final List sessions = new ArrayList<>(); + + public DockerSessions(DockerClient dockerClient) { + this.dockerClient = dockerClient; + } + + public Session createSession(String userId, String containerId) { + Session session = new Session(userId, containerId); + sessions.add(session); + LOGGER.info("Created new session for {}", userId); + return session; + } + + public void removeSession(Session session) { + sessions.remove(session); + dockerClient.stopContainerCmd(session.getContainerId()).exec(); + } + + public void stopContainer(Session session) { + dockerClient.stopContainerCmd(session.getContainerId()).exec(); + } + + public List getSessions() { + return sessions; + } + + public boolean hasSession(String userId) { + for (Session session : sessions) { + if (session.getSessionId().equalsIgnoreCase(userId)) { + return true; + } + } + return false; + } + + public Session getSessionForUser(String userId) { + for (Session session : sessions) { + if (session.getSessionId().equals(userId)) { + return session; + } + } + throw new RuntimeException("No session found for user"); + } +} diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/Session.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/Session.java new file mode 100644 index 00000000..8dd9fc23 --- /dev/null +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/docker/Session.java @@ -0,0 +1,39 @@ +package com.javadiscord.bot.utils.docker; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +public class Session { + private final Instant startTime; + private final String sessionId; + private final String containerId; + + private final List commandHistory = new ArrayList<>(); + + public Session(String sessionId, String containerId) { + this.startTime = Instant.now(); + this.sessionId = sessionId; + this.containerId = containerId; + } + + public void updateHistory(String command) { + commandHistory.add(command); + } + + public List getCommandHistory() { + return commandHistory; + } + + public String getSessionId() { + return sessionId; + } + + public String getContainerId() { + return containerId; + } + + public Instant getStartTime() { + return startTime; + } +} From ba722baa05343ef3388442c98d486f41270f87e1 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 21:57:54 +0100 Subject: [PATCH 22/57] Fix example --- .../processor/loader/SlashCommandLoader.java | 2 +- .../core/interaction/SlashCommandEvent.java | 9 ++++++++ .../main/java/com/javadiscord/bot/Main.java | 23 +++++++++++++++++++ .../bot/commands/slash/LinuxCommand.java | 22 ++++++++---------- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java index 6b828981..8692ab99 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java @@ -38,7 +38,7 @@ private void loadInteractionListeners() { clazz, method, method.getAnnotation(SlashCommand.class).name() ); } else { - LOGGER.error("{} failed validation", method.getName()); + throw new RuntimeException(method.getName() + " failed validation"); } } } diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java index 7fedf20b..a654298a 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/SlashCommandEvent.java @@ -15,6 +15,7 @@ import com.javadiscord.jdi.core.models.guild.Interaction; import com.javadiscord.jdi.core.models.guild.InteractionData; import com.javadiscord.jdi.core.models.guild.InteractionType; +import com.javadiscord.jdi.core.models.guild.ResolvedData; import com.javadiscord.jdi.core.models.message.embed.Embed; import com.javadiscord.jdi.core.models.user.User; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; @@ -133,6 +134,10 @@ public InteractionData interactionData() { return interaction.data(); } + public ResolvedData resolvedData() { + return interactionData().resolved(); + } + public Optional option(String name) { InteractionData interactionData = interaction.data(); ApplicationCommandOption[] options = interactionData.options(); @@ -147,4 +152,8 @@ public Optional option(String name) { public ApplicationCommandOption[] options() { return interaction.data().options(); } + + public Interaction interaction() { + return interaction; + } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java index 7642f6c6..618977c5 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java @@ -1,10 +1,15 @@ package com.javadiscord.bot; import com.javadiscord.bot.utils.chatgpt.ChatGPT; +import com.javadiscord.bot.utils.docker.DockerCommandRunner; +import com.javadiscord.bot.utils.docker.DockerSessions; import com.javadiscord.bot.utils.jshell.JShellService; import com.javadiscord.jdi.core.Discord; import com.javadiscord.jdi.core.annotations.Component; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.core.DockerClientBuilder; + public class Main { public static void main(String[] args) { Discord discord = new Discord(System.getenv("BOT_TOKEN")); @@ -20,4 +25,22 @@ public static ChatGPT chatGpt() { public static JShellService jShellService() { return new JShellService(); } + + private static DockerClient dockerClient = + DockerClientBuilder.getInstance("tcp://localhost:2375").build(); + + @Component + public static DockerClient dockerClient() { + return dockerClient; + } + + @Component + public static DockerCommandRunner dockerCommandRunner() { + return new DockerCommandRunner(dockerClient); + } + + @Component + public static DockerSessions dockerSessions() { + return new DockerSessions(dockerClient); + } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java index 771a6929..31ee7595 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/LinuxCommand.java @@ -12,6 +12,7 @@ import com.javadiscord.bot.utils.docker.*; import com.javadiscord.jdi.core.CommandOptionType; import com.javadiscord.jdi.core.annotations.CommandOption; +import com.javadiscord.jdi.core.annotations.Inject; import com.javadiscord.jdi.core.annotations.SlashCommand; import com.javadiscord.jdi.core.interaction.SlashCommandEvent; import com.javadiscord.jdi.core.models.application.ApplicationCommandOption; @@ -29,19 +30,16 @@ public class LinuxCommand { private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor(); - private final DockerClient dockerClient; - public final DockerSessions dockerSessions; - private final DockerCommandRunner commandRunner; + @Inject + private DockerClient dockerClient; - public LinuxCommand( - DockerClient dockerClient, - DockerSessions dockerSessions, - DockerCommandRunner commandRunner - ) { - this.dockerClient = dockerClient; - this.dockerSessions = dockerSessions; - this.commandRunner = commandRunner; + @Inject + private DockerSessions dockerSessions; + @Inject + private DockerCommandRunner commandRunner; + + public LinuxCommand() { Runtime.getRuntime() .addShutdownHook( new Thread( @@ -97,7 +95,7 @@ private void handleLinuxCommand(SlashCommandEvent event, String command, User me Embed embed = new Embed.Builder() - .author(new EmbedAuthor(member.asMention(), null, null, null)) + .author(new EmbedAuthor(member.displayName(), null, null, null)) .description(shortenOutput(reply)) .color(Color.RED) .build(); From 4ecd69e4d9577f72bab9ad4ec3d7f2cc0c84f4e7 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 22:14:30 +0100 Subject: [PATCH 23/57] Fix StickerUpdate --- .../com/javadiscord/jdi/core/models/message/StickerUpdate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/message/StickerUpdate.java b/models/src/main/java/com/javadiscord/jdi/core/models/message/StickerUpdate.java index e9be206b..de6074b0 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/message/StickerUpdate.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/message/StickerUpdate.java @@ -7,6 +7,6 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record StickerUpdate( - @JsonProperty("guildId") long guildId, + @JsonProperty("guild_id") long guildId, @JsonProperty("stickers") List stickers ) {} From 9288c3306c853a5ca46cd36ff558d089c8bc0904 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 22:26:34 +0100 Subject: [PATCH 24/57] Header :D --- core/src/main/java/com/javadiscord/jdi/core/Discord.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 40a6a548..c8857484 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -109,6 +109,15 @@ public Discord(String botToken, IdentifyRequest identifyRequest) { } public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) { + System.err.println(""" + _ ____ ___\s + | | _ \\_ _| https://github.com/javadiscord/java-discord-api + _ | | | | | | Open-Source Discord Framework\s + | |_| | |_| | | GPL-3.0 license\s + \\___/|____/___| Version 1.0 + + """); + this.botToken = botToken; this.discordRequestDispatcher = new DiscordRequestDispatcher(botToken); this.gateway = getGatewayURL(botToken); From 7b2b6c858c6562c5a3acfe09eb8da0da3b8f785e Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Tue, 28 May 2024 22:33:39 +0100 Subject: [PATCH 25/57] Fix bug with IntegrationUpdate --- .../main/java/com/javadiscord/jdi/core/EventListener.java | 2 +- .../java/com/javadiscord/jdi/core/GatewayEventListener.java | 2 +- .../internal/gateway/handlers/events/EventCodecHandler.java | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/javadiscord/jdi/core/EventListener.java b/core/src/main/java/com/javadiscord/jdi/core/EventListener.java index 816144ee..96430105 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/EventListener.java +++ b/core/src/main/java/com/javadiscord/jdi/core/EventListener.java @@ -133,7 +133,7 @@ default void onMessageReactionAdd(MessageReaction messageReaction, Guild guild) default void onThreadMembersUpdate(ThreadMember threadMember, Guild guild) {} - default void onGuildIntegrationUpdate(Integration integration, Guild guild) {} + default void onGuildIntegrationUpdate(IntegrationUpdate integration, Guild guild) {} default void onVoiceServerUpdate(VoiceServer voiceServer, Guild guild) {} diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java index 7168ea49..e343adb7 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java @@ -133,7 +133,7 @@ public void receive(EventType eventType, Object event) { case MESSAGE_REACTION_REMOVE -> listener.onMessageReactionsRemoved((MessageReactionsRemoved) event, guild); case GUILD_INTEGRATIONS_UPDATE -> - listener.onGuildIntegrationUpdate((Integration) event, guild); + listener.onGuildIntegrationUpdate((IntegrationUpdate) event, guild); case AUTO_MODERATION_RULE_CREATE -> listener.onAutoModerationRuleCreate((AutoModerationRule) event, guild); case AUTO_MODERATION_RULE_DELETE -> diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/EventCodecHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/EventCodecHandler.java index b3ae2647..514635c1 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/EventCodecHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/EventCodecHandler.java @@ -169,6 +169,8 @@ EventType.MESSAGE_REACTION_REMOVE_ALL, new MessageReactionsRemovedHandler() EVENT_DECODERS.put(EventType.INTERACTION_CREATE, new InteractionCreateDecoder()); EVENT_HANDLERS.put(EventType.INTERACTION_CREATE, new InteractionCreateHandler()); + EVENT_DECODERS.put(EventType.GUILD_INTEGRATIONS_UPDATE, new IntegrationUpdateDecoder()); + EVENT_HANDLERS.put(EventType.GUILD_INTEGRATIONS_UPDATE, new IntegrationUpdateHandler()); AutoModerationDecoder autoModerationDecoder = new AutoModerationDecoder(); EVENT_DECODERS.put(EventType.AUTO_MODERATION_RULE_CREATE, autoModerationDecoder); @@ -223,9 +225,6 @@ EventType.GUILD_SCHEDULED_EVENT_USER_REMOVE, new ScheduledEventUserRemoveHandler EVENT_DECODERS.put(EventType.GUILD_STICKERS_UPDATE, new StickerUpdateDecoder()); EVENT_HANDLERS.put(EventType.GUILD_STICKERS_UPDATE, new StickerUpdateHandler()); - EVENT_DECODERS.put(EventType.GUILD_INTEGRATIONS_UPDATE, new IntegrationUpdateDecoder()); - EVENT_HANDLERS.put(EventType.GUILD_INTEGRATIONS_UPDATE, new IntegrationUpdateHandler()); - EVENT_DECODERS.put(EventType.GUILD_MEMBERS_CHUNK, new MemberChunkDecoder()); EVENT_HANDLERS.put(EventType.GUILD_MEMBERS_CHUNK, new MemberChunkHandler()); } From 28263e54f0a1ee02a5710d092b94d31254824b2b Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 13:18:35 +0100 Subject: [PATCH 26/57] Moved log4j2.xml into core --- {gateway => core}/src/main/resources/log4j2.xml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {gateway => core}/src/main/resources/log4j2.xml (100%) diff --git a/gateway/src/main/resources/log4j2.xml b/core/src/main/resources/log4j2.xml similarity index 100% rename from gateway/src/main/resources/log4j2.xml rename to core/src/main/resources/log4j2.xml From c44f31643451e8073ff9d6fb6f00ae3137200fc8 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 13:28:03 +0100 Subject: [PATCH 27/57] chore: cleanup and fixes --- .../javadiscord/jdi/core/processor/ClassFileUtil.java | 11 ----------- .../jdi/internal/gateway/WebSocketHandler.java | 5 +++-- .../jdi/internal/gateway/WebSocketManager.java | 9 ++++++++- .../gateway/handlers/heartbeat/HeartbeatService.java | 4 ++++ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java index 0ee7ce82..6caf2e51 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java @@ -25,17 +25,6 @@ public static List getClassesInClassPath() { String[] classpathEntries = classpath.split(File.pathSeparator); for (String entry : classpathEntries) { - if ( - entry.contains("io.netty") - || entry.contains("org.apache") - || entry.contains("io.vertx") - || entry.contains("com.fasterxml") - || entry.contains("org.javassist") - || entry.contains("com.github.mizosoft.methanol") - ) { - continue; - } - File file = new File(entry); try { classesInPath.addAll(getClasses(file)); diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java index e8ecfd20..7d59580b 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketHandler.java @@ -28,18 +28,19 @@ public class WebSocketHandler implements Handler { private static final Map OPERATION_HANDLER = new HashMap<>(); private final ConnectionMediator connectionMediator; private final Cache cache; + private final HeartbeatService heartbeatService; public WebSocketHandler( ConnectionMediator connectionMediator, - Cache cache + Cache cache, HeartbeatService heartbeatService ) { this.connectionMediator = connectionMediator; this.cache = cache; + this.heartbeatService = heartbeatService; registerHandlers(); } private void registerHandlers() { - HeartbeatService heartbeatService = new HeartbeatService(connectionMediator); OPERATION_HANDLER.put(GatewayOpcode.HELLO, new HelloOperationHandler(heartbeatService)); OPERATION_HANDLER.put( GatewayOpcode.HEARTBEAT_ACK, new HeartbeatAckOperationHandler(heartbeatService) diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java index 9f858b07..1dd31228 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManager.java @@ -1,6 +1,7 @@ package com.javadiscord.jdi.internal.gateway; import com.javadiscord.jdi.internal.cache.Cache; +import com.javadiscord.jdi.internal.gateway.handlers.heartbeat.HeartbeatService; import com.javadiscord.jdi.internal.gateway.identify.IdentifyRequest; import com.fasterxml.jackson.core.JsonProcessingException; @@ -23,6 +24,7 @@ public class WebSocketManager { private final Cache cache; private WebSocket webSocket; private WebSocketClient webSocketClient; + private HeartbeatService heartbeatService; private boolean retryAllowed; public WebSocketManager( @@ -36,6 +38,8 @@ public WebSocketManager( } public void start(ConnectionMediator connectionMediator) { + heartbeatService = new HeartbeatService(connectionMediator); + String gatewayURL = connectionMediator.getConnectionDetails().getGatewayURL(); WebSocketConnectOptions webSocketConnectOptions = @@ -61,7 +65,7 @@ public void start(ConnectionMediator connectionMediator) { this.webSocket = webSocket; WebSocketHandler webSocketHandler = - new WebSocketHandler(connectionMediator, cache); + new WebSocketHandler(connectionMediator, cache, heartbeatService); webSocketHandler.handle(webSocket); @@ -101,6 +105,9 @@ public void stop() { if (webSocket != null && !webSocket.isClosed()) { webSocket.close(); } + if (heartbeatService != null) { + heartbeatService.stop(); + } webSocketClient.close() .onSuccess(res -> LOGGER.info("Web socket client has been shutdown")) .onFailure(err -> LOGGER.error("Failed to shutdown web socket client", err)); diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HeartbeatService.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HeartbeatService.java index a8dcb19f..98c08a25 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HeartbeatService.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/heartbeat/HeartbeatService.java @@ -55,6 +55,10 @@ private void checkHeartbeatAckReceived(WebSocket webSocket) { } } + public void stop() { + EXECUTOR_SERVICE.shutdown(); + } + public void sendHeartbeat(WebSocket webSocket) { webSocket.write( Buffer.buffer() From 93b8b924b89b2ed413dc23f970682485150fde65 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 13:53:53 +0100 Subject: [PATCH 28/57] chore: readability refactoring --- .../validator/EventListenerValidator.java | 74 ++++++++++++------- .../validator/SlashCommandValidator.java | 51 +++++++------ 2 files changed, 75 insertions(+), 50 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java index b7fa39ce..4153d35a 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java @@ -405,37 +405,55 @@ public boolean hasZeroArgsConstructor(Class clazz) { private boolean validateMethods(Class clazz) { Method[] methods = clazz.getMethods(); for (Method method : methods) { - for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP - .entrySet()) { - Class annotationClass = entry.getKey(); - if (method.isAnnotationPresent(annotationClass)) { - String[] expectedParamTypes = entry.getValue(); - if (method.getParameterCount() > 0) { - Class[] paramTypes = method.getParameterTypes(); - for (Class type : paramTypes) { - boolean isExpectedType = false; - for (String expectedType : expectedParamTypes) { - if (type.getName().equals(expectedType)) { - isExpectedType = true; - break; - } - } - if (!isExpectedType) { - LOGGER.error("Unexpected parameter found: {}", type.getName()); - return false; - } else { - LOGGER.trace("Loaded {}", clazz.getName()); - } - } - } else if (method.getParameterCount() != 0) { - LOGGER.error( - "{} does not have the expected parameter types", method.getName() - ); - return false; - } + if (!validateMethodAnnotations(method)) { + return false; + } + } + return true; + } + + private boolean validateMethodAnnotations(Method method) { + for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP + .entrySet()) { + Class annotationClass = entry.getKey(); + if (method.isAnnotationPresent(annotationClass)) { + if (!validateMethodParameters(method, entry.getValue())) { + return false; } } } return true; } + + private boolean validateMethodParameters(Method method, String[] expectedParamTypes) { + if (method.getParameterCount() > 0) { + return checkParameterTypes(method, expectedParamTypes); + } else if (method.getParameterCount() != 0) { + LOGGER.error("{} does not have the expected parameter types", method.getName()); + return false; + } + return true; + } + + private boolean checkParameterTypes(Method method, String[] expectedParamTypes) { + Class[] paramTypes = method.getParameterTypes(); + for (Class type : paramTypes) { + if (!isExpectedType(type, expectedParamTypes)) { + LOGGER.error("Unexpected parameter found: {}", type.getName()); + return false; + } else { + LOGGER.trace("Loaded {}", method.getDeclaringClass().getName()); + } + } + return true; + } + + private boolean isExpectedType(Class type, String[] expectedParamTypes) { + for (String expectedType : expectedParamTypes) { + if (type.getName().equals(expectedType)) { + return true; + } + } + return false; + } } diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java index f74f5e4a..3b3d2e40 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java @@ -28,34 +28,41 @@ public class SlashCommandValidator { } public boolean validate(Method method) { - for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP - .entrySet()) { + for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP.entrySet()) { Class annotationClass = entry.getKey(); if (method.isAnnotationPresent(annotationClass)) { - String[] expectedParamTypes = entry.getValue(); - if (method.getParameterCount() > 0) { - Class[] paramTypes = method.getParameterTypes(); - for (Class type : paramTypes) { - boolean isExpectedType = false; - for (String expectedType : expectedParamTypes) { - if (type.getName().equals(expectedType)) { - isExpectedType = true; - break; - } - } - if (!isExpectedType) { - LOGGER.error("Unexpected parameter found: {}", type.getName()); - return false; - } - } - } else if (method.getParameterCount() != 0) { - LOGGER.error( - "{} does not have the expected parameter types", method.getName() - ); + if (!validateMethodParameters(method, entry.getValue())) { return false; } } } return true; } + + private boolean validateMethodParameters(Method method, String[] expectedParamTypes) { + if (method.getParameterCount() > 0) { + return checkParameterTypes(method, expectedParamTypes); + } + return true; + } + + private boolean checkParameterTypes(Method method, String[] expectedParamTypes) { + Class[] paramTypes = method.getParameterTypes(); + for (Class type : paramTypes) { + if (!isExpectedType(type, expectedParamTypes)) { + LOGGER.error("Unexpected parameter found: {}", type.getName()); + return false; + } + } + return true; + } + + private boolean isExpectedType(Class type, String[] expectedParamTypes) { + for (String expectedType : expectedParamTypes) { + if (type.getName().equals(expectedType)) { + return true; + } + } + return false; + } } From 84c101e6cd695dbc5f25e09c066e29bb14d5a2de Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 14:00:31 +0100 Subject: [PATCH 29/57] chore: readability refactoring --- .../validator/SlashCommandValidator.java | 3 +- .../com/javadiscord/jdi/core/Discord.java | 217 +++++++++--------- 2 files changed, 106 insertions(+), 114 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java index 3b3d2e40..a7942a34 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java @@ -28,7 +28,8 @@ public class SlashCommandValidator { } public boolean validate(Method method) { - for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP.entrySet()) { + for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP + .entrySet()) { Class annotationClass = entry.getKey(); if (method.isAnnotationPresent(annotationClass)) { if (!validateMethodParameters(method, entry.getValue())) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index c8857484..ffe91608 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -110,12 +110,11 @@ public Discord(String botToken, IdentifyRequest identifyRequest) { public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) { System.err.println(""" - _ ____ ___\s + _ ____ ___ | | _ \\_ _| https://github.com/javadiscord/java-discord-api - _ | | | | | | Open-Source Discord Framework\s - | |_| | |_| | | GPL-3.0 license\s + _ | | | | | | Open-Source Discord Framework + | |_| | |_| | | GPL-3.0 license \\___/|____/___| Version 1.0 - """); this.botToken = botToken; @@ -140,8 +139,7 @@ private void registerLoadedAnnotationsWithDiscord() { try { Class slashCommandClassInstanceClass = slashCommandClassInstance.getClass(); Method method = - (Method) slashCommandClassInstanceClass - .getMethod("method") + (Method) slashCommandClassInstanceClass.getMethod("method") .invoke(slashCommandClassInstance); Annotation[] annotations = method.getAnnotations(); @@ -150,66 +148,7 @@ private void registerLoadedAnnotationsWithDiscord() { annotation.annotationType().getName() .equals("com.javadiscord.jdi.core.annotations.SlashCommand") ) { - Method nameMethod = annotation.annotationType().getMethod("name"); - String name = (String) nameMethod.invoke(annotation); - - Method descriptionMethod = - annotation.annotationType().getMethod("description"); - String description = (String) descriptionMethod.invoke(annotation); - - Method optionsMethod = annotation.annotationType().getMethod("options"); - Object[] options = (Object[]) optionsMethod.invoke(annotation); - - CommandBuilder builder = new CommandBuilder(name, description); - - for (Object option : options) { - Method optionNameMethod = option.getClass().getMethod("name"); - String optionName = (String) optionNameMethod.invoke(option); - - Method optionDescriptionMethod = - option.getClass().getMethod("description"); - String optionDescription = - (String) optionDescriptionMethod.invoke(option); - - Method optionTypeMethod = option.getClass().getMethod("type"); - Enum optionType = (Enum) optionTypeMethod.invoke(option); - String optionTypeValue = optionType.name(); - - Method optionRequiredMethod = option.getClass().getMethod("required"); - boolean optionRequired = (boolean) optionRequiredMethod.invoke(option); - - List choices = new ArrayList<>(); - - Object[] choicesArray = - (Object[]) option.getClass().getMethod("choices").invoke(option); - - for (Object choice : choicesArray) { - Annotation annotation1 = (Annotation) choice; - if ( - annotation1.annotationType().getName().equals( - "com.javadiscord.jdi.core.annotations.CommandOptionChoice" - ) - ) { - Method nameMethod1 = - annotation1.annotationType().getMethod("name"); - Method valueMethod1 = - annotation1.annotationType().getMethod("value"); - String name1 = (String) nameMethod1.invoke(annotation1); - String value1 = (String) valueMethod1.invoke(annotation1); - choices.add(new CommandOptionChoice(value1, name1)); - } - } - - builder.addOption( - new CommandOption( - optionName, - optionDescription, - CommandOptionType.fromName(optionTypeValue), - optionRequired - ).addChoice(choices) - ); - } - + CommandBuilder builder = buildCommand(annotation); createInteractionRequests.add(builder); } } @@ -219,6 +158,74 @@ private void registerLoadedAnnotationsWithDiscord() { }); } + private CommandBuilder buildCommand(Annotation annotation) throws ReflectiveOperationException { + Method nameMethod = annotation.annotationType().getMethod("name"); + String name = (String) nameMethod.invoke(annotation); + + Method descriptionMethod = annotation.annotationType().getMethod("description"); + String description = (String) descriptionMethod.invoke(annotation); + + Method optionsMethod = annotation.annotationType().getMethod("options"); + Object[] options = (Object[]) optionsMethod.invoke(annotation); + + CommandBuilder builder = new CommandBuilder(name, description); + for (Object option : options) { + addCommandOption(builder, option); + } + + return builder; + } + + private void addCommandOption( + CommandBuilder builder, + Object option + ) throws ReflectiveOperationException { + Method optionNameMethod = option.getClass().getMethod("name"); + String optionName = (String) optionNameMethod.invoke(option); + + Method optionDescriptionMethod = option.getClass().getMethod("description"); + String optionDescription = (String) optionDescriptionMethod.invoke(option); + + Method optionTypeMethod = option.getClass().getMethod("type"); + Enum optionType = (Enum) optionTypeMethod.invoke(option); + String optionTypeValue = optionType.name(); + + Method optionRequiredMethod = option.getClass().getMethod("required"); + boolean optionRequired = (boolean) optionRequiredMethod.invoke(option); + + List choices = new ArrayList<>(); + Object[] choicesArray = (Object[]) option.getClass().getMethod("choices").invoke(option); + for (Object choice : choicesArray) { + addCommandOptionChoice(choices, choice); + } + + builder.addOption( + new CommandOption( + optionName, + optionDescription, + CommandOptionType.fromName(optionTypeValue), + optionRequired + ).addChoice(choices) + ); + } + + private void addCommandOptionChoice( + List choices, + Object choice + ) throws ReflectiveOperationException { + Annotation annotation1 = (Annotation) choice; + if ( + annotation1.annotationType().getName() + .equals("com.javadiscord.jdi.core.annotations.CommandOptionChoice") + ) { + Method nameMethod1 = annotation1.annotationType().getMethod("name"); + Method valueMethod1 = annotation1.annotationType().getMethod("value"); + String name1 = (String) nameMethod1.invoke(annotation1); + String value1 = (String) valueMethod1.invoke(annotation1); + choices.add(new CommandOptionChoice(value1, name1)); + } + } + private boolean annotationLibPresent() { try { Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader"); @@ -237,7 +244,7 @@ private void loadComponents() { constructor.newInstance(); } } catch (Exception | Error e) { - /* Ignore */ + LOGGER.warn("Component loading failed", e); } } @@ -256,7 +263,7 @@ private void loadAnnotations() { } } } catch (Exception | Error e) { - /* Ignore */ + LOGGER.warn("Event listener loading failed", e); } } @@ -277,7 +284,6 @@ private void loadSlashCommands() { ); return; } - return; } } } catch (Exception | Error e) { @@ -288,15 +294,8 @@ private void loadSlashCommands() { public void start() { started = true; - webSocketManager = - new WebSocketManager( - new GatewaySetting().setApiVersion(10).setEncoding(GatewayEncoding.JSON), - identifyRequest, - cache - ); - - WebSocketManagerProxy webSocketManagerProxy = - new WebSocketManagerProxy(webSocketManager); + webSocketManager = new WebSocketManager(gatewaySetting, identifyRequest, cache); + WebSocketManagerProxy webSocketManagerProxy = new WebSocketManagerProxy(webSocketManager); ConnectionDetails connectionDetails = new ConnectionDetails(gateway.url(), botToken, gatewaySetting); ConnectionMediator connectionMediator = @@ -315,12 +314,7 @@ public void stop() { LOGGER.info("Shutdown initiated"); - if (webSocketManager != null) { - webSocketManager.stop(); - } - discordRequestDispatcher.stop(); - EXECUTOR.shutdown(); try { @@ -328,13 +322,12 @@ public void stop() { EXECUTOR.shutdownNow(); if (!EXECUTOR.awaitTermination(30, TimeUnit.SECONDS)) { LOGGER.warn( - "Executor failed to shutdown within the specified time limit, some" - + " tasks may still be running" + "Executor failed to shutdown within the specified time limit, some tasks may still be running" ); } } } catch (InterruptedException e) { - LOGGER.error("Termination was interrupted within {} seconds", 30, e); + LOGGER.error("Termination was interrupted", e); Thread.currentThread().interrupt(); } } @@ -376,16 +369,8 @@ private static Gateway getGatewayURL(String authentication) { } } - public void registerSlashCommand( - String name, - String description, - CommandOption... options - ) { - CommandBuilder builder = - new CommandBuilder( - name, - description - ); + public void registerSlashCommand(String name, String description, CommandOption... options) { + CommandBuilder builder = new CommandBuilder(name, description); for (CommandOption option : options) { builder.addOption(option); } @@ -399,7 +384,7 @@ public void registerSlashCommand(CommandBuilder builder) { } public void deleteSlashCommand(long id) { - + // Implement command deletion logic } public DiscordRequestDispatcher getDiscordRequestDispatcher() { @@ -414,7 +399,7 @@ public List getAnnotatedEventListeners() { return annotatedEventListeners; } - public boolean started() { + public boolean isStarted() { return started; } @@ -428,29 +413,35 @@ public long getApplicationId() { void handleReadyEvent(ReadyEvent event) { applicationId = event.application().id(); - EXECUTOR.execute(discordRequestDispatcher); for (CommandBuilder builder : createInteractionRequests) { builder.applicationId(applicationId); CreateCommandRequest request = builder.build(); DiscordResponseFuture future = sendRequest(request); - future.onSuccess(res -> { - if (res.status() >= 200 && res.status() < 300) { - LOGGER.info("Registered slash command {} with discord", request.name()); - } else { - LOGGER.error( - "Failed to register slash command {} with discord\n{}", request.name(), - res.body() - ); - } - }); - future.onError( - err -> LOGGER - .error("Failed to register slash command {} with discord", request.name(), err) - ); + handleCommandRegistrationResponse(request, future); } createInteractionRequests.clear(); } + + private void handleCommandRegistrationResponse( + CreateCommandRequest request, + DiscordResponseFuture future + ) { + future.onSuccess(res -> { + if (res.status() >= 200 && res.status() < 300) { + LOGGER.info("Registered slash command {} with discord", request.name()); + } else { + LOGGER.error( + "Failed to register slash command {} with discord\n{}", request.name(), + res.body() + ); + } + }); + future.onError( + err -> LOGGER + .error("Failed to register slash command {} with discord", request.name(), err) + ); + } } From f4a3dfa74995455f3d663981c83d6a5c25b6f9f5 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 14:13:35 +0100 Subject: [PATCH 30/57] chore: fix --- .../java/com/javadiscord/jdi/core/api/ChannelRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java index dd7f17c2..ebe7235b 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java @@ -63,13 +63,13 @@ Message.class, new CreateMessageBuilder(channelId).embeds(embeds).build() ); } - public AsyncResponse createReaction( + public AsyncResponse createReaction( long channelId, long messageId, Emoji emoji ) { return responseParser.callAndParse( - MessageReaction.class, new CreateReactionRequest(channelId, messageId, emoji) + Void.class, new CreateReactionRequest(channelId, messageId, emoji) ); } From 10c04577f5f1c2db0ec91c520dacba88f30cf0ec Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 14:15:29 +0100 Subject: [PATCH 31/57] chore: spotless --- .../main/java/com/javadiscord/jdi/core/api/ChannelRequest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java index ebe7235b..815fa4fd 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java @@ -9,7 +9,6 @@ import com.javadiscord.jdi.core.models.emoji.Emoji; import com.javadiscord.jdi.core.models.invite.Invite; import com.javadiscord.jdi.core.models.message.Message; -import com.javadiscord.jdi.core.models.message.MessageReaction; import com.javadiscord.jdi.core.models.message.embed.Embed; import com.javadiscord.jdi.core.models.user.User; import com.javadiscord.jdi.internal.api.channel.*; @@ -69,7 +68,7 @@ public AsyncResponse createReaction( Emoji emoji ) { return responseParser.callAndParse( - Void.class, new CreateReactionRequest(channelId, messageId, emoji) + Void.class, new CreateReactionRequest(channelId, messageId, emoji) ); } From 439c8dca623c9f62607928fbfbba492f5d83f394 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 14:23:46 +0100 Subject: [PATCH 32/57] Add Qodana GHA and fixes --- .github/workflows/qodana_code_quality.yml | 20 ++++++++++++ .../jdi/core/api/AutoModerationRequest.java | 2 +- .../CreateAutoModerationRuleBuilder.java | 2 +- .../bot/utils/chatgpt/ChatGPT.java | 4 +-- .../utils/chatgpt/ChatGPTResponseParser.java | 2 +- qodana.yaml | 31 +++++++++++++++++++ 6 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/qodana_code_quality.yml create mode 100644 qodana.yaml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 00000000..7d7af59f --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,20 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + - slash-commands + +jobs: + qodana: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2024.1 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} \ No newline at end of file diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/AutoModerationRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/AutoModerationRequest.java index b6d30fd8..f47fdc79 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/AutoModerationRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/AutoModerationRequest.java @@ -2,9 +2,9 @@ import java.util.List; +import com.javadiscord.jdi.core.api.builders.CreateAutoModerationRuleBuilder; import com.javadiscord.jdi.core.api.builders.ModifyAutoModerationRuleBuilder; import com.javadiscord.jdi.core.models.auto_moderation.AutoModerationRule; -import com.javadiscord.jdi.core.request.builders.CreateAutoModerationRuleBuilder; import com.javadiscord.jdi.internal.api.auto_moderation.*; public class AutoModerationRequest { diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateAutoModerationRuleBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateAutoModerationRuleBuilder.java index 9d64000e..0258c855 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateAutoModerationRuleBuilder.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateAutoModerationRuleBuilder.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.request.builders; +package com.javadiscord.jdi.core.api.builders; import java.util.List; import java.util.Optional; diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java index 59c38324..3ee1fce6 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java @@ -104,9 +104,9 @@ public Optional ask(String question) { openAiHttpException.statusCode ) ); - } catch (RuntimeException runtimeException) { + } catch (RuntimeException e) { logger.warn( - "There was an error using the OpenAI API: " + runtimeException.getMessage() + "There was an error using the OpenAI API: {}", e.getMessage() ); } return Optional.empty(); diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java index 1b88f643..1095c559 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java @@ -15,7 +15,7 @@ private ChatGPTResponseParser() {} public static String[] parse(String response) { String[] partedResponse = new String[] {response}; if (response.length() > RESPONSE_LENGTH_LIMIT) { - logger.debug("Response to parse:\n" + response); + logger.debug("Response to parse:\n{}", response); partedResponse = partitionAiResponse(response); } return partedResponse; diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 00000000..ebc500e0 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,31 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +projectJDK: 21 #(Applied in CI/CD pipeline) + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-jvm:latest From b124a2c4adfe83edcf4e515c3099dda6e6fdba4b Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 14:28:26 +0100 Subject: [PATCH 33/57] Fixes --- .../builders/ListPrivateArchivedThreadsBuilder.java | 2 +- .../jdi/core/interaction/InteractionEventHandler.java | 1 + qodana.yaml | 10 +++------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/ListPrivateArchivedThreadsBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/ListPrivateArchivedThreadsBuilder.java index db3eb713..1d05a182 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/builders/ListPrivateArchivedThreadsBuilder.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/ListPrivateArchivedThreadsBuilder.java @@ -22,7 +22,7 @@ public ListPrivateArchivedThreadsBuilder before(OffsetDateTime before) { } public ListPrivateArchivedThreadsBuilder limit(int limit) { - this.limit = Optional.ofNullable(limit); + this.limit = Optional.of(limit); return this; } diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 8bc669cf..56327091 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -78,6 +78,7 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); } else { Object handlerInstance = handler.getDeclaredConstructor().newInstance(); + cachedInstances.put(handler.getName(), handlerInstance); injectComponents(handlerInstance); method.invoke(handlerInstance, paramOrder.toArray()); } diff --git a/qodana.yaml b/qodana.yaml index ebc500e0..4620052a 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -3,29 +3,25 @@ # https://www.jetbrains.com/help/qodana/qodana-yaml.html # #-------------------------------------------------------------------------------# version: "1.0" - #Specify inspection profile for code analysis profile: name: qodana.starter - #Enable inspections #include: # - name: - #Disable inspections #exclude: # - name: # paths: # - - projectJDK: 21 #(Applied in CI/CD pipeline) - #Execute shell command before Qodana execution (Applied in CI/CD pipeline) #bootstrap: sh ./prepare-qodana.sh - #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) #plugins: # - id: #(plugin id can be found at https://plugins.jetbrains.com) - #Specify Qodana linter for analysis (Applied in CI/CD pipeline) linter: jetbrains/qodana-jvm:latest +include: + - name: CommentedOutCode + - name: Deprecation From c21832c66aea84c81a668802f11277ed7d44499f Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 16:16:16 +0100 Subject: [PATCH 34/57] Better compile time reflection and refactoring --- .../processor/loader/ComponentLoader.java | 10 +- .../com/javadiscord/jdi/core/Constants.java | 13 +++ .../com/javadiscord/jdi/core/Discord.java | 34 +++++-- .../core/GatewayEventListenerAnnotations.java | 94 ++++++++++++------- .../interaction/InteractionEventHandler.java | 42 ++++----- .../internal/ReflectiveComponentLoader.java | 7 ++ .../jdi/internal/ReflectiveLoader.java | 22 +++++ .../ReflectiveSlashCommandClassMethod.java | 9 ++ .../ReflectiveSlashCommandLoader.java | 7 ++ settings.gradle | 1 - 10 files changed, 159 insertions(+), 80 deletions(-) create mode 100644 core/src/main/java/com/javadiscord/jdi/core/Constants.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandLoader.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java index 567722ac..d68a50d2 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java @@ -20,15 +20,9 @@ public class ComponentLoader { private static final Map, Object> COMPONENTS = new HashMap<>(); private final ComponentValidator componentValidator = new ComponentValidator(); - public ComponentLoader() { - try { - loadComponents(); - } catch (Exception e) { - LOGGER.error("An error occurred while loading components classes", e); - } - } + public ComponentLoader() {} - private void loadComponents() { + public void loadComponents() { List classes = ClassFileUtil.getClassesInClassPath(); for (File classFile : classes) { try { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Constants.java b/core/src/main/java/com/javadiscord/jdi/core/Constants.java new file mode 100644 index 00000000..280b8887 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/core/Constants.java @@ -0,0 +1,13 @@ +package com.javadiscord.jdi.core; + +public class Constants { + public static final String COMMAND_OPTION_CHOICE_ANNOTATION = + "com.javadiscord.jdi.core.annotations.CommandOptionChoice"; + public static final String LISTENER_LOADER_CLASS = + "com.javadiscord.jdi.core.processor.loader.ListenerLoader"; + public static final String COMPONENT_LOADER_CLASS = + "com.javadiscord.jdi.core.processor.loader.ComponentLoader"; + public static final String SLASH_COMMAND_LOADER_CLASS = + "com.javadiscord.jdi.core.processor.loader.SlashCommandLoader"; + +} diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index ffe91608..a4d0445d 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -22,6 +22,9 @@ import com.javadiscord.jdi.core.api.builders.command.CommandOptionType; import com.javadiscord.jdi.core.interaction.InteractionEventHandler; import com.javadiscord.jdi.core.models.ready.ReadyEvent; +import com.javadiscord.jdi.internal.ReflectiveComponentLoader; +import com.javadiscord.jdi.internal.ReflectiveLoader; +import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; @@ -137,10 +140,11 @@ private void registerLoadedAnnotationsWithDiscord() { LOGGER.info("Registering slash commands with Discord"); loadedSlashCommands.forEach((commandName, slashCommandClassInstance) -> { try { - Class slashCommandClassInstanceClass = slashCommandClassInstance.getClass(); - Method method = - (Method) slashCommandClassInstanceClass.getMethod("method") - .invoke(slashCommandClassInstance); + ReflectiveSlashCommandClassMethod slashCommandClassMethod = + ReflectiveLoader + .proxy(slashCommandClassInstance, ReflectiveSlashCommandClassMethod.class); + + Method method = slashCommandClassMethod.method(); Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { @@ -216,7 +220,7 @@ private void addCommandOptionChoice( Annotation annotation1 = (Annotation) choice; if ( annotation1.annotationType().getName() - .equals("com.javadiscord.jdi.core.annotations.CommandOptionChoice") + .equals(Constants.COMMAND_OPTION_CHOICE_ANNOTATION) ) { Method nameMethod1 = annotation1.annotationType().getMethod("name"); Method valueMethod1 = annotation1.annotationType().getMethod("value"); @@ -228,7 +232,7 @@ private void addCommandOptionChoice( private boolean annotationLibPresent() { try { - Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader"); + Class.forName(Constants.LISTENER_LOADER_CLASS); return true; } catch (Exception e) { return false; @@ -239,9 +243,19 @@ private void loadComponents() { LOGGER.info("Loading Components"); try { Class clazz = - Class.forName("com.javadiscord.jdi.core.processor.loader.ComponentLoader"); + Class.forName(Constants.COMPONENT_LOADER_CLASS); + ReflectiveComponentLoader componentLoader = null; for (Constructor constructor : clazz.getConstructors()) { - constructor.newInstance(); + if (constructor.getParameterCount() == 0) { + componentLoader = + ReflectiveLoader + .proxy(constructor.newInstance(), ReflectiveComponentLoader.class); + } + } + if (componentLoader != null) { + componentLoader.loadComponents(); + } else { + throw new RuntimeException("Unable to create ComponentLoader instance"); } } catch (Exception | Error e) { LOGGER.warn("Component loading failed", e); @@ -252,7 +266,7 @@ private void loadAnnotations() { LOGGER.info("Loading EventListeners"); try { Class clazz = - Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader"); + Class.forName(Constants.LISTENER_LOADER_CLASS); for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 1) { Parameter parameters = constructor.getParameters()[0]; @@ -271,7 +285,7 @@ private void loadSlashCommands() { LOGGER.info("Loading SlashCommands"); try { Class clazz = - Class.forName("com.javadiscord.jdi.core.processor.loader.SlashCommandLoader"); + Class.forName(Constants.SLASH_COMMAND_LOADER_CLASS); for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 1) { Parameter parameters = constructor.getParameters()[0]; diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java index e07af5ef..b58e7ccf 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java @@ -212,56 +212,78 @@ public GatewayEventListenerAnnotations(Discord discord) { this.discord = discord; } - @SuppressWarnings("unchecked") @Override public void receive(EventType eventType, Object event) { - if (!EVENT_TYPE_ANNOTATIONS.containsKey(eventType)) { + if (!isEventTypeValid(eventType)) { return; } - Class annotationClass; - try { - annotationClass = - (Class) Class.forName(EVENT_TYPE_ANNOTATIONS.get(eventType)); - } catch (ClassNotFoundException e) { + + Class annotationClass = getAnnotationClass(eventType); + if (annotationClass == null) { LOGGER.error("Could not find annotation binding for {}", eventType); return; } + + invokeAnnotatedMethods(annotationClass, event); + } + + private boolean isEventTypeValid(EventType eventType) { + return EVENT_TYPE_ANNOTATIONS.containsKey(eventType); + } + + @SuppressWarnings("unchecked") + private Class getAnnotationClass(EventType eventType) { + try { + return (Class) Class + .forName(EVENT_TYPE_ANNOTATIONS.get(eventType)); + } catch (ClassNotFoundException e) { + return null; + } + } + + private void invokeAnnotatedMethods(Class annotationClass, Object event) { for (Object listener : discord.getAnnotatedEventListeners()) { Method[] methods = listener.getClass().getMethods(); - List paramOrder = new ArrayList<>(); for (Method method : methods) { - if (!method.isAnnotationPresent(annotationClass)) { - continue; + if (method.isAnnotationPresent(annotationClass)) { + invokeMethod(listener, method, event); } - Parameter[] parameters = method.getParameters(); - for (Parameter parameter : parameters) { - if (parameter.getParameterizedType() == event.getClass()) { - paramOrder.add(event); - } else if (parameter.getParameterizedType() == Discord.class) { - paramOrder.add(discord); - } else if (parameter.getParameterizedType() == Guild.class) { - Guild guild = GatewayEventListener.getGuild(discord, event); - paramOrder.add(guild); - } - } - try { - if (paramOrder.size() != method.getParameterCount()) { - throw new RuntimeException( - "Bound " - + paramOrder.size() - + " parameters but expected " - + method.getParameterCount() - ); - } - LOGGER.trace("Invoking method {} with params {}", method.getName(), paramOrder); + } + } + } - method.invoke(listener, paramOrder.toArray()); + private void invokeMethod(Object listener, Method method, Object event) { + List paramOrder = getParamOrder(method, event); + if (paramOrder.size() != method.getParameterCount()) { + throw new RuntimeException( + "Bound " + paramOrder.size() + " parameters but expected " + + method.getParameterCount() + ); + } - } catch (Exception e) { - LOGGER.error("Failed to invoke {}", method.getName(), e); - throw new RuntimeException(e); - } + try { + LOGGER.trace("Invoking method {} with params {}", method.getName(), paramOrder); + method.invoke(listener, paramOrder.toArray()); + } catch (Exception e) { + LOGGER.error("Failed to invoke {}", method.getName(), e); + throw new RuntimeException(e); + } + } + + private List getParamOrder(Method method, Object event) { + List paramOrder = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + for (Parameter parameter : parameters) { + if (parameter.getParameterizedType() == event.getClass()) { + paramOrder.add(event); + } else if (parameter.getParameterizedType() == Discord.class) { + paramOrder.add(discord); + } else if (parameter.getParameterizedType() == Guild.class) { + Guild guild = GatewayEventListener.getGuild(discord, event); + paramOrder.add(guild); } } + return paramOrder; } + } diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 56327091..2c29d96d 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -9,6 +9,9 @@ import com.javadiscord.jdi.core.GatewayEventListener; import com.javadiscord.jdi.core.Guild; import com.javadiscord.jdi.core.models.guild.Interaction; +import com.javadiscord.jdi.internal.ReflectiveLoader; +import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod; +import com.javadiscord.jdi.internal.ReflectiveSlashCommandLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,29 +33,21 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { String command = interaction.data().name(); try { - Class slashCommandLoaderClass = slashCommandLoader.getClass(); + ReflectiveSlashCommandLoader reflectiveSlashCommandLoader = + ReflectiveLoader.proxy(slashCommandLoader, ReflectiveSlashCommandLoader.class); - Method getSlashCommandClassMethod = - slashCommandLoaderClass.getMethod("getSlashCommandClassMethod", String.class); - - Object commandClassMethodInstance = - getSlashCommandClassMethod.invoke(slashCommandLoader, command); - - if (commandClassMethodInstance == null) { - LOGGER.warn("No handler found for /{} command", command); - return; - } - - Class handler = - (Class) commandClassMethodInstance.getClass().getMethod("clazz") - .invoke(commandClassMethodInstance); + ReflectiveSlashCommandClassMethod reflectiveSlashCommandClassMethod = + ReflectiveLoader.proxy( + reflectiveSlashCommandLoader.getSlashCommandClassMethod(command), + ReflectiveSlashCommandClassMethod.class + ); - Method method = - (Method) commandClassMethodInstance.getClass().getMethod("method") - .invoke(commandClassMethodInstance); + Class handler = reflectiveSlashCommandClassMethod.clazz(); + Method method = reflectiveSlashCommandClassMethod.method(); List paramOrder = new ArrayList<>(); Parameter[] parameters = method.getParameters(); + for (Parameter parameter : parameters) { if (parameter.getParameterizedType() == interaction.getClass()) { paramOrder.add(interaction); @@ -88,13 +83,10 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { } } - private void injectComponents(Object object) throws Exception { - Class slashCommandLoaderClass = slashCommandLoader.getClass(); + private void injectComponents(Object object) { + ReflectiveSlashCommandLoader reflectiveSlashCommandLoader = + ReflectiveLoader.proxy(slashCommandLoader, ReflectiveSlashCommandLoader.class); - Method injectComponentsMethod = - slashCommandLoaderClass.getMethod("injectComponents", Object.class); - - injectComponentsMethod.invoke(slashCommandLoader, object); + reflectiveSlashCommandLoader.injectComponents(object); } - } diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java new file mode 100644 index 00000000..0af9f55f --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java @@ -0,0 +1,7 @@ +package com.javadiscord.jdi.internal; + +public interface ReflectiveComponentLoader { + void loadComponents(); + + void injectComponents(Object component); +} diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java new file mode 100644 index 00000000..f2fc3000 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java @@ -0,0 +1,22 @@ +package com.javadiscord.jdi.internal; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +public class ReflectiveLoader { + public static T proxy(Object object, Class interfaceClass) { + InvocationHandler handler = + (proxy, method, methodArgs) -> object.getClass() + .getMethod(method.getName(), method.getParameterTypes()) + .invoke(object, methodArgs); + + @SuppressWarnings("unchecked") T proxyInstance = + (T) Proxy.newProxyInstance( + interfaceClass.getClassLoader(), + new Class[] {interfaceClass}, + handler + ); + + return proxyInstance; + } +} diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java new file mode 100644 index 00000000..6cb82e96 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java @@ -0,0 +1,9 @@ +package com.javadiscord.jdi.internal; + +import java.lang.reflect.Method; + +public interface ReflectiveSlashCommandClassMethod { + Class clazz(); + + Method method(); +} diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandLoader.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandLoader.java new file mode 100644 index 00000000..d88434ab --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandLoader.java @@ -0,0 +1,7 @@ +package com.javadiscord.jdi.internal; + +public interface ReflectiveSlashCommandLoader { + Object getSlashCommandClassMethod(String name); + + void injectComponents(Object object); +} diff --git a/settings.gradle b/settings.gradle index 03f66d72..c058073f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,4 +9,3 @@ include 'example:echo-bot' findProject(':example:echo-bot')?.name = 'echo-bot' include 'example:lj-discord-bot' findProject(':example:lj-discord-bot')?.name = 'lj-discord-bot' - From fe0240a2676a857c92a3fed9515dbb6c14019764 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 17:11:20 +0100 Subject: [PATCH 35/57] Refactoring --- .../jdi/core/processor/ClassFileUtil.java | 47 ++++---- .../processor/loader/ComponentLoader.java | 113 ++++++++++-------- .../jdi/core/api/GuildRequest.java | 25 ++-- .../jdi/core/api/GuildTemplateRequest.java | 8 +- .../javadiscord/jdi/core/api/UserRequest.java | 8 +- .../jdi/internal/cache/CacheTest.java | 16 +-- .../jdi/core/GatewayEventListener.java | 53 ++++---- .../java/com/javadiscord/jdi/core/Guild.java | 7 +- .../internal/ReflectiveComponentLoader.java | 2 - .../events/codec/decoders/GuildDecoder.java | 8 +- .../handlers/guild/GuildCreateHandler.java | 6 +- .../handlers/guild/GuildDeleteHandler.java | 6 +- .../guild/GuildUpdateEventHandler.java | 6 +- .../core/models/application/Application.java | 4 +- .../guild/{Guild.java => GuildModel.java} | 2 +- .../jdi/core/models/guild/Interaction.java | 2 +- .../models/guild_template/GuildTemplate.java | 4 +- .../jdi/core/models/ready/ReadyEvent.java | 4 +- .../jdi/core/models/webhook/Webhook.java | 4 +- ...t.java => ThreadGuildModelMemberTest.java} | 2 +- 20 files changed, 180 insertions(+), 147 deletions(-) rename models/src/main/java/com/javadiscord/jdi/core/models/guild/{Guild.java => GuildModel.java} (99%) rename models/src/test/unit/com/javadiscord/jdi/internal/channel/{ThreadGuildMemberTest.java => ThreadGuildModelMemberTest.java} (97%) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java index 6caf2e51..dbeae656 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java @@ -18,44 +18,45 @@ public class ClassFileUtil { private ClassFileUtil() {} public static List getClassesInClassPath() { - if (!classesInPath.isEmpty()) { - return classesInPath; - } - String classpath = System.getProperty("java.class.path"); - String[] classpathEntries = classpath.split(File.pathSeparator); + if (classesInPath.isEmpty()) { + String classpath = System.getProperty("java.class.path"); + String[] classpathEntries = classpath.split(File.pathSeparator); - for (String entry : classpathEntries) { - File file = new File(entry); - try { - classesInPath.addAll(getClasses(file)); - } catch (IOException ignore) { - /* Ignore */ + for (String entry : classpathEntries) { + File file = new File(entry); + try { + classesInPath.addAll(getClasses(file)); + } catch (IOException ignore) { + /* Ignore */ + } } } return classesInPath; } public static String getClassName(File file) throws IOException { - String className = null; try ( FileInputStream fis = new FileInputStream(file); DataInputStream dis = new DataInputStream(fis) ) { if (isJarFile(file)) { - try (ZipInputStream zip = new ZipInputStream(fis)) { - ZipEntry entry; - while ((entry = zip.getNextEntry()) != null) { - if (!entry.isDirectory() && entry.getName().endsWith(".class")) { - className = extractClassName(zip); - break; - } - } - } + return getClassNameFromJar(fis); } else { - className = extractClassName(dis); + return extractClassName(dis); + } + } + } + + private static String getClassNameFromJar(FileInputStream fis) throws IOException { + try (ZipInputStream zip = new ZipInputStream(fis)) { + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) { + if (!entry.isDirectory() && entry.getName().endsWith(".class")) { + return extractClassName(zip); + } } } - return className; + return null; } private static List getClasses(File file) throws IOException { diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java index d68a50d2..e42abe15 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java @@ -25,62 +25,79 @@ public ComponentLoader() {} public void loadComponents() { List classes = ClassFileUtil.getClassesInClassPath(); for (File classFile : classes) { - try { - Class clazz = Class.forName(ClassFileUtil.getClassName(classFile)); - if (componentValidator.validate(clazz)) { - for (Method method : clazz.getMethods()) { - if (method.isAnnotationPresent(Component.class)) { - if (!COMPONENTS.containsKey(method.getReturnType())) { - COMPONENTS.put(method.getReturnType(), method.invoke(null)); - LOGGER - .info("Loaded component {}", method.getReturnType().getName()); - } else { - LOGGER.error( - "Component {} already loaded", method.getReturnType().getName() - ); - } - } - } - } else { - LOGGER.error("{} failed validation", clazz.getName()); - } - } catch (Exception | Error ignore) { - /* Ignore */ + processClassFile(classFile); + } + } + + private void processClassFile(File classFile) { + try { + Class clazz = Class.forName(ClassFileUtil.getClassName(classFile)); + if (componentValidator.validate(clazz)) { + processClassMethods(clazz); + } else { + LOGGER.error("{} failed validation", clazz.getName()); } + } catch (Exception | Error ignore) { + // Ignore + } + } + + private void processClassMethods(Class clazz) throws Exception { + for (Method method : clazz.getMethods()) { + if (method.isAnnotationPresent(Component.class)) { + registerComponent(method); + } + } + } + + private void registerComponent(Method method) throws Exception { + if (!COMPONENTS.containsKey(method.getReturnType())) { + COMPONENTS.put(method.getReturnType(), method.invoke(null)); + LOGGER.info("Loaded component {}", method.getReturnType().getName()); + } else { + LOGGER.error("Component {} already loaded", method.getReturnType().getName()); } } public static void injectComponents(Object component) { try { - Class clazz = component.getClass(); - for (Field field : clazz.getDeclaredFields()) { - if (field.isAnnotationPresent(Inject.class)) { - if (COMPONENTS.containsKey(field.getType())) { - Object dependency = COMPONENTS.get(field.getType()); - if (dependency != null) { - field.setAccessible(true); - try { - field.set(component, dependency); - LOGGER.info( - "Injected component {} into {}", - dependency.getClass().getName(), field.getType() - ); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "Failed to inject dependency into field: " + field.getName(), - e - ); - } - } - } else { - LOGGER.error( - "No object {} was found in field {}", field.getType(), field.getName() - ); - } + injectFields(component); + } catch (Exception e) { + LOGGER.error("Failed to inject components into {}", component.getClass().getName(), e); + } + } + + private static void injectFields(Object component) { + Class clazz = component.getClass(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class)) { + injectField(component, field); + } + } + } + + private static void injectField(Object component, Field field) { + if (COMPONENTS.containsKey(field.getType())) { + Object dependency = COMPONENTS.get(field.getType()); + if (dependency != null) { + try { + field.setAccessible(true); + field.set(component, dependency); + LOGGER.info( + "Injected component {} into {}", + dependency.getClass().getName(), field.getType() + ); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Failed to inject dependency into field: " + field.getName(), + e + ); } } - } catch (Exception | Error ignore) { - /* Ignore */ + } else { + LOGGER.error( + "No object {} was found in field {}", field.getType(), field.getName() + ); } } } diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/GuildRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/GuildRequest.java index 51f11bb3..b442dbee 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/GuildRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/GuildRequest.java @@ -49,8 +49,8 @@ public AsyncResponse createGuildChannel(CreateGuildChannelBuilder build return responseParser.callAndParse(Channel.class, builder.guildId(guildId).build()); } - public AsyncResponse createGuild(CreateGuildBuilder builder) { - return responseParser.callAndParse(Guild.class, builder.build()); + public AsyncResponse createGuild(CreateGuildBuilder builder) { + return responseParser.callAndParse(GuildModel.class, builder.build()); } public AsyncResponse createGuildRole(CreateGuildRoleBuilder builder) { @@ -63,8 +63,8 @@ Void.class, new DeleteGuildIntegrationRequest(guildId, integrationId) ); } - public AsyncResponse deleteGuild() { - return responseParser.callAndParse(Guild.class, new DeleteGuildRequest(guildId)); + public AsyncResponse deleteGuild() { + return responseParser.callAndParse(GuildModel.class, new DeleteGuildRequest(guildId)); } public AsyncResponse deleteGuildRole(long roleId) { @@ -105,16 +105,16 @@ Onboarding.class, new GetGuildOnboardingRequest(guildId) ); } - public AsyncResponse guildPreview() { - return responseParser.callAndParse(Guild.class, new GetGuildPreviewRequest(guildId)); + public AsyncResponse guildPreview() { + return responseParser.callAndParse(GuildModel.class, new GetGuildPreviewRequest(guildId)); } public AsyncResponse guildPruneCount(GetGuildPruneCountBuilder builder) { return responseParser.callAndParse(PruneCount.class, builder.guildId(guildId).build()); } - public AsyncResponse guild(GetGuildBuilder builder) { - return responseParser.callAndParse(Guild.class, builder.guildId(guildId).build()); + public AsyncResponse guild(GetGuildBuilder builder) { + return responseParser.callAndParse(GuildModel.class, builder.guildId(guildId).build()); } public AsyncResponse> guildRoles() { @@ -212,8 +212,8 @@ public AsyncResponse modifyGuildOnboarding( ); } - public AsyncResponse modifyGuild(ModifyGuildBuilder builder) { - return responseParser.callAndParse(Guild.class, builder.guildId(guildId).build()); + public AsyncResponse modifyGuild(ModifyGuildBuilder builder) { + return responseParser.callAndParse(GuildModel.class, builder.guildId(guildId).build()); } public AsyncResponse> modifyGuildRolePositions( @@ -242,8 +242,9 @@ public AsyncResponse modifyUserVoiceState(ModifyUserVoiceStateBuilde return responseParser.callAndParse(VoiceState.class, builder.guildId(guildId).build()); } - public AsyncResponse removeGuildBan(long userId) { - return responseParser.callAndParse(Guild.class, new RemoveGuildBanRequest(guildId, userId)); + public AsyncResponse removeGuildBan(long userId) { + return responseParser + .callAndParse(GuildModel.class, new RemoveGuildBanRequest(guildId, userId)); } public AsyncResponse removeGuildMemberRole(long userId, long roleId) { diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/GuildTemplateRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/GuildTemplateRequest.java index c439b747..2faf3580 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/GuildTemplateRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/GuildTemplateRequest.java @@ -5,7 +5,7 @@ import com.javadiscord.jdi.core.api.builders.CreateGuildFromTemplateBuilder; import com.javadiscord.jdi.core.api.builders.CreateGuildTemplateBuilder; import com.javadiscord.jdi.core.api.builders.ModifyGuildTemplateBuilder; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.core.models.guild_template.GuildTemplate; import com.javadiscord.jdi.internal.api.guild_template.*; @@ -18,9 +18,11 @@ public GuildTemplateRequest(DiscordResponseParser responseParser, long guildId) this.guildId = guildId; } - public AsyncResponse createGuildFromTemplate(CreateGuildFromTemplateBuilder builder) { + public AsyncResponse createGuildFromTemplate( + CreateGuildFromTemplateBuilder builder + ) { return responseParser.callAndParse( - Guild.class, builder.build() + GuildModel.class, builder.build() ); } diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/UserRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/UserRequest.java index 8bcba63d..39406fb1 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/UserRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/UserRequest.java @@ -7,7 +7,7 @@ import com.javadiscord.jdi.core.api.builders.ModifyCurrentUserBuilder; import com.javadiscord.jdi.core.api.builders.UpdateCurrentUserApplicationRoleConnectionBuilder; import com.javadiscord.jdi.core.models.channel.Channel; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.core.models.user.Connection; import com.javadiscord.jdi.core.models.user.Member; import com.javadiscord.jdi.core.models.user.User; @@ -54,8 +54,10 @@ Member.class, new GetCurrentUserGuildMemberRequest(guildId) ); } - public AsyncResponse> getCurrentUserGuilds(GetCurrentUserGuildsBuilder builder) { - return responseParser.callAndParseList(Guild.class, builder.build()); + public AsyncResponse> getCurrentUserGuilds( + GetCurrentUserGuildsBuilder builder + ) { + return responseParser.callAndParseList(GuildModel.class, builder.build()); } public AsyncResponse getCurrentUser() { diff --git a/cache/src/test/unit/com/javadiscord/jdi/internal/cache/CacheTest.java b/cache/src/test/unit/com/javadiscord/jdi/internal/cache/CacheTest.java index b07d4378..e713d948 100644 --- a/cache/src/test/unit/com/javadiscord/jdi/internal/cache/CacheTest.java +++ b/cache/src/test/unit/com/javadiscord/jdi/internal/cache/CacheTest.java @@ -4,7 +4,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import org.junit.jupiter.api.Test; @@ -12,7 +12,7 @@ class CacheTest { @Test void testFetchingGuildFromCache() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); Cache cache = new Cache(CacheType.FULL); @@ -25,7 +25,7 @@ void testFetchingGuildFromCache() { @Test void testFetchingGuildFromCacheWithNoCache() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); Cache cache = new Cache(CacheType.NO_CACHE); @@ -37,7 +37,7 @@ void testFetchingGuildFromCacheWithNoCache() { @Test void testFetchingGuildFromCacheWithPartialCache() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); Cache cache = new Cache(CacheType.PARTIAL); @@ -49,7 +49,7 @@ void testFetchingGuildFromCacheWithPartialCache() { @Test void testFetchingFromFullCache() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); Cache cache = new Cache(CacheType.FULL); @@ -70,7 +70,7 @@ void testFetchingFromFullCache() { @Test void testFetchingFromFullCacheWhenItemNotPresent() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); Cache cache = new Cache(CacheType.FULL); @@ -86,7 +86,7 @@ void testFetchingFromFullCacheWhenItemNotPresent() { @Test void testRemovingGuildFromCache() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); Cache cache = new Cache(CacheType.FULL); @@ -101,7 +101,7 @@ void testRemovingGuildFromCache() { @Test void testGetCacheForType() { - Guild guildMock = mock(Guild.class); + GuildModel guildMock = mock(GuildModel.class); when(guildMock.id()).thenReturn(1L); { diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java index e343adb7..0299bc56 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java @@ -38,40 +38,51 @@ public GatewayEventListener(Discord discord) { } public static Guild getGuild(Discord discord, Object event) { - if (event instanceof com.javadiscord.jdi.core.models.guild.Guild) { - return new Guild( - (com.javadiscord.jdi.core.models.guild.Guild) event, - discord.getCache(), - discord + if (event instanceof GuildModel) { + return createGuildFromEvent( + discord, (GuildModel) event ); + } else { + return createGuildFromEventObject(discord, event); } + } + + private static Guild createGuildFromEvent( + Discord discord, + GuildModel guildEvent + ) { + return new Guild(guildEvent, discord.getCache(), discord); + } + private static Guild createGuildFromEventObject(Discord discord, Object event) { Cache cache = discord.getCache(); Guild guild = null; try { - Field guildIdField = event.getClass().getDeclaredField("guildId"); - guildIdField.setAccessible(true); - long guildId; - - if (guildIdField.getType() == String.class) { - guildId = Long.parseLong((String) guildIdField.get(event)); - } else { - guildId = (long) guildIdField.get(event); - } - - com.javadiscord.jdi.core.models.guild.Guild model = - (com.javadiscord.jdi.core.models.guild.Guild) cache.getCacheForGuild(guildId) - .get( - guildId, - com.javadiscord.jdi.core.models.guild.Guild.class - ); + long guildId = extractGuildId(event); + GuildModel model = + (GuildModel) cache.getCacheForGuild(guildId) + .get(guildId, GuildModel.class); guild = new Guild(model, cache, discord); } catch (NoSuchFieldException | IllegalAccessException e) { LOGGER.debug("{} did not come with a guildId field", event.getClass().getSimpleName()); } + return guild; } + private static long extractGuildId( + Object event + ) throws NoSuchFieldException, IllegalAccessException { + Field guildIdField = event.getClass().getDeclaredField("guildId"); + guildIdField.setAccessible(true); + + if (guildIdField.getType() == String.class) { + return Long.parseLong((String) guildIdField.get(event)); + } else { + return (long) guildIdField.get(event); + } + } + @Override public void receive(EventType eventType, Object event) { if (eventType == EventType.READY) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Guild.java b/core/src/main/java/com/javadiscord/jdi/core/Guild.java index d590a562..1ce4d68f 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Guild.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Guild.java @@ -2,10 +2,11 @@ import com.javadiscord.jdi.core.api.*; import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.internal.cache.Cache; public class Guild { - private final com.javadiscord.jdi.core.models.guild.Guild metadata; + private final GuildModel metadata; private final Cache cache; private final Discord discord; private final ApplicationRequest applicationRequest; @@ -25,7 +26,7 @@ public class Guild { private final VoiceRequest voiceRequest; private final InteractionRequest interactionRequest; - public Guild(com.javadiscord.jdi.core.models.guild.Guild guild, Cache cache, Discord discord) { + public Guild(GuildModel guild, Cache cache, Discord discord) { this.metadata = guild; this.cache = cache; this.discord = discord; @@ -72,7 +73,7 @@ public void sendMessage(CreateMessageBuilder builder) { channelRequest.createMessage(builder); } - public com.javadiscord.jdi.core.models.guild.Guild getMetadata() { + public GuildModel getMetadata() { return metadata; } diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java index 0af9f55f..858f04f1 100644 --- a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveComponentLoader.java @@ -2,6 +2,4 @@ public interface ReflectiveComponentLoader { void loadComponents(); - - void injectComponents(Object component); } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java index c94ba450..15325a42 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/decoders/GuildDecoder.java @@ -1,6 +1,6 @@ package com.javadiscord.jdi.internal.gateway.handlers.events.codec.decoders; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.internal.gateway.GatewayEvent; import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventDecoder; @@ -9,14 +9,14 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -public class GuildDecoder implements EventDecoder { +public class GuildDecoder implements EventDecoder { private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().addModule(new JavaTimeModule()).build(); @Override - public Guild decode(GatewayEvent gatewayEvent) { + public GuildModel decode(GatewayEvent gatewayEvent) { try { - return OBJECT_MAPPER.readValue(gatewayEvent.data().toString(), Guild.class); + return OBJECT_MAPPER.readValue(gatewayEvent.data().toString(), GuildModel.class); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildCreateHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildCreateHandler.java index 71199e0c..6bff5d3e 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildCreateHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildCreateHandler.java @@ -1,14 +1,14 @@ package com.javadiscord.jdi.internal.gateway.handlers.events.codec.handlers.guild; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.internal.cache.Cache; import com.javadiscord.jdi.internal.gateway.ConnectionMediator; import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventHandler; -public class GuildCreateHandler implements EventHandler { +public class GuildCreateHandler implements EventHandler { @Override - public void handle(Guild event, ConnectionMediator connectionMediator, Cache cache) { + public void handle(GuildModel event, ConnectionMediator connectionMediator, Cache cache) { cache.createCache(event.id()); cache.getCacheForGuild(event.id()).add(event.id(), event); } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildDeleteHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildDeleteHandler.java index f16c2bad..d81c2e6f 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildDeleteHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildDeleteHandler.java @@ -1,13 +1,13 @@ package com.javadiscord.jdi.internal.gateway.handlers.events.codec.handlers.guild; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.internal.cache.Cache; import com.javadiscord.jdi.internal.gateway.ConnectionMediator; import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventHandler; -public class GuildDeleteHandler implements EventHandler { +public class GuildDeleteHandler implements EventHandler { @Override - public void handle(Guild event, ConnectionMediator connectionMediator, Cache cache) { + public void handle(GuildModel event, ConnectionMediator connectionMediator, Cache cache) { cache.removeGuild(event.id()); } } diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildUpdateEventHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildUpdateEventHandler.java index 1e426080..49e7d2ad 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildUpdateEventHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/guild/GuildUpdateEventHandler.java @@ -1,13 +1,13 @@ package com.javadiscord.jdi.internal.gateway.handlers.events.codec.handlers.guild; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.internal.cache.Cache; import com.javadiscord.jdi.internal.gateway.ConnectionMediator; import com.javadiscord.jdi.internal.gateway.handlers.events.codec.EventHandler; -public class GuildUpdateEventHandler implements EventHandler { +public class GuildUpdateEventHandler implements EventHandler { @Override - public void handle(Guild event, ConnectionMediator connectionMediator, Cache cache) { + public void handle(GuildModel event, ConnectionMediator connectionMediator, Cache cache) { if (!cache.isGuildCached(event.id())) { cache.createCache(event.id()); } diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/application/Application.java b/models/src/main/java/com/javadiscord/jdi/core/models/application/Application.java index a9b6f543..b0f99fe6 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/application/Application.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/application/Application.java @@ -2,7 +2,7 @@ import java.util.List; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.core.models.user.User; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -30,7 +30,7 @@ public record Application( @JsonProperty("owner") User owner, @JsonProperty("verify_key") String verifyKey, @JsonProperty("guild_id") String guildId, - @JsonProperty("guild") Guild guild, + @JsonProperty("guild") GuildModel guild, @JsonProperty("primary_sku_id") String primarySkuId, @JsonProperty("slug") String slug, @JsonProperty("cover_image") String coverImage, diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/guild/Guild.java b/models/src/main/java/com/javadiscord/jdi/core/models/guild/GuildModel.java similarity index 99% rename from models/src/main/java/com/javadiscord/jdi/core/models/guild/Guild.java rename to models/src/main/java/com/javadiscord/jdi/core/models/guild/GuildModel.java index 135e2527..1a7c8f9f 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/guild/Guild.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/guild/GuildModel.java @@ -9,7 +9,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public record Guild( +public record GuildModel( @JsonProperty("id") long id, @JsonProperty("name") String name, @JsonProperty("icon") String icon, diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/guild/Interaction.java b/models/src/main/java/com/javadiscord/jdi/core/models/guild/Interaction.java index 41792ae7..00b4950d 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/guild/Interaction.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/guild/Interaction.java @@ -18,7 +18,7 @@ public record Interaction( @JsonProperty("application_id") long applicationId, @JsonProperty("type") InteractionType type, @JsonProperty("data") InteractionData data, - @JsonProperty("guild") Guild guild, + @JsonProperty("guild") GuildModel guild, @JsonProperty("guild_id") long guildId, @JsonProperty("channel") Channel channel, @JsonProperty("channel_id") long channelId, diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/guild_template/GuildTemplate.java b/models/src/main/java/com/javadiscord/jdi/core/models/guild_template/GuildTemplate.java index f0399fe3..f49a7e68 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/guild_template/GuildTemplate.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/guild_template/GuildTemplate.java @@ -2,7 +2,7 @@ import java.time.OffsetDateTime; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.core.models.user.User; import com.fasterxml.jackson.annotation.JsonFormat; @@ -24,6 +24,6 @@ public record GuildTemplate( "updated_at" ) OffsetDateTime updatedAt, @JsonProperty("source_guild_id") long sourceGuildId, - @JsonProperty("serialized_source_guild") Guild sourceGuild, + @JsonProperty("serialized_source_guild") GuildModel sourceGuild, @JsonProperty("is_dirty") boolean isDirty ) {} diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/ready/ReadyEvent.java b/models/src/main/java/com/javadiscord/jdi/core/models/ready/ReadyEvent.java index ee8194a6..34988d3a 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/ready/ReadyEvent.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/ready/ReadyEvent.java @@ -2,7 +2,7 @@ import java.util.List; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.core.models.user.User; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -20,7 +20,7 @@ public record ReadyEvent( String[] relationships, String[] private_channels, String[] presences, - List guilds, + List guilds, @JsonProperty("guild_join_requests") String[] guildJoinRequests, @JsonProperty("geo_ordered_rtc_regions") String[] geoOrderedRtcRegions, Auth auth, diff --git a/models/src/main/java/com/javadiscord/jdi/core/models/webhook/Webhook.java b/models/src/main/java/com/javadiscord/jdi/core/models/webhook/Webhook.java index a6fb092a..2593f30d 100644 --- a/models/src/main/java/com/javadiscord/jdi/core/models/webhook/Webhook.java +++ b/models/src/main/java/com/javadiscord/jdi/core/models/webhook/Webhook.java @@ -1,7 +1,7 @@ package com.javadiscord.jdi.core.models.webhook; import com.javadiscord.jdi.core.models.channel.Channel; -import com.javadiscord.jdi.core.models.guild.Guild; +import com.javadiscord.jdi.core.models.guild.GuildModel; import com.javadiscord.jdi.core.models.user.User; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -18,7 +18,7 @@ public record Webhook( @JsonProperty("avatar") String avatar, @JsonProperty("token") String token, @JsonProperty("application_id") long applicationId, - @JsonProperty("source_guild") Guild sourceGuild, + @JsonProperty("source_guild") GuildModel sourceGuild, @JsonProperty("source_channel") Channel sourceChannel, @JsonProperty("url") String url ) {} diff --git a/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildMemberTest.java b/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java similarity index 97% rename from models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildMemberTest.java rename to models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java index 3d76ff9d..01af50b6 100644 --- a/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildMemberTest.java +++ b/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java @@ -13,7 +13,7 @@ import java.time.OffsetDateTime; -class ThreadGuildMemberTest { +class ThreadGuildModelMemberTest { private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().addModule(new JavaTimeModule()).build(); From deb8ac8596dd866b803da34c129e027e80e50445 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 17:13:27 +0100 Subject: [PATCH 36/57] Refactoring --- .../interaction/InteractionEventHandler.java | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 2c29d96d..1de7dc3b 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -45,37 +45,10 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { Class handler = reflectiveSlashCommandClassMethod.clazz(); Method method = reflectiveSlashCommandClassMethod.method(); - List paramOrder = new ArrayList<>(); - Parameter[] parameters = method.getParameters(); - - for (Parameter parameter : parameters) { - if (parameter.getParameterizedType() == interaction.getClass()) { - paramOrder.add(interaction); - } else if (parameter.getParameterizedType() == Discord.class) { - paramOrder.add(discord); - } else if (parameter.getParameterizedType() == Guild.class) { - paramOrder.add(GatewayEventListener.getGuild(discord, interaction.guild())); - } else if (parameter.getParameterizedType() == SlashCommandEvent.class) { - paramOrder.add(new SlashCommandEvent(interaction, discord)); - } - } - - if (paramOrder.size() != method.getParameterCount()) { - throw new RuntimeException( - "Bound " - + paramOrder.size() - + " parameters but expected " - + method.getParameterCount() - ); - } + List paramOrder = getOrderOfParameters(method, interaction); - if (cachedInstances.containsKey(handler.getName())) { - method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); - } else { - Object handlerInstance = handler.getDeclaredConstructor().newInstance(); - cachedInstances.put(handler.getName(), handlerInstance); - injectComponents(handlerInstance); - method.invoke(handlerInstance, paramOrder.toArray()); + if (validateParameterCount(method, paramOrder)) { + invokeHandler(handler, method, paramOrder); } } catch (Exception e) { @@ -83,6 +56,50 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { } } + private List getOrderOfParameters(Method method, Interaction interaction) { + List paramOrder = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + + for (Parameter parameter : parameters) { + if (parameter.getParameterizedType() == interaction.getClass()) { + paramOrder.add(interaction); + } else if (parameter.getParameterizedType() == Discord.class) { + paramOrder.add(discord); + } else if (parameter.getParameterizedType() == Guild.class) { + paramOrder.add(GatewayEventListener.getGuild(discord, interaction.guild())); + } else if (parameter.getParameterizedType() == SlashCommandEvent.class) { + paramOrder.add(new SlashCommandEvent(interaction, discord)); + } + } + + return paramOrder; + } + + private boolean validateParameterCount(Method method, List paramOrder) { + if (paramOrder.size() != method.getParameterCount()) { + throw new RuntimeException( + "Bound " + paramOrder.size() + " parameters but expected " + + method.getParameterCount() + ); + } + return true; + } + + private void invokeHandler( + Class handler, + Method method, + List paramOrder + ) throws Exception { + if (cachedInstances.containsKey(handler.getName())) { + method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); + } else { + Object handlerInstance = handler.getDeclaredConstructor().newInstance(); + cachedInstances.put(handler.getName(), handlerInstance); + injectComponents(handlerInstance); + method.invoke(handlerInstance, paramOrder.toArray()); + } + } + private void injectComponents(Object object) { ReflectiveSlashCommandLoader reflectiveSlashCommandLoader = ReflectiveLoader.proxy(slashCommandLoader, ReflectiveSlashCommandLoader.class); From b880043bbc84c960a6ff77fc0a112131d11c8c54 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 17:35:03 +0100 Subject: [PATCH 37/57] Refactoring --- .../com/javadiscord/jdi/core/Constants.java | 12 ++++ .../com/javadiscord/jdi/core/Discord.java | 58 +++++++------------ .../jdi/internal/ReflectiveCommandOption.java | 13 +++++ .../ReflectiveCommandOptionChoice.java | 7 +++ 4 files changed, 53 insertions(+), 37 deletions(-) create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOption.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOptionChoice.java diff --git a/core/src/main/java/com/javadiscord/jdi/core/Constants.java b/core/src/main/java/com/javadiscord/jdi/core/Constants.java index 280b8887..adedf72d 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Constants.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Constants.java @@ -3,6 +3,10 @@ public class Constants { public static final String COMMAND_OPTION_CHOICE_ANNOTATION = "com.javadiscord.jdi.core.annotations.CommandOptionChoice"; + + public static final String SLASH_COMMAND_ANNOTATION = + "com.javadiscord.jdi.core.annotations.SlashCommand"; + public static final String LISTENER_LOADER_CLASS = "com.javadiscord.jdi.core.processor.loader.ListenerLoader"; public static final String COMPONENT_LOADER_CLASS = @@ -10,4 +14,12 @@ public class Constants { public static final String SLASH_COMMAND_LOADER_CLASS = "com.javadiscord.jdi.core.processor.loader.SlashCommandLoader"; + public static final String LAUNCH_HEADER = """ + _ ____ ___ + | | _ \\_ _| https://github.com/javadiscord/java-discord-api + _ | | | | | | Open-Source Discord Framework + | |_| | |_| | | GPL-3.0 license + \\___/|____/___| Version 1.0 + """; + } diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index a4d0445d..bf7a7f2b 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -22,9 +22,7 @@ import com.javadiscord.jdi.core.api.builders.command.CommandOptionType; import com.javadiscord.jdi.core.interaction.InteractionEventHandler; import com.javadiscord.jdi.core.models.ready.ReadyEvent; -import com.javadiscord.jdi.internal.ReflectiveComponentLoader; -import com.javadiscord.jdi.internal.ReflectiveLoader; -import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod; +import com.javadiscord.jdi.internal.*; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; @@ -112,13 +110,7 @@ public Discord(String botToken, IdentifyRequest identifyRequest) { } public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) { - System.err.println(""" - _ ____ ___ - | | _ \\_ _| https://github.com/javadiscord/java-discord-api - _ | | | | | | Open-Source Discord Framework - | |_| | |_| | | GPL-3.0 license - \\___/|____/___| Version 1.0 - """); + System.err.println(Constants.LAUNCH_HEADER); this.botToken = botToken; this.discordRequestDispatcher = new DiscordRequestDispatcher(botToken); @@ -150,7 +142,7 @@ private void registerLoadedAnnotationsWithDiscord() { for (Annotation annotation : annotations) { if ( annotation.annotationType().getName() - .equals("com.javadiscord.jdi.core.annotations.SlashCommand") + .equals(Constants.SLASH_COMMAND_ANNOTATION) ) { CommandBuilder builder = buildCommand(annotation); createInteractionRequests.add(builder); @@ -183,22 +175,18 @@ private CommandBuilder buildCommand(Annotation annotation) throws ReflectiveOper private void addCommandOption( CommandBuilder builder, Object option - ) throws ReflectiveOperationException { - Method optionNameMethod = option.getClass().getMethod("name"); - String optionName = (String) optionNameMethod.invoke(option); - - Method optionDescriptionMethod = option.getClass().getMethod("description"); - String optionDescription = (String) optionDescriptionMethod.invoke(option); + ) { - Method optionTypeMethod = option.getClass().getMethod("type"); - Enum optionType = (Enum) optionTypeMethod.invoke(option); - String optionTypeValue = optionType.name(); + ReflectiveCommandOption reflectiveCommandOption = + ReflectiveLoader.proxy(option, ReflectiveCommandOption.class); - Method optionRequiredMethod = option.getClass().getMethod("required"); - boolean optionRequired = (boolean) optionRequiredMethod.invoke(option); + String optionName = reflectiveCommandOption.name(); + String optionDescription = reflectiveCommandOption.description(); + String optionTypeValue = reflectiveCommandOption.type().name(); + boolean optionRequired = reflectiveCommandOption.required(); List choices = new ArrayList<>(); - Object[] choicesArray = (Object[]) option.getClass().getMethod("choices").invoke(option); + Object[] choicesArray = reflectiveCommandOption.choices(); for (Object choice : choicesArray) { addCommandOptionChoice(choices, choice); } @@ -209,24 +197,21 @@ private void addCommandOption( optionDescription, CommandOptionType.fromName(optionTypeValue), optionRequired - ).addChoice(choices) + ) + .addChoice(choices) ); } - private void addCommandOptionChoice( - List choices, - Object choice - ) throws ReflectiveOperationException { - Annotation annotation1 = (Annotation) choice; + private void addCommandOptionChoice(List choices, Object choice) { + Annotation annotation = (Annotation) choice; if ( - annotation1.annotationType().getName() - .equals(Constants.COMMAND_OPTION_CHOICE_ANNOTATION) + annotation.annotationType().getName().equals(Constants.COMMAND_OPTION_CHOICE_ANNOTATION) ) { - Method nameMethod1 = annotation1.annotationType().getMethod("name"); - Method valueMethod1 = annotation1.annotationType().getMethod("value"); - String name1 = (String) nameMethod1.invoke(annotation1); - String value1 = (String) valueMethod1.invoke(annotation1); - choices.add(new CommandOptionChoice(value1, name1)); + ReflectiveCommandOptionChoice commandOptionChoice = + ReflectiveLoader.proxy(annotation, ReflectiveCommandOptionChoice.class); + choices.add( + new CommandOptionChoice(commandOptionChoice.value(), commandOptionChoice.name()) + ); } } @@ -307,7 +292,6 @@ private void loadSlashCommands() { public void start() { started = true; - webSocketManager = new WebSocketManager(gatewaySetting, identifyRequest, cache); WebSocketManagerProxy webSocketManagerProxy = new WebSocketManagerProxy(webSocketManager); ConnectionDetails connectionDetails = diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOption.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOption.java new file mode 100644 index 00000000..5023e098 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOption.java @@ -0,0 +1,13 @@ +package com.javadiscord.jdi.internal; + +public interface ReflectiveCommandOption { + String name(); + + String description(); + + Enum type(); + + Object[] choices(); + + boolean required(); +} diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOptionChoice.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOptionChoice.java new file mode 100644 index 00000000..505c8f07 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveCommandOptionChoice.java @@ -0,0 +1,7 @@ +package com.javadiscord.jdi.internal; + +public interface ReflectiveCommandOptionChoice { + String name(); + + String value(); +} From 18709543c42711d91a7849683ec1746fa0f8b758 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 19:33:06 +0100 Subject: [PATCH 38/57] Rate limit --- .../java/com/javadiscord/jdi/RateLimit.java | 60 +++++++++++++++++++ .../api/DiscordRequestDispatcher.java | 24 ++++++++ .../jdi/internal/gateway/Gateway.java | 4 +- .../internal/gateway/SessionStartLimit.java | 4 +- .../gateway/WebSocketManagerProxy.java | 1 - 5 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/com/javadiscord/jdi/RateLimit.java diff --git a/api/src/main/java/com/javadiscord/jdi/RateLimit.java b/api/src/main/java/com/javadiscord/jdi/RateLimit.java new file mode 100644 index 00000000..11ac692a --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/RateLimit.java @@ -0,0 +1,60 @@ +package com.javadiscord.jdi; + +public class RateLimit { + private String bucket; + private int limit; + private int remaining; + private long reset; + private int resetAfter; + private boolean globalRateLimit; + + public RateLimit() {} + + public String getBucket() { + return bucket; + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getRemaining() { + return remaining; + } + + public void setRemaining(int remaining) { + this.remaining = remaining; + } + + public long getReset() { + return reset; + } + + public void setReset(long reset) { + this.reset = reset; + } + + public int getResetAfter() { + return resetAfter; + } + + public void setResetAfter(int resetAfter) { + this.resetAfter = resetAfter; + } + + public boolean isGlobalRateLimit() { + return globalRateLimit; + } + + public void setGlobalRateLimit(boolean globalRateLimit) { + this.globalRateLimit = globalRateLimit; + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java b/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java index 3677b791..86d8e622 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java @@ -2,6 +2,7 @@ import java.net.URI; import java.net.http.HttpClient; +import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Map; @@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; +import com.javadiscord.jdi.RateLimit; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -27,6 +30,8 @@ public class DiscordRequestDispatcher implements Runnable { private int numberOfRequestsSent; private long timeSinceLastRequest; + private RateLimit rateLimit = new RateLimit(); + public DiscordRequestDispatcher(String botToken) { this.botToken = botToken; this.httpClient = HttpClient.newBuilder().build(); @@ -51,6 +56,14 @@ public void run() { long currentTime = System.currentTimeMillis(); long elapsed = currentTime - timeSinceLastRequest; + if (rateLimit.getRemaining() == 0 && elapsed < rateLimit.getResetAfter()) { + try { + Thread.sleep(rateLimit.getResetAfter() - elapsed); + } catch (InterruptedException e) { + /* Ignore */ + } + } + if (elapsed < 1000 && numberOfRequestsSent >= 50) { try { Thread.sleep(1000 - elapsed); @@ -119,6 +132,17 @@ private void sendRequest(DiscordRequestBuilder discordRequestBuilder) { HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + HttpHeaders headers = response.headers(); + headers.firstValue("x-ratelimit-bucket").ifPresent(val -> rateLimit.setBucket(val)); + headers.firstValue("x-ratelimit-limit") + .ifPresent(val -> rateLimit.setLimit(Integer.parseInt(val))); + headers.firstValue("x-ratelimit-remaining") + .ifPresent(val -> rateLimit.setRemaining(Integer.parseInt(val))); + headers.firstValue("x-ratelimit-reset") + .ifPresent(val -> rateLimit.setReset(Long.parseLong(val))); + headers.firstValue("x-ratelimit-reset-after") + .ifPresent(val -> rateLimit.setResetAfter(Integer.parseInt(val))); + numberOfRequestsSent++; timeSinceLastRequest = System.currentTimeMillis(); diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/Gateway.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/Gateway.java index b1baad35..75493c22 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/Gateway.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/Gateway.java @@ -5,7 +5,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record Gateway( - String url, - int shards, + @JsonProperty("url") String url, + @JsonProperty("shards") int shards, @JsonProperty("session_start_limit") SessionStartLimit sessionStartLimit ) {} diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/SessionStartLimit.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/SessionStartLimit.java index 58f3459e..8b06d492 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/SessionStartLimit.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/SessionStartLimit.java @@ -5,8 +5,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionStartLimit( - int total, - int remaining, + @JsonProperty("total") int total, + @JsonProperty("remaining") int remaining, @JsonProperty("reset_after") long resetAfter, @JsonProperty("max_concurrency") int maxConcurrency ) {} diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManagerProxy.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManagerProxy.java index 7e0b0791..5a37fa61 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManagerProxy.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/WebSocketManagerProxy.java @@ -14,7 +14,6 @@ public void start(ConnectionMediator connectionMediator) { } public void restart(ConnectionMediator connectionMediator) { - webSocketManager.restart(connectionMediator); } From 376988758553fe85172b779349dc3c1d21bbd817 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 21:34:13 +0100 Subject: [PATCH 39/57] Fix Tests Cache Updater --- .../jdi/core/api/DiscordResponseParser.java | 9 ++- .../api/DiscordRequestDispatcher.java | 2 - .../jdi/{ => internal/api}/RateLimit.java | 2 +- .../jdi/internal/utils/CacheUpdater.java | 63 +++++++++++++++++++ .../helpers/LiveDiscordHelper.java | 2 +- .../java/com/javadiscord/jdi/core/Guild.java | 2 +- 6 files changed, 74 insertions(+), 6 deletions(-) rename api/src/main/java/com/javadiscord/jdi/{ => internal/api}/RateLimit.java (96%) create mode 100644 api/src/main/java/com/javadiscord/jdi/internal/utils/CacheUpdater.java diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java b/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java index d8b0c2b5..ed4fa7ae 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/DiscordResponseParser.java @@ -7,6 +7,8 @@ import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher; import com.javadiscord.jdi.internal.api.DiscordResponse; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; +import com.javadiscord.jdi.internal.cache.Cache; +import com.javadiscord.jdi.internal.utils.CacheUpdater; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -17,9 +19,11 @@ public class DiscordResponseParser { private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().addModule(new JavaTimeModule()).build(); private final DiscordRequestDispatcher dispatcher; + private final CacheUpdater cacheUpdater; - public DiscordResponseParser(DiscordRequestDispatcher dispatcher) { + public DiscordResponseParser(DiscordRequestDispatcher dispatcher, Cache cache) { this.dispatcher = dispatcher; + this.cacheUpdater = new CacheUpdater(cache); } public AsyncResponse> callAndParseList(Class clazz, DiscordRequest request) { @@ -31,6 +35,7 @@ public AsyncResponse> callAndParseList(Class clazz, DiscordReques try { List resultList = parseResponseFromList(clazz, response.body()); asyncResponse.setResult(resultList); + cacheUpdater.updateCache(resultList); } catch (Exception e) { asyncResponse.setException(e); } @@ -52,6 +57,7 @@ public AsyncResponse> callAndParseMap(String key, DiscordRequest req try { List resultList = parseResponseFromMap(key, response.body()); asyncResponse.setResult(resultList); + cacheUpdater.updateCache(resultList); } catch (Exception e) { asyncResponse.setException(e); } @@ -108,6 +114,7 @@ private void success( result = OBJECT_MAPPER.readValue(response.body(), type); } asyncResponse.setResult(result); + cacheUpdater.updateCache(result); } catch (JsonProcessingException e) { asyncResponse.setException(e); } diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java b/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java index 86d8e622..5deb942c 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java @@ -11,8 +11,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; -import com.javadiscord.jdi.RateLimit; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/api/src/main/java/com/javadiscord/jdi/RateLimit.java b/api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java similarity index 96% rename from api/src/main/java/com/javadiscord/jdi/RateLimit.java rename to api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java index 11ac692a..7a66f0f2 100644 --- a/api/src/main/java/com/javadiscord/jdi/RateLimit.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi; +package com.javadiscord.jdi.internal.api; public class RateLimit { private String bucket; diff --git a/api/src/main/java/com/javadiscord/jdi/internal/utils/CacheUpdater.java b/api/src/main/java/com/javadiscord/jdi/internal/utils/CacheUpdater.java new file mode 100644 index 00000000..6f2ac997 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/internal/utils/CacheUpdater.java @@ -0,0 +1,63 @@ +package com.javadiscord.jdi.internal.utils; + +import java.lang.reflect.Field; +import java.util.List; + +import com.javadiscord.jdi.internal.cache.Cache; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CacheUpdater { + + private final Cache cache; + + private static final Logger LOGGER = LogManager.getLogger(CacheUpdater.class); + + public CacheUpdater(Cache cache) { + this.cache = cache; + } + + public void updateCache(T result) { + if (result == null) { + return; + } + try { + Field guildIdField = result.getClass().getDeclaredField("guildId"); + Field idField = result.getClass().getDeclaredField("id"); + + long guildId = getLongFromField(guildIdField, result); + long id = getLongFromField(idField, result); + + if (cache.getCacheForGuild(guildId) == null) { + LOGGER.trace( + "Failed to cache result of type {} with guildId of {}", + result.getClass().getName(), guildId + ); + } else { + cache.getCacheForGuild(guildId).add(id, result); + } + + } catch (IllegalAccessException | NoSuchFieldException | NumberFormatException e) { + LOGGER.trace( + "Failed to cache result of type {}, cause: {}", + result.getClass().getName(), e.getMessage() + ); + } + } + + public void updateCache(List resultList) { + resultList.forEach(this::updateCache); + } + + private long getLongFromField( + Field field, + T result + ) throws IllegalAccessException, NumberFormatException { + field.setAccessible(true); + if (field.getType() == String.class) { + return Long.parseLong((String) field.get(result)); + } + return (long) field.get(result); + } +} diff --git a/api/src/test/integration/helpers/LiveDiscordHelper.java b/api/src/test/integration/helpers/LiveDiscordHelper.java index 18b9d281..34ca7d49 100644 --- a/api/src/test/integration/helpers/LiveDiscordHelper.java +++ b/api/src/test/integration/helpers/LiveDiscordHelper.java @@ -17,7 +17,7 @@ public class LiveDiscordHelper { private static final CountDownLatch STARTUP_LATCH = new CountDownLatch(1); private static Guild guild; - private static class TestListener implements EventListener { + public static class TestListener implements EventListener { @Override public void onGuildCreate(Guild guild) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Guild.java b/core/src/main/java/com/javadiscord/jdi/core/Guild.java index 1ce4d68f..ba1d3102 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Guild.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Guild.java @@ -34,7 +34,7 @@ public Guild(GuildModel guild, Cache cache, Discord discord) { long guildId = guild.id(); DiscordResponseParser discordResponseParser = - new DiscordResponseParser(discord.getDiscordRequestDispatcher()); + new DiscordResponseParser(discord.getDiscordRequestDispatcher(), cache); this.applicationRequest = new ApplicationRequest(discordResponseParser, guildId); this.applicationRoleConnectionMetaRequest = From 47f0ca355d3e21a7357565b50893c1c27bd39f51 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 21:42:33 +0100 Subject: [PATCH 40/57] Remove Qodana --- .github/workflows/qodana_code_quality.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/workflows/qodana_code_quality.yml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 7d7af59f..00000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - - slash-commands - -jobs: - qodana: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2024.1 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} \ No newline at end of file From e61be1d101da9dfeb42ed470d5332172131cb5bc Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 21:50:29 +0100 Subject: [PATCH 41/57] Busy-wait fix --- .../api/DiscordRequestDispatcher.java | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java b/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java index 5deb942c..e31d8df9 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/DiscordRequestDispatcher.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; @@ -25,11 +26,11 @@ public class DiscordRequestDispatcher implements Runnable { private final BlockingQueue queue; private final String botToken; private final AtomicBoolean running = new AtomicBoolean(false); + private final RateLimit rateLimit = new RateLimit(); + private int numberOfRequestsSent; private long timeSinceLastRequest; - private RateLimit rateLimit = new RateLimit(); - public DiscordRequestDispatcher(String botToken) { this.botToken = botToken; this.httpClient = HttpClient.newBuilder().build(); @@ -51,30 +52,24 @@ public void run() { LOGGER.info("Request dispatcher has started"); while (running.get()) { - long currentTime = System.currentTimeMillis(); - long elapsed = currentTime - timeSinceLastRequest; - - if (rateLimit.getRemaining() == 0 && elapsed < rateLimit.getResetAfter()) { - try { - Thread.sleep(rateLimit.getResetAfter() - elapsed); - } catch (InterruptedException e) { - /* Ignore */ + try { + long currentTime = System.currentTimeMillis(); + long elapsed = currentTime - timeSinceLastRequest; + + if (rateLimit.getRemaining() == 0 && elapsed < rateLimit.getResetAfter()) { + TimeUnit.MILLISECONDS.sleep(rateLimit.getResetAfter() - elapsed); + } - } - if (elapsed < 1000 && numberOfRequestsSent >= 50) { - try { - Thread.sleep(1000 - elapsed); - } catch (InterruptedException e) { - /* Ignore */ + if (elapsed < 1000 && numberOfRequestsSent >= 50) { + TimeUnit.MILLISECONDS.sleep(1000 - elapsed); + numberOfRequestsSent = 0; } - numberOfRequestsSent = 0; - } - try { sendRequest(queue.take()); } catch (InterruptedException e) { - /* Ignore */ + LOGGER.warn("Request dispatcher has interrupted"); + Thread.currentThread().interrupt(); } } @@ -131,7 +126,7 @@ private void sendRequest(DiscordRequestBuilder discordRequestBuilder) { httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); HttpHeaders headers = response.headers(); - headers.firstValue("x-ratelimit-bucket").ifPresent(val -> rateLimit.setBucket(val)); + headers.firstValue("x-ratelimit-bucket").ifPresent(rateLimit::setBucket); headers.firstValue("x-ratelimit-limit") .ifPresent(val -> rateLimit.setLimit(Integer.parseInt(val))); headers.firstValue("x-ratelimit-remaining") From b60f6ed03083ca30691cc9217f508379a3332bb7 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:01:10 +0100 Subject: [PATCH 42/57] Sonar fixes --- .../jdi/core/processor/loader/ComponentLoader.java | 9 +++++---- .../jdi/core/processor/loader/SlashCommandLoader.java | 2 +- .../com/javadiscord/jdi/core/api/EmojiRequestTest.java | 2 +- .../com/javadiscord/jdi/core/api/UserRequestTest.java | 1 + .../main/java/com/javadiscord/jdi/core/Constants.java | 1 + core/src/main/java/com/javadiscord/jdi/core/Discord.java | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java index e42abe15..0f4d4816 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java @@ -2,6 +2,7 @@ import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; @@ -20,8 +21,6 @@ public class ComponentLoader { private static final Map, Object> COMPONENTS = new HashMap<>(); private final ComponentValidator componentValidator = new ComponentValidator(); - public ComponentLoader() {} - public void loadComponents() { List classes = ClassFileUtil.getClassesInClassPath(); for (File classFile : classes) { @@ -37,7 +36,7 @@ private void processClassFile(File classFile) { } else { LOGGER.error("{} failed validation", clazz.getName()); } - } catch (Exception | Error ignore) { + } catch (Exception ignore) { // Ignore } } @@ -50,7 +49,9 @@ private void processClassMethods(Class clazz) throws Exception { } } - private void registerComponent(Method method) throws Exception { + private void registerComponent( + Method method + ) throws InvocationTargetException, IllegalAccessException { if (!COMPONENTS.containsKey(method.getReturnType())) { COMPONENTS.put(method.getReturnType(), method.invoke(null)); LOGGER.info("Loaded component {}", method.getReturnType().getName()); diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java index 8692ab99..73b3c42c 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java @@ -42,7 +42,7 @@ private void loadInteractionListeners() { } } } - } catch (Exception | Error ignore) { + } catch (Exception ignore) { /* Ignore */ } } diff --git a/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java b/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java index 854a3488..d0c73b68 100644 --- a/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java +++ b/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java @@ -53,7 +53,7 @@ void testCreateEmoji() throws InterruptedException, URISyntaxException, IOExcept latch.countDown(); }); - asyncResponse.onError(Assertions::fail); + asyncResponse.onError(e -> fail(e.getMessage())); assertTrue(latch.await(30, TimeUnit.SECONDS)); } diff --git a/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java b/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java index 29ee1e20..dfb40023 100644 --- a/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java +++ b/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java @@ -30,6 +30,7 @@ void delayBetweenTests() throws InterruptedException { } @Test + @Disabled void testGetCurrentUserGuildMember() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); AsyncResponse asyncResponse = guild.user().getCurrentUserGuildMember(); diff --git a/core/src/main/java/com/javadiscord/jdi/core/Constants.java b/core/src/main/java/com/javadiscord/jdi/core/Constants.java index adedf72d..ebde9c43 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Constants.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Constants.java @@ -15,6 +15,7 @@ public class Constants { "com.javadiscord.jdi.core.processor.loader.SlashCommandLoader"; public static final String LAUNCH_HEADER = """ + _ ____ ___ | | _ \\_ _| https://github.com/javadiscord/java-discord-api _ | | | | | | Open-Source Discord Framework diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index bf7a7f2b..c54ac07a 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -110,7 +110,7 @@ public Discord(String botToken, IdentifyRequest identifyRequest) { } public Discord(String botToken, IdentifyRequest identifyRequest, Cache cache) { - System.err.println(Constants.LAUNCH_HEADER); + LOGGER.info(Constants.LAUNCH_HEADER); this.botToken = botToken; this.discordRequestDispatcher = new DiscordRequestDispatcher(botToken); From d59cadc71c609239422a2c98b2ab0150737013a8 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:04:16 +0100 Subject: [PATCH 43/57] Sonar fixes --- .../core/processor/validator/SlashCommandValidator.java | 9 +++++---- .../java/com/javadiscord/jdi/internal/api/RateLimit.java | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java index a7942a34..c861da7f 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java @@ -31,10 +31,11 @@ public boolean validate(Method method) { for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP .entrySet()) { Class annotationClass = entry.getKey(); - if (method.isAnnotationPresent(annotationClass)) { - if (!validateMethodParameters(method, entry.getValue())) { - return false; - } + if ( + method.isAnnotationPresent(annotationClass) + && !validateMethodParameters(method, entry.getValue()) + ) { + return false; } } return true; diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java b/api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java index 7a66f0f2..84294c17 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/RateLimit.java @@ -8,8 +8,6 @@ public class RateLimit { private int resetAfter; private boolean globalRateLimit; - public RateLimit() {} - public String getBucket() { return bucket; } From 50a8c3c844a34d9ea470a4df3c05ca059e7f0428 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:04:48 +0100 Subject: [PATCH 44/57] Sonar fixes --- .../com/javadiscord/jdi/core/api/EmojiRequestTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java b/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java index d0c73b68..0a7b6483 100644 --- a/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java +++ b/api/src/test/integration/com/javadiscord/jdi/core/api/EmojiRequestTest.java @@ -6,6 +6,7 @@ import helpers.LiveDiscordHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -20,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*; +@Disabled class EmojiRequestTest { private static Guild guild; From 4f7968c00d895e256c992ec3138e97ec21c4e88d Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:12:14 +0100 Subject: [PATCH 45/57] Clean up --- .../main/java/com/javadiscord/bot/Main.java | 8 +- .../bot/commands/slash/JShellCommand.java | 145 ++++++++++-------- .../bot/listeners/SpamListener.java | 6 +- .../bot/listeners/SuggestionListener.java | 7 +- .../java/com/javadiscord/bot/utils/Tenor.java | 8 +- .../bot/utils/chatgpt/ChatGPT.java | 32 +--- .../utils/chatgpt/ChatGPTResponseParser.java | 14 +- .../bot/utils/jshell/JShellService.java | 1 + .../channel/ThreadGuildModelMemberTest.java | 2 +- 9 files changed, 100 insertions(+), 123 deletions(-) diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java index 618977c5..7d314111 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/Main.java @@ -26,21 +26,21 @@ public static JShellService jShellService() { return new JShellService(); } - private static DockerClient dockerClient = + private static final DockerClient DOCKER_CLIENT = DockerClientBuilder.getInstance("tcp://localhost:2375").build(); @Component public static DockerClient dockerClient() { - return dockerClient; + return DOCKER_CLIENT; } @Component public static DockerCommandRunner dockerCommandRunner() { - return new DockerCommandRunner(dockerClient); + return new DockerCommandRunner(DOCKER_CLIENT); } @Component public static DockerSessions dockerSessions() { - return new DockerSessions(dockerClient); + return new DockerSessions(DOCKER_CLIENT); } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java index aff56ce4..3fe7e4c6 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/JShellCommand.java @@ -33,91 +33,102 @@ public void handle(SlashCommandEvent event) { private void handleJShell(SlashCommandEvent event) { User user = event.user(); - long start = System.currentTimeMillis(); event.option("code").ifPresent(msg -> { JShellResponse response = jShellService.sendRequest(msg.valueAsString()); if (response == null) { - String reply = "Failed to execute the provided code, was it bad?"; - Embed embed = - new Embed.Builder() - .author(new EmbedAuthor(user.asMention(), user.avatar(), null, null)) - .description(reply) - .color(Color.ORANGE) - .build(); - event.reply(embed); + handleNullResponse(event, user); return; } if (response.error() != null && !response.error().isEmpty()) { - String reply = - """ - An error occurred while executing command: - - ```java - %s - ``` - - %s - """ - .formatted(msg.valueAsString(), response.error()); - Embed embed = - new Embed.Builder() - .author(new EmbedAuthor(user.asMention(), user.avatar(), null, null)) - .description(reply) - .color(Color.RED) - .build(); - event.reply(embed); + handleErrorResponse(event, user, msg.valueAsString(), response); return; } - StringBuilder sb = new StringBuilder(); - sb.append("## Snippets\n"); - for (JShellSnippet snippet : response.events()) { - sb.append("`"); - sb.append(snippet.statement()); - sb.append("`\n\n"); - sb.append("**Status**: "); - sb.append(snippet.status()); - sb.append("\n"); - - if (snippet.value() != null && !snippet.value().isEmpty()) { - sb.append("**Output**\n"); - sb.append("```java\n"); - sb.append(snippet.value()); - sb.append("```\n"); - } - } + handleSuccessResponse(event, user, response, start); + }); + } - if (!response.outputStream().isEmpty()) { - sb.append("## Console Output\n"); - sb.append("```java\n"); - sb.append(response.outputStream()); - sb.append("```\n"); - } + private void handleNullResponse(SlashCommandEvent event, User user) { + String reply = "Failed to execute the provided code, was it bad?"; + Embed embed = + new Embed.Builder() + .author(new EmbedAuthor(user.asMention(), user.avatar(), null, null)) + .description(reply) + .color(Color.ORANGE) + .build(); + event.reply(embed); + } + + private void handleErrorResponse( + SlashCommandEvent event, + User user, + String code, + JShellResponse response + ) { + String reply = + String.format( + """ + An error occurred while executing command: + + ```java + %s + ``` + + %s + """, code, response.error() + ); + Embed embed = + new Embed.Builder() + .author(new EmbedAuthor(user.asMention(), user.avatar(), null, null)) + .description(reply) + .color(Color.RED) + .build(); + event.reply(embed); + } - if (response.errorStream() != null && !response.errorStream().isEmpty()) { - sb.append("## Error Output\n"); - sb.append("```java\n"); - sb.append(response.errorStream()); - sb.append("```\n"); + private void handleSuccessResponse( + SlashCommandEvent event, + User user, + JShellResponse response, + long start + ) { + StringBuilder sb = new StringBuilder(); + sb.append("## Snippets\n"); + for (JShellSnippet snippet : response.events()) { + sb.append("`").append(snippet.statement()).append("`\n\n"); + sb.append("**Status**: ").append(snippet.status()).append("\n"); + + if (snippet.value() != null && !snippet.value().isEmpty()) { + sb.append("**Output**\n"); + sb.append("```java\n").append(snippet.value()).append("```\n"); } + } - Embed.Builder embed = - new Embed.Builder() - .author(new EmbedAuthor(user.asMention(), null, null, null)); + appendOutputStreams(sb, response); - if (sb.length() > 4000) { - embed.description(sb.substring(0, 4000)); - } else { - embed.description(sb.toString()); - } + Embed.Builder embed = + new Embed.Builder() + .author(new EmbedAuthor(user.asMention(), null, null, null)) + .description(sb.length() > 4000 ? sb.substring(0, 4000) : sb.toString()) + .color(Color.GREEN) + .footer("Time taken: " + (System.currentTimeMillis() - start) + "ms"); - embed.color(Color.GREEN); - embed.footer("Time taken: " + (System.currentTimeMillis() - start) + "ms"); - event.reply(embed.build()).onError(System.err::println); - }); + event.reply(embed.build()); + } + + private void appendOutputStreams(StringBuilder sb, JShellResponse response) { + if (!response.outputStream().isEmpty()) { + sb.append("## Console Output\n"); + sb.append("```java\n").append(response.outputStream()).append("```\n"); + } + + if (response.errorStream() != null && !response.errorStream().isEmpty()) { + sb.append("## Error Output\n"); + sb.append("```java\n").append(response.errorStream()).append("```\n"); + } } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java index 8958804d..3d052466 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SpamListener.java @@ -7,12 +7,8 @@ import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; import com.javadiscord.jdi.core.models.message.Message; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - @EventListener public class SpamListener { - private static final Logger LOGGER = LogManager.getLogger(SlashCommandListener.class); @MessageCreate public void onMessage(Message message, Guild guild) { @@ -33,7 +29,7 @@ public void onMessage(Message message, Guild guild) { """ .formatted(message.content()) ) - ).onError(System.err::println) + ) ); } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java index 8d7d9ceb..682441c1 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/SuggestionListener.java @@ -19,10 +19,6 @@ public void onMessage(Message message, Guild guild) { return; } - // guild.channel().createReaction(message.channelId(), message.id(), "thumbup"); - // guild.channel().createReaction(message.channelId(), message.id(), - // "thumbsdown"); - String title = message.content().length() > 60 ? message.content().substring(0, 60) @@ -34,8 +30,7 @@ public void onMessage(Message message, Guild guild) { message.id(), title ) - ).onError(System.err::println) - .onSuccess(System.out::println); + ); } } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java index c74c4e51..47499fcf 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java @@ -12,7 +12,7 @@ import org.apache.logging.log4j.Logger; public class Tenor { - private static final Logger logger = LogManager.getLogger(Tenor.class); + private static final Logger LOGGER = LogManager.getLogger(Tenor.class); private static final String API_KEY = System.getenv("TENOR_API_KEY"); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); @@ -38,10 +38,12 @@ public static JsonNode search(String searchTerm, int limit) { if (response.statusCode() == 200) { return OBJECT_MAPPER.readTree(response.body()); } else { - System.err.println("HTTP Code: " + response.statusCode() + " from " + url); + LOGGER.trace("HTTP Code: {} from {}", response.statusCode(), url); } + } catch (IOException | InterruptedException e) { - logger.error("Error making a request to Tenor", e); + LOGGER.error("Error making a request to Tenor", e); + Thread.currentThread().interrupt(); } return null; diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java index 3ee1fce6..ba801334 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPT.java @@ -14,37 +14,17 @@ import org.apache.logging.log4j.Logger; public class ChatGPT { - private static final Logger logger = LogManager.getLogger(ChatGPT.class); + private static final Logger LOGGER = LogManager.getLogger(ChatGPT.class); private static final String API_KEY = System.getenv("CHATGPT_API_KEY"); private static final Duration TIMEOUT = Duration.ofMinutes(3); private static final String AI_MODEL = "gpt-3.5-turbo"; - private final OpenAiService openAiService; - private static final int MAX_TOKENS = 2000; - - /** - * This parameter reduces the likelihood of the AI repeating itself. A higher - * frequency penalty makes the model less likely to repeat the same lines - * verbatim. It helps in generating more diverse and varied responses. - */ private static final double FREQUENCY_PENALTY = 0.5; - - /** - * This parameter controls the randomness of the AI's responses. A higher - * temperature results in more varied, unpredictable, and creative responses. - * Conversely, a lower temperature makes the model's responses more - * deterministic and conservative. - */ private static final double TEMPERATURE = 0.8; - - /** - * n: This parameter specifies the number of responses to generate for each - * prompt. If n is more than 1, the AI will generate multiple different - * responses to the same prompt, each one being a separate iteration based on - * the input. - */ private static final int MAX_NUMBER_OF_RESPONSES = 1; + private final OpenAiService openAiService; + public ChatGPT() { openAiService = new OpenAiService(API_KEY, TIMEOUT); @@ -92,9 +72,9 @@ public Optional ask(String question) { .getMessage() .getContent(); - return Optional.ofNullable(ChatGPTResponseParser.parse(response)); + return Optional.of(ChatGPTResponseParser.parse(response)); } catch (OpenAiHttpException openAiHttpException) { - logger.warn( + LOGGER.warn( String.format( "There was an error using the OpenAI API: %s Code: %s Type: %s Status" + " Code: %s", @@ -105,7 +85,7 @@ public Optional ask(String question) { ) ); } catch (RuntimeException e) { - logger.warn( + LOGGER.warn( "There was an error using the OpenAI API: {}", e.getMessage() ); } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java index 1095c559..165d2d66 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/chatgpt/ChatGPTResponseParser.java @@ -7,7 +7,7 @@ import org.apache.logging.log4j.Logger; public class ChatGPTResponseParser { - private static final Logger logger = LogManager.getLogger(ChatGPTResponseParser.class); + private static final Logger LOGGER = LogManager.getLogger(ChatGPTResponseParser.class); private static final int RESPONSE_LENGTH_LIMIT = 2000; private ChatGPTResponseParser() {} @@ -15,7 +15,7 @@ private ChatGPTResponseParser() {} public static String[] parse(String response) { String[] partedResponse = new String[] {response}; if (response.length() > RESPONSE_LENGTH_LIMIT) { - logger.debug("Response to parse:\n{}", response); + LOGGER.debug("Response to parse:\n{}", response); partedResponse = partitionAiResponse(response); } return partedResponse; @@ -29,9 +29,6 @@ private static String[] partitionAiResponse(String response) { List chunks = new ArrayList<>(); chunks.add(split); - // Check each chunk for correct length. If over the length, split in two and - // check - // again. while (!chunks.stream().allMatch(s -> s.length() < RESPONSE_LENGTH_LIMIT)) { for (int j = 0; j < chunks.size(); j++) { String chunk = chunks.get(j); @@ -43,22 +40,17 @@ private static String[] partitionAiResponse(String response) { } } - // Given the splitting on ```, the odd numbered entries need to have code marks - // restored. if (i % 2 != 0) { - // We assume that everything after the ``` on the same line is the language - // declaration. Could be empty. String lang = split.substring(0, split.indexOf(System.lineSeparator())); chunks = chunks.stream() .map(s -> ("```" + lang).concat(s).concat("```")) - // Handle case of doubling language declaration .map(s -> s.replaceFirst("```" + lang + lang, "```" + lang)) .toList(); } responseChunks.addAll(filterEmptyStrings(chunks)); - } // end of for loop. + } return responseChunks.toArray(new String[0]); } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java index 4e9a670d..822f3048 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java @@ -44,6 +44,7 @@ record Request(String code) {} LOGGER.error("Failed to parse data received from JShell API", e); } catch (IOException | InterruptedException e) { LOGGER.error("Failed to send request to JShell API", e); + Thread.currentThread().interrupt(); } return null; } diff --git a/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java b/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java index 01af50b6..e4e46d3b 100644 --- a/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java +++ b/models/src/test/unit/com/javadiscord/jdi/internal/channel/ThreadGuildModelMemberTest.java @@ -32,7 +32,7 @@ void testDecodingThreadMember() { ThreadMember threadMember = OBJECT_MAPPER.readValue(input, ThreadMember.class); assertEquals(1, threadMember.threadId()); assertEquals(10, threadMember.userId()); - assertEquals(OffsetDateTime.parse("2024-04-25T21:37:44Z"), threadMember.joinTime()); + assertEquals(OffsetDateTime.parse("2024-04-25T21:37:44Z").toString(), threadMember.joinTime()); assertEquals(0, threadMember.flags()); } catch (JsonProcessingException e) { fail(e.getMessage()); From 239b6865efdf638ba4be70c7cd10fa664d5155ac Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:13:42 +0100 Subject: [PATCH 46/57] Removed Error in catch --- core/src/main/java/com/javadiscord/jdi/core/Discord.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index c54ac07a..b8d9fdc0 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -242,7 +242,7 @@ private void loadComponents() { } else { throw new RuntimeException("Unable to create ComponentLoader instance"); } - } catch (Exception | Error e) { + } catch (Exception e) { LOGGER.warn("Component loading failed", e); } } @@ -261,7 +261,7 @@ private void loadAnnotations() { } } } - } catch (Exception | Error e) { + } catch (Exception e) { LOGGER.warn("Event listener loading failed", e); } } @@ -285,7 +285,7 @@ private void loadSlashCommands() { } } } - } catch (Exception | Error e) { + } catch (Exception e) { LOGGER.error("Failed to load SlashCommands", e); } } From 7e26ed50950c28ad45b350d4bfb62158f1249e22 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:15:41 +0100 Subject: [PATCH 47/57] Reduce Cognitive complexity --- .../processor/loader/ComponentLoader.java | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java index 0f4d4816..050047c6 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java @@ -52,11 +52,12 @@ private void processClassMethods(Class clazz) throws Exception { private void registerComponent( Method method ) throws InvocationTargetException, IllegalAccessException { - if (!COMPONENTS.containsKey(method.getReturnType())) { - COMPONENTS.put(method.getReturnType(), method.invoke(null)); - LOGGER.info("Loaded component {}", method.getReturnType().getName()); + Class returnType = method.getReturnType(); + if (!COMPONENTS.containsKey(returnType)) { + COMPONENTS.put(returnType, method.invoke(null)); + LOGGER.info("Loaded component {}", returnType.getName()); } else { - LOGGER.error("Component {} already loaded", method.getReturnType().getName()); + LOGGER.error("Component {} already loaded", returnType.getName()); } } @@ -78,27 +79,29 @@ private static void injectFields(Object component) { } private static void injectField(Object component, Field field) { - if (COMPONENTS.containsKey(field.getType())) { - Object dependency = COMPONENTS.get(field.getType()); - if (dependency != null) { - try { - field.setAccessible(true); - field.set(component, dependency); - LOGGER.info( - "Injected component {} into {}", - dependency.getClass().getName(), field.getType() - ); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "Failed to inject dependency into field: " + field.getName(), - e - ); - } - } + Class fieldType = field.getType(); + if (COMPONENTS.containsKey(fieldType)) { + injectDependency(component, field, COMPONENTS.get(fieldType)); } else { - LOGGER.error( - "No object {} was found in field {}", field.getType(), field.getName() - ); + LOGGER.error("No object {} was found in field {}", fieldType, field.getName()); + } + } + + private static void injectDependency(Object component, Field field, Object dependency) { + if (dependency != null) { + try { + field.setAccessible(true); + field.set(component, dependency); + LOGGER.info( + "Injected component {} into {}", dependency.getClass().getName(), + field.getType() + ); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Failed to inject dependency into field: " + field.getName(), + e + ); + } } } } From d4548dc31afd64161a26965beab16c3112c6cef6 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:35:56 +0100 Subject: [PATCH 48/57] Sonar --- .../processor/validator/EventListenerValidator.java | 9 +++++---- .../core/api/builders/command/CallbackMessage.java | 2 -- .../application_commands/CreateCommandRequest.java | 12 +++++++----- .../java/com/javadiscord/jdi/core/Constants.java | 4 ++++ .../java/com/javadiscord/bot/utils/CurseWords.java | 4 ++-- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java index 4153d35a..131d5bd0 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java @@ -416,10 +416,11 @@ private boolean validateMethodAnnotations(Method method) { for (Map.Entry, String[]> entry : EXPECTED_PARAM_TYPES_MAP .entrySet()) { Class annotationClass = entry.getKey(); - if (method.isAnnotationPresent(annotationClass)) { - if (!validateMethodParameters(method, entry.getValue())) { - return false; - } + if ( + method.isAnnotationPresent(annotationClass) + && !validateMethodParameters(method, entry.getValue()) + ) { + return false; } } return true; diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java index 171d2e8d..f7cfb49f 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/command/CallbackMessage.java @@ -35,8 +35,6 @@ public class CallbackMessage { @JsonProperty("poll") private Poll poll; - public CallbackMessage() {} - public boolean isTts() { return tts; } diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java index 38bd5b41..29cf3128 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/CreateCommandRequest.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import com.javadiscord.jdi.core.api.builders.command.CommandOption; import com.javadiscord.jdi.core.api.builders.command.CommandOptionType; @@ -23,11 +24,12 @@ public record CreateCommandRequest( @Override public DiscordRequestBuilder create() { - String path = "/applications/%s/commands".formatted(applicationId); + AtomicReference path = + new AtomicReference<>("/applications/%s/commands".formatted(applicationId)); - if (global.isPresent() && global.get()) { - path = "/applications/%s/guilds/%s/commands".formatted(applicationId, guildId); - } + global.ifPresent( + val -> path.set("/applications/%s/guilds/%s/commands".formatted(applicationId, guildId)) + ); Map body = new HashMap<>(); body.put("name", name); @@ -41,6 +43,6 @@ public DiscordRequestBuilder create() { return new DiscordRequestBuilder() .post() .body(body) - .path(path); + .path(path.get()); } } diff --git a/core/src/main/java/com/javadiscord/jdi/core/Constants.java b/core/src/main/java/com/javadiscord/jdi/core/Constants.java index ebde9c43..8cd9b51b 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Constants.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Constants.java @@ -23,4 +23,8 @@ public class Constants { \\___/|____/___| Version 1.0 """; + private Constants() { + throw new UnsupportedOperationException("Utility class"); + } + } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java index 147f7035..918a34ed 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java @@ -5,7 +5,7 @@ public class CurseWords { public static boolean containsCurseWord(String text) { - for (String s : curseWords) { + for (String s : CURSE_WORDS) { if (text.toLowerCase().contains(s.toLowerCase())) { return true; } @@ -13,7 +13,7 @@ public static boolean containsCurseWord(String text) { return false; } - private static final List curseWords = + private static final List CURSE_WORDS = Arrays.asList( "fuck", "shit", From c038ef7cefa87536d6f741c13972338cae9ffeb9 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 22:52:13 +0100 Subject: [PATCH 49/57] Sonar --- .../javadiscord/jdi/core/GatewayEventListener.java | 4 ++-- .../core/interaction/InteractionEventHandler.java | 13 ++++++------- .../java/com/javadiscord/bot/utils/Executor.java | 4 ++++ .../javadiscord/bot/utils/jshell/JShellService.java | 2 -- .../interaction/InteractionCreateHandler.java | 7 ++++++- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java index 0299bc56..34b545b6 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListener.java @@ -38,9 +38,9 @@ public GatewayEventListener(Discord discord) { } public static Guild getGuild(Discord discord, Object event) { - if (event instanceof GuildModel) { + if (event instanceof GuildModel guildModel) { return createGuildFromEvent( - discord, (GuildModel) event + discord, guildModel ); } else { return createGuildFromEventObject(discord, event); diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 1de7dc3b..7813350e 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -49,6 +49,11 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { if (validateParameterCount(method, paramOrder)) { invokeHandler(handler, method, paramOrder); + } else { + throw new RuntimeException( + "Bound " + paramOrder.size() + " parameters but expected " + + method.getParameterCount() + ); } } catch (Exception e) { @@ -76,13 +81,7 @@ private List getOrderOfParameters(Method method, Interaction interaction } private boolean validateParameterCount(Method method, List paramOrder) { - if (paramOrder.size() != method.getParameterCount()) { - throw new RuntimeException( - "Bound " + paramOrder.size() + " parameters but expected " - + method.getParameterCount() - ); - } - return true; + return paramOrder.size() == method.getParameterCount(); } private void invokeHandler( diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java index a8c06316..da8b8b2c 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Executor.java @@ -8,6 +8,10 @@ public class Executor { private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); + private Executor() { + throw new UnsupportedOperationException("Utility class"); + } + public static void execute(Runnable runnable) { EXECUTOR_SERVICE.submit(runnable); } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java index 822f3048..dc1d0474 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/jshell/JShellService.java @@ -22,8 +22,6 @@ public class JShellService { private final Map> history = new HashMap<>(); - public JShellService() {} - public JShellResponse sendRequest(String code) { record Request(String code) {} try (HttpClient client = HttpClient.newHttpClient()) { diff --git a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java index aba3b41e..d9a8aa1f 100644 --- a/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java +++ b/gateway/src/main/java/com/javadiscord/jdi/internal/gateway/handlers/events/codec/handlers/interaction/InteractionCreateHandler.java @@ -8,5 +8,10 @@ public class InteractionCreateHandler implements EventHandler { @Override - public void handle(Interaction event, ConnectionMediator connectionMediator, Cache cache) {} + public void handle(Interaction event, ConnectionMediator connectionMediator, Cache cache) { + /* + * Empty because we do not need to do anything with interaction events at the + * gateway level + */ + } } From 4e96c0883a6a8c6ebfb8c239e4b14ca8efe60503 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 23:00:32 +0100 Subject: [PATCH 50/57] Delete command request --- .../DeleteCommandRequest.java | 23 +++++++++----- .../com/javadiscord/jdi/core/Discord.java | 30 +++++++++++++++++-- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java index a25bbdf4..95e52773 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/application_commands/DeleteCommandRequest.java @@ -1,20 +1,29 @@ package com.javadiscord.jdi.internal.api.application_commands; -import java.util.Optional; - import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; public record DeleteCommandRequest( long applicationId, - Optional guildId, - long commandId + long guildId, + long commandId, + boolean global ) implements DiscordRequest { @Override public DiscordRequestBuilder create() { - // TODO: Implement - // https://discord.com/developers/docs/interactions/application-commands#updating-and-deleting-a-command - return null; + String path; + + if (global) { + path = "applications/%s/commands/%s".formatted(applicationId, commandId); + } else { + path = + "applications/%s/guilds/%s/commands/%s" + .formatted(applicationId, guildId, commandId); + } + + return new DiscordRequestBuilder() + .delete() + .path(path); } } diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index b8d9fdc0..21d74a63 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -27,6 +27,7 @@ import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher; import com.javadiscord.jdi.internal.api.DiscordResponseFuture; import com.javadiscord.jdi.internal.api.application_commands.CreateCommandRequest; +import com.javadiscord.jdi.internal.api.application_commands.DeleteCommandRequest; import com.javadiscord.jdi.internal.cache.Cache; import com.javadiscord.jdi.internal.cache.CacheType; import com.javadiscord.jdi.internal.gateway.*; @@ -60,6 +61,7 @@ public class Discord { private final Map loadedSlashCommands = new HashMap<>(); private final List eventListeners = new ArrayList<>(); private final List createInteractionRequests = new ArrayList<>(); + private final List deleteInteractionRequests = new ArrayList<>(); private WebSocketManager webSocketManager; private long applicationId; @@ -381,8 +383,9 @@ public void registerSlashCommand(CommandBuilder builder) { createInteractionRequests.add(builder); } - public void deleteSlashCommand(long id) { - // Implement command deletion logic + public void deleteSlashCommand(long commandId, long guildId, boolean global) { + deleteInteractionRequests + .add(new DeleteCommandRequest(applicationId, guildId, commandId, global)); } public DiscordRequestDispatcher getDiscordRequestDispatcher() { @@ -420,7 +423,30 @@ void handleReadyEvent(ReadyEvent event) { handleCommandRegistrationResponse(request, future); } + for (DeleteCommandRequest request : deleteInteractionRequests) { + DiscordResponseFuture future = sendRequest(request); + handleDeleteResponse(request, future); + } + createInteractionRequests.clear(); + deleteInteractionRequests.clear(); + } + + private void handleDeleteResponse(DeleteCommandRequest request, DiscordResponseFuture future) { + future.onSuccess(res -> { + if (res.status() >= 200 && res.status() < 300) { + LOGGER.info("Deleted slash command {} with discord", request.commandId()); + } else { + LOGGER.error( + "Failed to delete slash command {} with discord\n{}", request.commandId(), + res.body() + ); + } + }); + future.onError( + err -> LOGGER + .error("Failed to delete slash command {} with discord", request.commandId(), err) + ); } private void handleCommandRegistrationResponse( From d5f640470d65471da4c8e4be7a16f6bc9021fa87 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 23:05:51 +0100 Subject: [PATCH 51/57] Sonar --- .../javadiscord/bot/listeners/RolePlayMessageListener.java | 2 +- .../src/main/java/com/javadiscord/bot/utils/CurseWords.java | 4 ++++ .../src/main/java/com/javadiscord/bot/utils/Tenor.java | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java index 44a2955c..49632f8e 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/listeners/RolePlayMessageListener.java @@ -41,7 +41,7 @@ public void handleRolePlay(Message message, Guild guild) { return; } - String searchTerm = action.replaceAll(" ", "%20") + "ing%20anime"; + String searchTerm = action.replace(" ", "%20") + "ing%20anime"; JsonNode json = Tenor.search(searchTerm, 50); if (json != null && json.has("results")) { diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java index 918a34ed..27c7c0de 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/CurseWords.java @@ -4,6 +4,10 @@ import java.util.List; public class CurseWords { + private CurseWords() { + throw new UnsupportedOperationException("Utility class"); + } + public static boolean containsCurseWord(String text) { for (String s : CURSE_WORDS) { if (text.toLowerCase().contains(s.toLowerCase())) { diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java index 47499fcf..c5b035b5 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/utils/Tenor.java @@ -17,6 +17,10 @@ public class Tenor { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + private Tenor() { + throw new UnsupportedOperationException("Utility class"); + } + public static JsonNode search(String searchTerm, int limit) { final String url = String.format( From 7e1161c5c0d2363694bd3d02ccacc7cf4e0fcb32 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Wed, 29 May 2024 23:14:21 +0100 Subject: [PATCH 52/57] Sonar --- .../java/com/javadiscord/jdi/internal/ReflectiveLoader.java | 4 ++++ .../javadiscord/bot/commands/text/TextCommandRepository.java | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java index f2fc3000..d2ffcb54 100644 --- a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveLoader.java @@ -4,6 +4,10 @@ import java.lang.reflect.Proxy; public class ReflectiveLoader { + private ReflectiveLoader() { + throw new UnsupportedOperationException("Utility class"); + } + public static T proxy(Object object, Class interfaceClass) { InvocationHandler handler = (proxy, method, methodArgs) -> object.getClass() diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java index ee308d7e..ff313fbc 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/text/TextCommandRepository.java @@ -7,7 +7,6 @@ public class TextCommandRepository { private static final Map COMMANDS = new HashMap<>(); - static { COMMANDS.put("clear", new ClearChannelCommand()); COMMANDS.put("mute", new MuteCommand()); @@ -16,6 +15,10 @@ public class TextCommandRepository { COMMANDS.put("embed", new SayEmbedCommand()); } + private TextCommandRepository() { + throw new UnsupportedOperationException("Utility class"); + } + public static TextCommand get(String key) { return COMMANDS.get(key); } From 3d340bd2f1345e12f1acb2e8b38be10472aaf29c Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Thu, 30 May 2024 10:51:16 +0100 Subject: [PATCH 53/57] Sonar --- .../jdi/core/processor/loader/ComponentLoader.java | 11 ++++++++--- .../jdi/core/GatewayEventListenerAnnotations.java | 7 +++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java index 050047c6..14abe04d 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java @@ -41,7 +41,7 @@ private void processClassFile(File classFile) { } } - private void processClassMethods(Class clazz) throws Exception { + private void processClassMethods(Class clazz) { for (Method method : clazz.getMethods()) { if (method.isAnnotationPresent(Component.class)) { registerComponent(method); @@ -51,10 +51,15 @@ private void processClassMethods(Class clazz) throws Exception { private void registerComponent( Method method - ) throws InvocationTargetException, IllegalAccessException { + ) { Class returnType = method.getReturnType(); if (!COMPONENTS.containsKey(returnType)) { - COMPONENTS.put(returnType, method.invoke(null)); + try { + COMPONENTS.put(returnType, method.invoke(null)); + } catch (IllegalAccessException | InvocationTargetException e) { + LOGGER.error("Failed to register component {}", method.getName(), e); + return; + } LOGGER.info("Loaded component {}", returnType.getName()); } else { LOGGER.error("Component {} already loaded", returnType.getName()); diff --git a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java index b58e7ccf..4db68b10 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java +++ b/core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java @@ -255,12 +255,11 @@ private void invokeAnnotatedMethods(Class annotationClass, private void invokeMethod(Object listener, Method method, Object event) { List paramOrder = getParamOrder(method, event); if (paramOrder.size() != method.getParameterCount()) { - throw new RuntimeException( - "Bound " + paramOrder.size() + " parameters but expected " - + method.getParameterCount() + LOGGER.error( + "Bound {} parameters but expected {}", paramOrder.size(), method.getParameterCount() ); + return; } - try { LOGGER.trace("Invoking method {} with params {}", method.getName(), paramOrder); method.invoke(listener, paramOrder.toArray()); From d24e1839369725fb1f9515ea9a125d27c26b0763 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Thu, 30 May 2024 13:27:57 +0100 Subject: [PATCH 54/57] Added custom exceptions (Sonar) --- .../exceptions/ComponentInjectionException.java | 13 +++++++++++++ .../exceptions/NoZeroArgConstructorException.java | 13 +++++++++++++ .../internal/exceptions/ValidationException.java | 12 ++++++++++++ .../{core => internal}/processor/ClassFileUtil.java | 6 ++++-- .../processor/SlashCommandClassMethod.java | 2 +- .../processor/loader/ComponentLoader.java | 13 +++++++------ .../processor/loader/ListenerLoader.java | 11 +++++++---- .../processor/loader/SlashCommandLoader.java | 11 ++++++----- .../processor/validator/ComponentValidator.java | 2 +- .../processor/validator/EventListenerValidator.java | 2 +- .../processor/validator/SlashCommandValidator.java | 2 +- .../jdi/core/processor/ClassFileUtilTest.java | 1 + .../core/processor/EventListenerValidatorTest.java | 2 +- .../java/com/javadiscord/jdi/core/Constants.java | 6 +++--- .../main/java/com/javadiscord/jdi/core/Discord.java | 10 ++++++---- .../core/interaction/InteractionEventHandler.java | 3 ++- .../jdi/internal/exceptions/GatewayException.java | 12 ++++++++++++ .../internal/exceptions/InstantiationException.java | 13 +++++++++++++ .../exceptions/InvalidBotTokenException.java | 12 ++++++++++++ 19 files changed, 116 insertions(+), 30 deletions(-) create mode 100644 annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ComponentInjectionException.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/NoZeroArgConstructorException.java create mode 100644 annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ValidationException.java rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/ClassFileUtil.java (96%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/SlashCommandClassMethod.java (69%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/loader/ComponentLoader.java (90%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/loader/ListenerLoader.java (85%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/loader/SlashCommandLoader.java (87%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/validator/ComponentValidator.java (94%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/validator/EventListenerValidator.java (99%) rename annotations/src/main/java/com/javadiscord/jdi/{core => internal}/processor/validator/SlashCommandValidator.java (97%) create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/exceptions/GatewayException.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java create mode 100644 core/src/main/java/com/javadiscord/jdi/internal/exceptions/InvalidBotTokenException.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ComponentInjectionException.java b/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ComponentInjectionException.java new file mode 100644 index 00000000..42e2407b --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ComponentInjectionException.java @@ -0,0 +1,13 @@ +package com.javadiscord.jdi.internal.exceptions; + +public class ComponentInjectionException extends RuntimeException { + + public ComponentInjectionException() { + super(); + } + + public ComponentInjectionException(String message) { + super(message); + } + +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/NoZeroArgConstructorException.java b/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/NoZeroArgConstructorException.java new file mode 100644 index 00000000..15b71d21 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/NoZeroArgConstructorException.java @@ -0,0 +1,13 @@ +package com.javadiscord.jdi.internal.exceptions; + +public class NoZeroArgConstructorException extends RuntimeException { + + public NoZeroArgConstructorException() { + super(); + } + + public NoZeroArgConstructorException(String message) { + super(message); + } + +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ValidationException.java b/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ValidationException.java new file mode 100644 index 00000000..2e610070 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/exceptions/ValidationException.java @@ -0,0 +1,12 @@ +package com.javadiscord.jdi.internal.exceptions; + +public class ValidationException extends RuntimeException { + + public ValidationException() { + super(); + } + + public ValidationException(String message) { + super(message); + } +} diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java similarity index 96% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java index dbeae656..7ef18630 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/ClassFileUtil.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor; +package com.javadiscord.jdi.internal.processor; import java.io.DataInputStream; import java.io.File; @@ -15,7 +15,9 @@ public class ClassFileUtil { private static final List classesInPath = new ArrayList<>(); private static boolean loadedParentJar = false; - private ClassFileUtil() {} + private ClassFileUtil() { + throw new UnsupportedOperationException("Utility class"); + } public static List getClassesInClassPath() { if (classesInPath.isEmpty()) { diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java similarity index 69% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java index 43bddf95..02a8cb5f 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/SlashCommandClassMethod.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor; +package com.javadiscord.jdi.internal.processor; import java.lang.reflect.Method; diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java similarity index 90% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java index 14abe04d..2ad035c3 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor.loader; +package com.javadiscord.jdi.internal.processor.loader; import java.io.File; import java.lang.reflect.Field; @@ -10,8 +10,9 @@ import com.javadiscord.jdi.core.annotations.Component; import com.javadiscord.jdi.core.annotations.Inject; -import com.javadiscord.jdi.core.processor.ClassFileUtil; -import com.javadiscord.jdi.core.processor.validator.ComponentValidator; +import com.javadiscord.jdi.internal.exceptions.ComponentInjectionException; +import com.javadiscord.jdi.internal.processor.ClassFileUtil; +import com.javadiscord.jdi.internal.processor.validator.ComponentValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -102,9 +103,9 @@ private static void injectDependency(Object component, Field field, Object depen field.getType() ); } catch (IllegalAccessException e) { - throw new RuntimeException( - "Failed to inject dependency into field: " + field.getName(), - e + throw new ComponentInjectionException( + "Failed to inject dependency into field: " + field.getName() + ", " + + e.getMessage() ); } } diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ListenerLoader.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java similarity index 85% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ListenerLoader.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java index de4b6a5b..2f0a5444 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ListenerLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java @@ -1,12 +1,13 @@ -package com.javadiscord.jdi.core.processor.loader; +package com.javadiscord.jdi.internal.processor.loader; import java.io.File; import java.lang.reflect.Constructor; import java.util.List; import com.javadiscord.jdi.core.annotations.EventListener; -import com.javadiscord.jdi.core.processor.ClassFileUtil; -import com.javadiscord.jdi.core.processor.validator.EventListenerValidator; +import com.javadiscord.jdi.internal.exceptions.NoZeroArgConstructorException; +import com.javadiscord.jdi.internal.processor.ClassFileUtil; +import com.javadiscord.jdi.internal.processor.validator.EventListenerValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -65,6 +66,8 @@ public static Constructor getZeroArgConstructor(Class clazz) { return constructor; } } - throw new RuntimeException("No zero arg constructor found for " + clazz.getName()); + throw new NoZeroArgConstructorException( + "No zero arg constructor found for " + clazz.getName() + ); } } diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java similarity index 87% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java index 73b3c42c..128bd85c 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor.loader; +package com.javadiscord.jdi.internal.processor.loader; import java.io.File; import java.lang.reflect.Constructor; @@ -7,9 +7,10 @@ import java.util.Map; import com.javadiscord.jdi.core.annotations.SlashCommand; -import com.javadiscord.jdi.core.processor.ClassFileUtil; -import com.javadiscord.jdi.core.processor.SlashCommandClassMethod; -import com.javadiscord.jdi.core.processor.validator.SlashCommandValidator; +import com.javadiscord.jdi.internal.exceptions.ValidationException; +import com.javadiscord.jdi.internal.processor.ClassFileUtil; +import com.javadiscord.jdi.internal.processor.SlashCommandClassMethod; +import com.javadiscord.jdi.internal.processor.validator.SlashCommandValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -38,7 +39,7 @@ private void loadInteractionListeners() { clazz, method, method.getAnnotation(SlashCommand.class).name() ); } else { - throw new RuntimeException(method.getName() + " failed validation"); + throw new ValidationException(method.getName() + " failed validation"); } } } diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/ComponentValidator.java similarity index 94% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/ComponentValidator.java index b6d47c94..f8eefbc1 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/ComponentValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/ComponentValidator.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor.validator; +package com.javadiscord.jdi.internal.processor.validator; import java.lang.reflect.Method; import java.lang.reflect.Modifier; diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java similarity index 99% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java index 131d5bd0..1cdc3766 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/EventListenerValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor.validator; +package com.javadiscord.jdi.internal.processor.validator; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/SlashCommandValidator.java similarity index 97% rename from annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java rename to annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/SlashCommandValidator.java index c861da7f..38bc915c 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/processor/validator/SlashCommandValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/SlashCommandValidator.java @@ -1,4 +1,4 @@ -package com.javadiscord.jdi.core.processor.validator; +package com.javadiscord.jdi.internal.processor.validator; import java.lang.annotation.Annotation; import java.lang.reflect.Method; diff --git a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/ClassFileUtilTest.java b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/ClassFileUtilTest.java index 989b0587..d6303580 100644 --- a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/ClassFileUtilTest.java +++ b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/ClassFileUtilTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; +import com.javadiscord.jdi.internal.processor.ClassFileUtil; import org.junit.jupiter.api.Test; import java.io.File; diff --git a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java index bdf9ae51..4eeb6cfb 100644 --- a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java +++ b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java @@ -10,7 +10,7 @@ import com.javadiscord.jdi.core.models.channel.Channel; import com.javadiscord.jdi.core.models.message.Message; -import com.javadiscord.jdi.core.processor.validator.EventListenerValidator; +import com.javadiscord.jdi.internal.processor.validator.EventListenerValidator; import org.junit.jupiter.api.Test; class EventListenerValidatorTest { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Constants.java b/core/src/main/java/com/javadiscord/jdi/core/Constants.java index 8cd9b51b..8fc84753 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Constants.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Constants.java @@ -8,11 +8,11 @@ public class Constants { "com.javadiscord.jdi.core.annotations.SlashCommand"; public static final String LISTENER_LOADER_CLASS = - "com.javadiscord.jdi.core.processor.loader.ListenerLoader"; + "com.javadiscord.jdi.internal.processor.loader.ListenerLoader"; public static final String COMPONENT_LOADER_CLASS = - "com.javadiscord.jdi.core.processor.loader.ComponentLoader"; + "com.javadiscord.jdi.internal.processor.loader.ComponentLoader"; public static final String SLASH_COMMAND_LOADER_CLASS = - "com.javadiscord.jdi.core.processor.loader.SlashCommandLoader"; + "com.javadiscord.jdi.internal.processor.loader.SlashCommandLoader"; public static final String LAUNCH_HEADER = """ diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 21d74a63..1640ca7e 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -30,6 +30,8 @@ import com.javadiscord.jdi.internal.api.application_commands.DeleteCommandRequest; import com.javadiscord.jdi.internal.cache.Cache; import com.javadiscord.jdi.internal.cache.CacheType; +import com.javadiscord.jdi.internal.exceptions.GatewayException; +import com.javadiscord.jdi.internal.exceptions.InvalidBotTokenException; import com.javadiscord.jdi.internal.gateway.*; import com.javadiscord.jdi.internal.gateway.identify.IdentifyRequest; @@ -242,7 +244,7 @@ private void loadComponents() { if (componentLoader != null) { componentLoader.loadComponents(); } else { - throw new RuntimeException("Unable to create ComponentLoader instance"); + throw new InstantiationException("Unable to create ComponentLoader instance"); } } catch (Exception e) { LOGGER.warn("Component loading failed", e); @@ -357,15 +359,15 @@ private static Gateway getGatewayURL(String authentication) { httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 401) { - throw new RuntimeException("Invalid bot token provided"); + throw new InvalidBotTokenException("Invalid bot token provided"); } if (response.statusCode() != 200) { - throw new RuntimeException("Unexpected error occurred: " + response.body()); + throw new GatewayException("Unexpected error occurred: " + response.body()); } return OBJECT_MAPPER.readValue(response.body(), Gateway.class); } catch (Exception e) { LOGGER.error("Failed to fetch the gateway URL from discord", e); - throw new RuntimeException(e); + throw new GatewayException(e.getMessage()); } } diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 7813350e..1e10215e 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -12,6 +12,7 @@ import com.javadiscord.jdi.internal.ReflectiveLoader; import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod; import com.javadiscord.jdi.internal.ReflectiveSlashCommandLoader; +import com.javadiscord.jdi.internal.exceptions.InstantiationException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -50,7 +51,7 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { if (validateParameterCount(method, paramOrder)) { invokeHandler(handler, method, paramOrder); } else { - throw new RuntimeException( + throw new InstantiationException( "Bound " + paramOrder.size() + " parameters but expected " + method.getParameterCount() ); diff --git a/core/src/main/java/com/javadiscord/jdi/internal/exceptions/GatewayException.java b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/GatewayException.java new file mode 100644 index 00000000..057dc420 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/GatewayException.java @@ -0,0 +1,12 @@ +package com.javadiscord.jdi.internal.exceptions; + +public class GatewayException extends RuntimeException { + + public GatewayException() { + super(); + } + + public GatewayException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java new file mode 100644 index 00000000..82913459 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java @@ -0,0 +1,13 @@ +package com.javadiscord.jdi.internal.exceptions; + +public class InstantiationException extends RuntimeException { + + public InstantiationException() { + super(); + } + + public InstantiationException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InvalidBotTokenException.java b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InvalidBotTokenException.java new file mode 100644 index 00000000..b2ca5956 --- /dev/null +++ b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InvalidBotTokenException.java @@ -0,0 +1,12 @@ +package com.javadiscord.jdi.internal.exceptions; + +public class InvalidBotTokenException extends RuntimeException { + + public InvalidBotTokenException() { + super(); + } + + public InvalidBotTokenException(String message) { + super(message); + } +} From 66ca27a0a12d75342a839cd220e90573acbe2f45 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Thu, 30 May 2024 13:36:38 +0100 Subject: [PATCH 55/57] Sonar --- .../interaction/InteractionEventHandler.java | 25 ++++++++++++------- .../exceptions/InstantiationException.java | 13 ---------- 2 files changed, 16 insertions(+), 22 deletions(-) delete mode 100644 core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 1e10215e..70ba19db 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -1,5 +1,6 @@ package com.javadiscord.jdi.core.interaction; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; @@ -12,7 +13,6 @@ import com.javadiscord.jdi.internal.ReflectiveLoader; import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod; import com.javadiscord.jdi.internal.ReflectiveSlashCommandLoader; -import com.javadiscord.jdi.internal.exceptions.InstantiationException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -89,14 +89,21 @@ private void invokeHandler( Class handler, Method method, List paramOrder - ) throws Exception { - if (cachedInstances.containsKey(handler.getName())) { - method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); - } else { - Object handlerInstance = handler.getDeclaredConstructor().newInstance(); - cachedInstances.put(handler.getName(), handlerInstance); - injectComponents(handlerInstance); - method.invoke(handlerInstance, paramOrder.toArray()); + ) throws InstantiationException { + try { + if (cachedInstances.containsKey(handler.getName())) { + method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); + } else { + Object handlerInstance = handler.getDeclaredConstructor().newInstance(); + cachedInstances.put(handler.getName(), handlerInstance); + injectComponents(handlerInstance); + method.invoke(handlerInstance, paramOrder.toArray()); + } + } catch ( + InvocationTargetException | IllegalAccessException | NoSuchMethodException + | InstantiationException e + ) { + throw new InstantiationException(e.getLocalizedMessage()); } } diff --git a/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java b/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java deleted file mode 100644 index 82913459..00000000 --- a/core/src/main/java/com/javadiscord/jdi/internal/exceptions/InstantiationException.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.javadiscord.jdi.internal.exceptions; - -public class InstantiationException extends RuntimeException { - - public InstantiationException() { - super(); - } - - public InstantiationException(String message) { - super(message); - } - -} From dcf8c96e1e0544022db7c3de8c3e259b167a1763 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Thu, 30 May 2024 13:40:10 +0100 Subject: [PATCH 56/57] Sonar --- .../jdi/internal/api/channel/CreateMessageRequest.java | 4 ++-- .../jdi/internal/api/sticker/CreateGuildStickerRequest.java | 2 ++ .../jdi/internal/api/webhook/EditWebhookMessageRequest.java | 4 +++- .../jdi/internal/api/webhook/ExecuteWebhookRequest.java | 4 +++- .../com/javadiscord/jdi/core/api/UserRequestTest.java | 5 ----- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java index db9ac121..35a412b1 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java @@ -72,8 +72,8 @@ public DiscordRequestBuilder create() { default -> multiPartBody.filePart(name, path, MediaType.ANY); } - } catch (FileNotFoundException e) { - throw new RuntimeException(e); + } catch (FileNotFoundException ignore) { + /* Ignore */ } } diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/sticker/CreateGuildStickerRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/sticker/CreateGuildStickerRequest.java index cd9cd8a3..70e3f2b3 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/sticker/CreateGuildStickerRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/sticker/CreateGuildStickerRequest.java @@ -34,6 +34,8 @@ public DiscordRequestBuilder create() { case "png" -> body.filePart("file", filePath, MediaType.IMAGE_PNG); case "jpg", "jpeg" -> body.filePart("file", filePath, MediaType.IMAGE_JPEG); case "gif" -> body.filePart("file", filePath, MediaType.IMAGE_GIF); + default -> + throw new IllegalArgumentException("Unsupported extension: " + extension); } return new DiscordRequestBuilder() diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/EditWebhookMessageRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/EditWebhookMessageRequest.java index 97b91948..dffbc90d 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/EditWebhookMessageRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/EditWebhookMessageRequest.java @@ -47,7 +47,9 @@ public DiscordRequestBuilder create() { for (int i = 0; i < paths.size(); i++) { try { bodyBuilder.filePart("file[%d]".formatted(i), paths.get(i)); - } catch (FileNotFoundException ignored) {} + } catch (FileNotFoundException ignored) { + /* Ignore */ + } } } ); diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/ExecuteWebhookRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/ExecuteWebhookRequest.java index 72f541b6..9ac17dd2 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/ExecuteWebhookRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/webhook/ExecuteWebhookRequest.java @@ -70,7 +70,9 @@ public DiscordRequestBuilder create() { for (int i = 0; i < paths.size(); i++) { try { bodyBuilder.filePart("file[%d]".formatted(i), paths.get(i)); - } catch (FileNotFoundException ignored) {} + } catch (FileNotFoundException ignored) { + /* Ignore */ + } } } ); diff --git a/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java b/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java index dfb40023..426b1005 100644 --- a/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java +++ b/api/src/test/integration/com/javadiscord/jdi/core/api/UserRequestTest.java @@ -24,11 +24,6 @@ public static void setup() throws InterruptedException { guild = new LiveDiscordHelper().getGuild(); } - @AfterEach - void delayBetweenTests() throws InterruptedException { - TimeUnit.SECONDS.sleep(30); - } - @Test @Disabled void testGetCurrentUserGuildMember() throws InterruptedException { From 8c115325cb59450e7ab299d31767859bd2b07956 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Thu, 30 May 2024 13:56:44 +0100 Subject: [PATCH 57/57] Sonar security fix --- .../jdi/internal/processor/ClassFileUtil.java | 15 +++- .../jdi/internal/processor/ZipSecurity.java | 68 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 annotations/src/main/java/com/javadiscord/jdi/internal/processor/ZipSecurity.java diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java index 7ef18630..42acacfb 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ClassFileUtil.java @@ -5,6 +5,9 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; @@ -50,7 +53,7 @@ public static String getClassName(File file) throws IOException { } private static String getClassNameFromJar(FileInputStream fis) throws IOException { - try (ZipInputStream zip = new ZipInputStream(fis)) { + try (ZipInputStream zip = ZipSecurity.createSecureInputStream(new ZipInputStream(fis))) { ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { if (!entry.isDirectory() && entry.getName().endsWith(".class")) { @@ -89,7 +92,7 @@ private static List getClassesFromJar(File jarFile) throws IOException { List classFiles = new ArrayList<>(); try ( FileInputStream fis = new FileInputStream(jarFile); - ZipInputStream zip = new ZipInputStream(fis) + ZipInputStream zip = ZipSecurity.createSecureInputStream(new ZipInputStream(fis)) ) { ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { @@ -118,7 +121,7 @@ private static File extractClassFileFromJar( ZipInputStream zip, String entryName ) throws IOException { - File tempFile = File.createTempFile(entryName.replace('/', '_'), ".class"); + File tempFile = safeTempFile(entryName).toFile(); tempFile.deleteOnExit(); try (FileOutputStream fos = new FileOutputStream(tempFile)) { byte[] buffer = new byte[1024]; @@ -129,4 +132,10 @@ private static File extractClassFileFromJar( } return tempFile; } + + private static Path safeTempFile(String entryName) throws IOException { + String sanitizedEntryName = entryName.replace('/', '_'); + Path secureTempDir = Paths.get(System.getProperty("java.io.tmpdir")); + return Files.createTempFile(secureTempDir, sanitizedEntryName, ".class"); + } } diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ZipSecurity.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ZipSecurity.java new file mode 100644 index 00000000..552941b1 --- /dev/null +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/ZipSecurity.java @@ -0,0 +1,68 @@ +package com.javadiscord.jdi.internal.processor; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class ZipSecurity { + + public static ZipInputStream createSecureInputStream(InputStream stream) { + return new SecureZipInputStream(stream); + } + + private static class SecureZipInputStream extends ZipInputStream { + + public SecureZipInputStream(InputStream in) { + super(in); + } + + @Override + public ZipEntry getNextEntry() throws IOException { + ZipEntry entry = super.getNextEntry(); + if (entry == null) { + return null; + } + String entryName = entry.getName(); + if (!entryName.trim().isEmpty()) { + if (isAbsolutePath(entryName)) { + throw new SecurityException( + "Encountered zip file with absolute path: " + entryName + ); + } + if (containsPathTraversal(entryName)) { + throw new SecurityException( + "Path contains traversal to sensitive locations: " + entryName + ); + } + } + return entry; + } + + private boolean containsPathTraversal(String entryName) { + if (entryName.contains("../") || entryName.contains("..\\")) { + try { + if (isPathOutsideCurrentDirectory(entryName)) { + return true; + } + } catch (IOException ignore) { + /* Ignore */ + } + } + return false; + } + + private boolean isPathOutsideCurrentDirectory(String entryName) throws IOException { + File currentDirectory = new File("").getCanonicalFile(); + File untrustedFile = new File(currentDirectory, entryName); + Path untrustedPath = untrustedFile.getCanonicalFile().toPath(); + return !untrustedPath.startsWith(currentDirectory.toPath()); + } + + private boolean isAbsolutePath(String entryName) { + return entryName.startsWith("/"); + } + } +}