From f6473e77f0aa1cf2b2ea87ec56a1ad592cede422 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 05:06:30 +0000 Subject: [PATCH 01/59] build(deps): bump spring-boot-starter-websocket from 3.0.5 to 3.0.6 Bumps [spring-boot-starter-websocket](https://github.com/spring-projects/spring-boot) from 3.0.5 to 3.0.6. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.5...v3.0.6) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-websocket dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 028884c1..11d8e6cc 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ org.springframework.boot spring-boot-starter-websocket - 3.0.5 + 3.0.6 From 305990a76c0517318f7895c0d085e3e572b4e281 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 22 Apr 2023 17:24:49 +0100 Subject: [PATCH 02/59] feat: Added some changes to resolved comamnd options. --- .../data/command/ResolvedCommandOption.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/model/interaction/data/command/ResolvedCommandOption.java b/src/main/java/com/seailz/discordjar/model/interaction/data/command/ResolvedCommandOption.java index 427e98d9..be9397b0 100644 --- a/src/main/java/com/seailz/discordjar/model/interaction/data/command/ResolvedCommandOption.java +++ b/src/main/java/com/seailz/discordjar/model/interaction/data/command/ResolvedCommandOption.java @@ -3,12 +3,14 @@ import com.seailz.discordjar.core.Compilerable; import com.seailz.discordjar.command.CommandOptionType; import com.seailz.discordjar.model.interaction.data.ResolvedData; +import com.seailz.discordjar.model.message.Attachment; import com.seailz.discordjar.model.role.Role; import com.seailz.discordjar.model.user.User; import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONObject; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -87,6 +89,10 @@ public int getAsInt() { public boolean getAsBoolean() { return (boolean) data; } + public double getAsDouble() { + BigDecimal bd = (BigDecimal) data; + return bd.doubleValue(); + } /** * Returns a raw version of the data of the option. @@ -102,6 +108,9 @@ public Role getAsRole() { public User getAsUser() { return this.resolved.users().get(getAsString()); } + public Attachment getAsAttachment() { + return this.resolved.attachments().get(getAsString()); + } public CommandOptionType type() { return type; @@ -111,6 +120,10 @@ public List options() { return options; } + public ResolvedData resolved() { + return resolved; + } + public boolean focused() { return focused; } From 673f68b38b6f51e300753673c45b377b968d4857 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 22 Apr 2023 17:29:16 +0100 Subject: [PATCH 03/59] feat: Added some changes to resolved data --- .../seailz/discordjar/model/interaction/data/ResolvedData.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java b/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java index 3127712a..c0fb3a20 100644 --- a/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java +++ b/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java @@ -94,6 +94,8 @@ public static ResolvedData decompile(JSONObject obj, DiscordJar discordJar) { HashMap messages; HashMap attachments; + System.out.println(obj); + try { JSONObject resolved = obj.getJSONObject("users"); users = new HashMap<>(); From ef7939334f6be69df4e7591805b46183d7526ee4 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 22 Apr 2023 17:38:40 +0100 Subject: [PATCH 04/59] feat: Fixed ResolvedData.java --- .../model/interaction/data/ResolvedData.java | 80 +++++++++---------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java b/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java index c0fb3a20..bd6d2c8e 100644 --- a/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java +++ b/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java @@ -87,70 +87,66 @@ public JSONObject compile() { @NotNull public static ResolvedData decompile(JSONObject obj, DiscordJar discordJar) { - HashMap users; - HashMap members; - HashMap roles = null; - HashMap channels; - HashMap messages; - HashMap attachments; + HashMap users = new HashMap<>(); + HashMap members = new HashMap<>(); + HashMap roles = new HashMap<>(); + HashMap channels = new HashMap<>(); + HashMap messages = new HashMap<>(); + HashMap attachments = new HashMap<>(); System.out.println(obj); - try { - JSONObject resolved = obj.getJSONObject("users"); - users = new HashMap<>(); - HashMap finalUsers = users; - resolved.toMap().forEach((key, value) -> finalUsers.put(key, User.decompile((JSONObject) value, discordJar))); - } catch (Exception e) { + if (obj.has("users") && !obj.isNull("users")) { + JSONObject usersObj = obj.getJSONObject("users"); + for (String s : usersObj.keySet()) { + users.put(s, User.decompile(usersObj.getJSONObject(s), discordJar)); + } + } else { users = null; } - try { - JSONObject resolved = obj.getJSONObject("members"); - members = new HashMap<>(); - HashMap finalMembers = members; - resolved.toMap().forEach((key, value) -> finalMembers.put(key, Member.decompile((JSONObject) value, discordJar, null, null))); - } catch (Exception e) { + if (obj.has("members") && !obj.isNull("members")) { + JSONObject membersObj = obj.getJSONObject("members"); + for (String s : membersObj.keySet()) { + members.put(s, Member.decompile(membersObj.getJSONObject(s), discordJar, null, null)); + } + } else { members = null; } - try { + if (obj.has("roles") && !obj.isNull("roles")) { JSONObject rolesObj = obj.getJSONObject("roles"); - roles = new HashMap<>(); - - HashMap finalRoles = roles; - rolesObj.toMap().forEach((key, value) -> { - String json = new com.google.gson.Gson().toJson(value); - finalRoles.put(key, Role.decompile(new JSONObject(json))); - }); - } catch (Exception e) { + for (String s : rolesObj.keySet()) { + roles.put(s, Role.decompile(rolesObj.getJSONObject(s))); + } + } else { roles = null; } - try { + if (obj.has("channels") && !obj.isNull("channels")) { JSONObject channelsObj = obj.getJSONObject("channels"); - channels = new HashMap<>(); - HashMap finalChannels = channels; - channelsObj.toMap().forEach((key, value) -> finalChannels.put(key, Channel.decompile((JSONObject) value, discordJar))); - } catch (Exception e) { + for (String s : channelsObj.keySet()) { + channels.put(s, Channel.decompile(channelsObj.getJSONObject(s), discordJar)); + } + } else { channels = null; } - try { + if (obj.has("messages") && !obj.isNull("messages")) { JSONObject messagesObj = obj.getJSONObject("messages"); - messages = new HashMap<>(); - HashMap finalMessages = messages; - messagesObj.toMap().forEach((key, value) -> finalMessages.put(key, Message.decompile((JSONObject) value, discordJar))); - } catch (Exception e) { + for (String s : messagesObj.keySet()) { + messages.put(s, Message.decompile(messagesObj.getJSONObject(s), discordJar)); + } + } else { messages = null; } - try { + if (obj.has("attachments") && !obj.isNull("attachments")) { JSONObject attachmentsObj = obj.getJSONObject("attachments"); - attachments = new HashMap<>(); - HashMap finalAttachments = attachments; - attachmentsObj.toMap().forEach((key, value) -> finalAttachments.put(key, Attachment.decompile((JSONObject) value))); - } catch (Exception e) { + for (String s : attachmentsObj.keySet()) { + attachments.put(s, Attachment.decompile(attachmentsObj.getJSONObject(s))); + } + } else { attachments = null; } From e1aee1d15374014d38b493d2f3c5d24447939578 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 22 Apr 2023 17:43:29 +0100 Subject: [PATCH 05/59] fix: Removed a print statement used in testing --- .../seailz/discordjar/model/interaction/data/ResolvedData.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java b/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java index bd6d2c8e..8c2034f9 100644 --- a/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java +++ b/src/main/java/com/seailz/discordjar/model/interaction/data/ResolvedData.java @@ -94,8 +94,6 @@ public static ResolvedData decompile(JSONObject obj, DiscordJar discordJar) { HashMap messages = new HashMap<>(); HashMap attachments = new HashMap<>(); - System.out.println(obj); - if (obj.has("users") && !obj.isNull("users")) { JSONObject usersObj = obj.getJSONObject("users"); for (String s : usersObj.keySet()) { From f09d296e5569f87d8fa069f34533b88cef0992d2 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 27 Apr 2023 15:35:47 +0100 Subject: [PATCH 06/59] feat: Adds the Messageable, MessageRetrievable, and Transcriptable interfaces to threads. --- .../com/seailz/discordjar/model/channel/thread/Thread.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/model/channel/thread/Thread.java b/src/main/java/com/seailz/discordjar/model/channel/thread/Thread.java index 6e3ad32f..b4d0c304 100644 --- a/src/main/java/com/seailz/discordjar/model/channel/thread/Thread.java +++ b/src/main/java/com/seailz/discordjar/model/channel/thread/Thread.java @@ -5,6 +5,9 @@ import com.seailz.discordjar.model.channel.GuildChannel; import com.seailz.discordjar.model.channel.MessagingChannel; import com.seailz.discordjar.model.channel.TextChannel; +import com.seailz.discordjar.model.channel.interfaces.MessageRetrievable; +import com.seailz.discordjar.model.channel.interfaces.Messageable; +import com.seailz.discordjar.model.channel.interfaces.Transcriptable; import com.seailz.discordjar.model.channel.interfaces.Typeable; import com.seailz.discordjar.model.channel.internal.ThreadImpl; import com.seailz.discordjar.model.channel.utils.ChannelType; @@ -52,7 +55,7 @@ * @since 1.0 * @see GuildChannel */ -public interface Thread extends GuildChannel, Typeable { +public interface Thread extends GuildChannel, Typeable, Messageable, MessageRetrievable, Transcriptable { /** * The id of the parent channel From f87640689b5a0053cb85a2777fbfbac30ac75e32 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 29 Apr 2023 11:40:50 +0100 Subject: [PATCH 07/59] feat: Added a method to remove a field from an Embeder.java object. --- .../com/seailz/discordjar/model/embed/Embeder.java | 2 ++ .../seailz/discordjar/model/embed/EmbederImpl.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/model/embed/Embeder.java b/src/main/java/com/seailz/discordjar/model/embed/Embeder.java index 500d1c87..fd83741e 100644 --- a/src/main/java/com/seailz/discordjar/model/embed/Embeder.java +++ b/src/main/java/com/seailz/discordjar/model/embed/Embeder.java @@ -17,6 +17,8 @@ public interface Embeder { Embeder timestamp(String timestamp); Embeder timestamp(); + Embeder removeField(String name); + Embeder field(EmbedField field); Embeder field(String name, String value, boolean inline); diff --git a/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java b/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java index 95c73443..08084c62 100644 --- a/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java +++ b/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java @@ -135,6 +135,20 @@ public Embeder timestamp() { return timestamp(Instant.now().toString()); } + @Override + public Embeder removeField(String name) { + EmbedField[] fields = this.fields; + EmbedField[] newFields = new EmbedField[fields.length - 1]; + int i = 0; + for (EmbedField field : fields) { + if (!field.name().equals(name)) { + newFields[i++] = field; + } + } + this.fields = newFields; + return this; + } + @Override public JSONObject compile() { From 072ae61c2a6da8cf2ac4ca79a0b131a612668621 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 29 Apr 2023 20:26:33 +0100 Subject: [PATCH 08/59] feat: Added Embeder methods to add fields to specific indexes --- .../discordjar/model/embed/Embeder.java | 4 +++ .../discordjar/model/embed/EmbederImpl.java | 25 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/model/embed/Embeder.java b/src/main/java/com/seailz/discordjar/model/embed/Embeder.java index fd83741e..19f897ca 100644 --- a/src/main/java/com/seailz/discordjar/model/embed/Embeder.java +++ b/src/main/java/com/seailz/discordjar/model/embed/Embeder.java @@ -20,8 +20,12 @@ public interface Embeder { Embeder removeField(String name); Embeder field(EmbedField field); + Embeder field(EmbedField field, int index); + Embeder field(String name, String value); + Embeder field(String name, String value, int index); Embeder field(String name, String value, boolean inline); + Embeder field(String name, String value, boolean inline, int index); Embeder color(Color color); Embeder color(int color); diff --git a/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java b/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java index 08084c62..fd5ad27e 100644 --- a/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java +++ b/src/main/java/com/seailz/discordjar/model/embed/EmbederImpl.java @@ -55,19 +55,40 @@ public Embeder url(String url) { @Override public Embeder field(EmbedField field) { + return field(field, fields.length); + } + + @Override + public Embeder field(EmbedField field, int index) { EmbedField[] fields = this.fields; EmbedField[] newFields = new EmbedField[fields.length + 1]; - System.arraycopy(fields, 0, newFields, 0, fields.length); - newFields[fields.length] = field; + System.arraycopy(fields, 0, newFields, 0, index); + newFields[index] = field; + System.arraycopy(fields, index, newFields, index + 1, fields.length - index); this.fields = newFields; return this; } + @Override + public Embeder field(String name, String value) { + return field(new EmbedField(name, value, false)); + } + + @Override + public Embeder field(String name, String value, int index) { + return field(new EmbedField(name, value, false), index); + } + @Override public Embeder field(String name, String value, boolean inline) { return field(new EmbedField(name, value, inline)); } + @Override + public Embeder field(String name, String value, boolean inline, int index) { + return field(new EmbedField(name, value, inline), index); + } + @Override public Embeder footer(EmbedFooter footer) { this.footer = footer; From 811dce781aa6b3c9c0440ad2332cc80403019c71 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 29 Apr 2023 20:27:31 +0100 Subject: [PATCH 09/59] fix: Fixed message editing url not replacing {message.id} with the actual message id, so the URL would end up being something like BASEURL/channels/x/messages/{message.id} --- .../com/seailz/discordjar/action/message/MessageEditAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java b/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java index bd8816b6..635be97c 100644 --- a/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java +++ b/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java @@ -182,7 +182,7 @@ public MessageEditAction addFiles(List files) { public CompletableFuture run() { CompletableFuture future = new CompletableFuture<>(); future.completeAsync(() -> { - String url = URLS.PATCH.CHANNEL.MESSAGE.EDIT.replace("{channel.id}", channelId); + String url = URLS.PATCH.CHANNEL.MESSAGE.EDIT.replace("{channel.id}", channelId).replace("{message.id}", messageId); JSONObject payload = new JSONObject(); if (this.text != null) payload.put("content", this.text); From 40ba629d3590b19767935d7263413ed8933e3ead Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 30 Apr 2023 12:34:39 +0100 Subject: [PATCH 10/59] fix(gateway): Limited WebSocket connections to one at a time. This should hopefully stop events being deployed twice. --- .../com/seailz/discordjar/DiscordJar.java | 4 ++++ .../discordjar/gateway/GatewayFactory.java | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index e911d341..f0f3f384 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -114,6 +114,9 @@ public class DiscordJar { */ private List buckets; + public int gatewayConnections = 0; + public List gatewayFactories = new ArrayList<>(); + public DiscordJar(String token, EnumSet intents, APIVersion version) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, false); } @@ -285,6 +288,7 @@ public void restartGateway() { Status stat = killGateway(); try { gatewayFactory = new GatewayFactory(this, debug); + gatewayFactories.add(gatewayFactory); } catch (ExecutionException | InterruptedException | DiscordRequest.UnhandledDiscordAPIErrorException e) { throw new RuntimeException(e); } diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index 4a0af54f..456c40a0 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -65,6 +65,25 @@ public class GatewayFactory extends TextWebSocketHandler { public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionException, InterruptedException, DiscordRequest.UnhandledDiscordAPIErrorException { this.discordJar = discordJar; this.debug = debug; + + new Thread(() -> { + if (discordJar.gatewayConnections > 0) { + // Kill all other connections + for (GatewayFactory gatewayFactory : discordJar.gatewayFactories) { + if (gatewayFactory != null && gatewayFactory.session.isOpen()) { + try { + gatewayFactory.killConnection(); + discordJar.gatewayFactories.remove(gatewayFactory); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + discordJar.gatewayConnections = 1; + }).start(); + DiscordResponse response = new DiscordRequest( new JSONObject(), new HashMap<>(), From 18a52cdec5159356ddddbe32a00f7497cb2c358b Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 30 Apr 2023 12:46:44 +0100 Subject: [PATCH 11/59] feat(rate limits): Updated ratelimit logic to stop using map() method. --- .../discordjar/utils/rest/DiscordRequest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 4952f092..c6effa31 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -133,7 +133,8 @@ else if (requestMethod == RequestMethod.DELETE) { HttpClient client = HttpClient.newHttpClient(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - + System.out.println(response.headers().map().isEmpty()); + System.out.println(response.headers().map().toString()); int responseCode = response.statusCode(); if (djv.isDebug()) { System.out.println(request.method() + " " + request.uri() + " with " + body + " returned " + responseCode + " with " + response.body()); @@ -142,8 +143,9 @@ else if (requestMethod == RequestMethod.DELETE) { response.headers().map().forEach((key, value) -> headers.put(key, value.get(0))); // check headers for rate-limit - if (response.headers().map().containsKey("X-RateLimit-Bucket")) { - String bucketId = response.headers().map().get("X-RateLimit-Bucket").get(0); + if (response.headers().firstValue(("X-RateLimit-Bucket")).isPresent()) { + System.out.println("Rate limit headers found!"); + String bucketId = response.headers().firstValue("X-RateLimit-Bucket").get(); Bucket buck = djv.getBucket(bucketId); List affectedRoutes = buck == null ? new ArrayList<>() : new ArrayList<>(buck.getAffectedRoutes()); @@ -151,10 +153,10 @@ else if (requestMethod == RequestMethod.DELETE) { else affectedRoutes.add(url); djv.updateBucket(bucketId, new Bucket( - bucketId, Integer.parseInt(response.headers().map().get("X-RateLimit-Remaining").get(0)), - Double.parseDouble(response.headers().map().get( + bucketId, Integer.parseInt(response.headers().firstValue("X-RateLimit-Remaining").get()), + Double.parseDouble(response.headers().firstValue( "X-RateLimit-Reset" - ).get(0)) + ).get()) ).setAffectedRoutes(affectedRoutes)); } @@ -173,9 +175,10 @@ else if (requestMethod == RequestMethod.DELETE) { Logger.getLogger("RateLimit").warning("[RATE LIMIT] Rate limit has been exceeded. Please make sure" + " you are not sending too many requests."); } + JSONObject body = new JSONObject(response.body()); - Bucket exceededBucket = djv.getBucket(response.headers().map().get("X-RateLimit-Bucket").get(0)); - //queueRequest(Double.parseDouble(response.headers().map().get("X-RateLimit-Reset").get(0)), exceededBucket); + Bucket exceededBucket = djv.getBucket(response.headers().firstValue("X-RateLimit-Bucket").get()); + queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); if (body.has("retry_after")) { new Thread(() -> { @@ -191,7 +194,7 @@ else if (requestMethod == RequestMethod.DELETE) { } }).start(); } else { - queueRequest(Double.parseDouble(response.headers().map().get("X-RateLimit-Reset").get(0)), exceededBucket); + queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); } if (body.getBoolean("global")) { Logger.getLogger("RateLimit").severe( From 061ae9a47ba1ed19da5bdc0e3da828400ac777bf Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 30 Apr 2023 12:53:05 +0100 Subject: [PATCH 12/59] feat(rate limits): Updated ratelimit logic to stop using map() method. --- .../com/seailz/discordjar/utils/rest/DiscordRequest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index c6effa31..139992fa 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -177,8 +177,7 @@ else if (requestMethod == RequestMethod.DELETE) { } JSONObject body = new JSONObject(response.body()); - Bucket exceededBucket = djv.getBucket(response.headers().firstValue("X-RateLimit-Bucket").get()); - queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); + //queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); if (body.has("retry_after")) { new Thread(() -> { @@ -194,6 +193,10 @@ else if (requestMethod == RequestMethod.DELETE) { } }).start(); } else { + if (response.headers().firstValue("X-RateLimit-Bucket").isEmpty() || response.headers().firstValue("X-RateLimit-Reset").isEmpty()) { + return new DiscordResponse(429, null, null, null); + } + Bucket exceededBucket = djv.getBucket(response.headers().firstValue("X-RateLimit-Bucket").get()); queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); } if (body.getBoolean("global")) { From e97717285ca26c42574b146b9dd0195798e84cbe Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 30 Apr 2023 12:58:39 +0100 Subject: [PATCH 13/59] feat(rate limits): Updated ratelimit logic to stop using map() method. --- .../java/com/seailz/discordjar/utils/rest/DiscordRequest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 139992fa..68c9cfed 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -171,6 +171,8 @@ else if (requestMethod == RequestMethod.DELETE) { } if (responseCode == 429) { + System.out.println("Rate limit has been exceeded. Please make sure you are not sending too many requests."); + System.out.println(response.body()); if (djv.isDebug()) { Logger.getLogger("RateLimit").warning("[RATE LIMIT] Rate limit has been exceeded. Please make sure" + " you are not sending too many requests."); From 97e4645195a45b441a7a52a16f04a948edf3b1e5 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 30 Apr 2023 14:38:56 +0100 Subject: [PATCH 14/59] feat(rate limits): Updated ratelimit logic to stop using map() method. --- .../com/seailz/discordjar/utils/rest/DiscordRequest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 68c9cfed..70b01571 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -133,8 +133,6 @@ else if (requestMethod == RequestMethod.DELETE) { HttpClient client = HttpClient.newHttpClient(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println(response.headers().map().isEmpty()); - System.out.println(response.headers().map().toString()); int responseCode = response.statusCode(); if (djv.isDebug()) { System.out.println(request.method() + " " + request.uri() + " with " + body + " returned " + responseCode + " with " + response.body()); @@ -144,7 +142,6 @@ else if (requestMethod == RequestMethod.DELETE) { // check headers for rate-limit if (response.headers().firstValue(("X-RateLimit-Bucket")).isPresent()) { - System.out.println("Rate limit headers found!"); String bucketId = response.headers().firstValue("X-RateLimit-Bucket").get(); Bucket buck = djv.getBucket(bucketId); @@ -171,8 +168,6 @@ else if (requestMethod == RequestMethod.DELETE) { } if (responseCode == 429) { - System.out.println("Rate limit has been exceeded. Please make sure you are not sending too many requests."); - System.out.println(response.body()); if (djv.isDebug()) { Logger.getLogger("RateLimit").warning("[RATE LIMIT] Rate limit has been exceeded. Please make sure" + " you are not sending too many requests."); @@ -348,7 +343,6 @@ public DiscordResponse invokeWithFiles(File... files) { HashMap headers = new HashMap<>(); response.headers().map().forEach((key, value) -> headers.put(key, value.get(0))); - System.out.println(response.body()); if (responseCode == 200 || responseCode == 201) { From fce9b60f7a2407ed9e73527cb2f7adaf8e298bee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 05:20:04 +0000 Subject: [PATCH 15/59] build(deps): bump jsoup from 1.15.4 to 1.16.1 Bumps [jsoup](https://github.com/jhy/jsoup) from 1.15.4 to 1.16.1. - [Release notes](https://github.com/jhy/jsoup/releases) - [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES) - [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.15.4...jsoup-1.16.1) --- updated-dependencies: - dependency-name: org.jsoup:jsoup dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11d8e6cc..ab93b5c2 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ org.jsoup jsoup - 1.15.4 + 1.16.1 com.squareup.okhttp3 From 0bc19338661d8fecf3444fb2f705f7b9a8a5e150 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 1 May 2023 16:37:58 +0100 Subject: [PATCH 16/59] feat: Added the ability to listen for slash commands using DiscordListener.java. This is not recommended. --- .../discordjar/command/CommandDispatcher.java | 3 +++ .../seailz/discordjar/events/DiscordListener.java | 11 +++++++++++ .../seailz/discordjar/events/EventDispatcher.java | 15 +++++++-------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java b/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java index 337764ca..a009e895 100644 --- a/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java +++ b/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java @@ -5,6 +5,7 @@ import com.seailz.discordjar.command.listeners.slash.SlashCommandListener; import com.seailz.discordjar.command.listeners.slash.SlashSubCommand; import com.seailz.discordjar.command.listeners.slash.SubCommandListener; +import com.seailz.discordjar.events.EventDispatcher; import com.seailz.discordjar.events.model.interaction.command.CommandInteractionEvent; import com.seailz.discordjar.events.model.interaction.command.SlashCommandInteractionEvent; import com.seailz.discordjar.model.interaction.data.command.ResolvedCommandOption; @@ -42,6 +43,8 @@ public void registerSubCommand(SlashCommandListener top, SlashSubCommand sub, Su } public void dispatch(String name, CommandInteractionEvent event) { + Class eventClass = (event instanceof SlashCommandInteractionEvent ? SlashCommandInteractionEvent.class : CommandInteractionEvent.class); + new EventDispatcher(event.getBot()).dispatchEvent(event, eventClass, event.getBot()); if ((event instanceof SlashCommandInteractionEvent) && ((SlashCommandInteractionEvent) event).getOptionsInternal() != null && !((SlashCommandInteractionEvent) event).getOptionsInternal().isEmpty()) { for (ResolvedCommandOption option : ((SlashCommandInteractionEvent) event).getOptionsInternal()) { if (option.type() == CommandOptionType.SUB_COMMAND) { diff --git a/src/main/java/com/seailz/discordjar/events/DiscordListener.java b/src/main/java/com/seailz/discordjar/events/DiscordListener.java index 74fb5759..8d434f8b 100644 --- a/src/main/java/com/seailz/discordjar/events/DiscordListener.java +++ b/src/main/java/com/seailz/discordjar/events/DiscordListener.java @@ -1,5 +1,6 @@ package com.seailz.discordjar.events; +import com.seailz.discordjar.command.listeners.CommandListener; import com.seailz.discordjar.events.model.command.CommandPermissionUpdateEvent; import com.seailz.discordjar.events.model.general.ReadyEvent; import com.seailz.discordjar.events.model.guild.GuildCreateEvent; @@ -7,6 +8,7 @@ import com.seailz.discordjar.events.model.guild.member.GuildMemberRemoveEvent; import com.seailz.discordjar.events.model.guild.member.GuildMemberUpdateEvent; import com.seailz.discordjar.events.model.interaction.button.ButtonInteractionEvent; +import com.seailz.discordjar.events.model.interaction.command.SlashCommandInteractionEvent; import com.seailz.discordjar.events.model.interaction.modal.ModalInteractionEvent; import com.seailz.discordjar.events.model.interaction.select.StringSelectMenuInteractionEvent; import com.seailz.discordjar.events.model.interaction.select.entity.ChannelSelectMenuInteractionEvent; @@ -53,6 +55,15 @@ public void onGuildMemberRemove(@NotNull GuildMemberRemoveEvent event) { public void onCommandPermissionUpdate(@NotNull CommandPermissionUpdateEvent event) { } + /** + * It is not recommend to use this method. Instead, where possible, you should use {@link com.seailz.discordjar.command.listeners.slash.SlashCommandListener SLashCommandListener} with a + * {@link com.seailz.discordjar.command.annotation.SlashCommandInfo SlashCommandInfo} annotation and then register it using {@link com.seailz.discordjar.DiscordJar#registerCommands(CommandListener...)} + * @param event The event + */ + @Deprecated + public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { + } + // Message Component Events // Select Menu Events public void onStringSelectMenuInteraction(@NotNull StringSelectMenuInteractionEvent event) { diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 54dc22bd..3179b8f9 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -7,6 +7,7 @@ import com.seailz.discordjar.utils.rest.DiscordRequest; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.HashMap; /** @@ -62,14 +63,12 @@ public void dispatchEvent(Event event, Class type, DiscordJar d int index = 0; for (Method i : listeners.values()) { - if (i.isAnnotationPresent(EventMethod.class)) { - if (i.getParameterTypes()[0].equals(type)) { - try { - i.setAccessible(true); - i.invoke(listeners.keySet().toArray()[index], event); - } catch (Exception e) { - e.printStackTrace(); - } + if (i.getParameterCount() == 1 && i.getParameterTypes()[0].equals(type) && Modifier.isPublic(i.getModifiers())) { + try { + i.setAccessible(true); + i.invoke(listeners.keySet().toArray()[index], event); + } catch (Exception e) { + e.printStackTrace(); } } index++; From b431cb91d7c7aca4099da5cac99b6e643f4d8e6c Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Tue, 2 May 2023 17:36:03 +0100 Subject: [PATCH 17/59] fix: Fixed command listeners registered with the event system not dispatching as the command dispatch method was creating a new instance of the EventDispatcher class instead of using the one provided by the DiscordJar class --- .../java/com/seailz/discordjar/command/CommandDispatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java b/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java index a009e895..823f4594 100644 --- a/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java +++ b/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java @@ -44,7 +44,7 @@ public void registerSubCommand(SlashCommandListener top, SlashSubCommand sub, Su public void dispatch(String name, CommandInteractionEvent event) { Class eventClass = (event instanceof SlashCommandInteractionEvent ? SlashCommandInteractionEvent.class : CommandInteractionEvent.class); - new EventDispatcher(event.getBot()).dispatchEvent(event, eventClass, event.getBot()); + event.getBot().getEventDispatcher().dispatchEvent(event, eventClass, event.getBot()); if ((event instanceof SlashCommandInteractionEvent) && ((SlashCommandInteractionEvent) event).getOptionsInternal() != null && !((SlashCommandInteractionEvent) event).getOptionsInternal().isEmpty()) { for (ResolvedCommandOption option : ((SlashCommandInteractionEvent) event).getOptionsInternal()) { if (option.type() == CommandOptionType.SUB_COMMAND) { From ef8d5d6c8fc0358a2e7e3739c9bffe4934be67b1 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 4 May 2023 20:31:19 +0100 Subject: [PATCH 18/59] feat: Deprecated old constructors for DiscordJar to make way for the DiscordJarBuilder class to be completed in a future PR, and added member caching. --- .../com/seailz/discordjar/DiscordJar.java | 100 +++++++++++++----- .../seailz/discordjar/DiscordJarBuilder.java | 9 ++ .../com/seailz/discordjar/cache/Cache.java | 9 +- .../gateway/events/DispatchedEvents.java | 26 ++++- .../seailz/discordjar/model/guild/Guild.java | 44 ++++---- 5 files changed, 138 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/seailz/discordjar/DiscordJarBuilder.java diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index f0f3f384..71da0565 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -25,6 +25,7 @@ import com.seailz.discordjar.model.emoji.sticker.Sticker; import com.seailz.discordjar.model.emoji.sticker.StickerPack; import com.seailz.discordjar.model.guild.Guild; +import com.seailz.discordjar.model.guild.Member; import com.seailz.discordjar.model.invite.Invite; import com.seailz.discordjar.model.invite.internal.InviteImpl; import com.seailz.discordjar.model.status.Status; @@ -76,18 +77,27 @@ public class DiscordJar { * Intents the bot will use when connecting to the gateway */ private final EnumSet intents; + + /* Caches */ + /** - * Used for caching guilds in memory + * Used for caching {@link Guild guilds} in memory */ private final Cache guildCache; /** - * Used for caching users in memory + * Used for caching {@link User users} in memory */ private final Cache userCache; /** - * Used for caching channels in memory + * Used for caching {@link Channel Channels} in memory */ private final Cache channelCache; + /** + * Used for caching {@link com.seailz.discordjar.model.guild.Member Guild Members} in memory + */ + private final Cache memberCache; + + /** * Manages dispatching events to listeners */ @@ -117,47 +127,72 @@ public class DiscordJar { public int gatewayConnections = 0; public List gatewayFactories = new ArrayList<>(); + /** + * Creates a new instance of discord.jar. + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, EnumSet intents, APIVersion version) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, false); } + /** + * Creates a new instance of discord.jar. + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, EnumSet intents, APIVersion version, boolean debug) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, debug); } + /** + * Creates a new instance of discord.jar. + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, APIVersion version) throws ExecutionException, InterruptedException { this(token, EnumSet.of(Intent.ALL), version, false, null, false); } + /** + * Creates a new instance of discord.jar. + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) throws ExecutionException, InterruptedException { this(token, EnumSet.noneOf(Intent.class), version, httpOnly, httpOnlyInfo, false); } + /** + * Creates a new instance of discord.jar. + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) throws ExecutionException, InterruptedException { this(token, EnumSet.noneOf(Intent.class), APIVersion.getLatest(), httpOnly, httpOnlyInfo, false); } - /** - * Creates a new instance of the DiscordJar class - * This will start the connection to the Discord gateway, set caches, set the event dispatcher, set the logger, set up eliminate handling, and initiates no shutdown - * - * @param token The token of the bot - * @param intents The intents the bot will use - * @param version The version of the Discord API the bot will use - * @param httpOnly Makes your bot an HTTP only bot. This WILL - * break some methods and is only recommended to be set to true if you know what you are doing. Otherwise, leave it to false or don't set it. - * HTTP-only bots (or Interaction-only bots) are bots that do not connect to the gateway, and therefore cannot receive events. They receive - * interactions through POST requests to a specified endpoint of your bot. This is useful if you want to make a bot that only uses slash commands. - * Voice will not work, neither will {@link #setStatus(Status)} & most gateway events. - * Interaction-based events will still be delivered as usual. - * For a full tutorial, see the README.md file. - * @param httpOnlyInfo The information needed to make your bot HTTP only. This is only needed if you set httpOnly to true, otherwise set to null. - * See the above parameter for more information. - * @param debug Should the bot be in debug mode? - * @throws ExecutionException If an error occurs while connecting to the gateway - * @throws InterruptedException If an error occurs while connecting to the gateway - */ - public DiscordJar(String token, EnumSet intents, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo, boolean debug) throws ExecutionException, InterruptedException { + /** + * Creates a new instance of the DiscordJar class + * This will start the connection to the Discord gateway, set caches, set the event dispatcher, set the logger, set up eliminate handling, and initiates no shutdown + * + * @param token The token of the bot + * @param intents The intents the bot will use + * @param version The version of the Discord API the bot will use + * @param httpOnly Makes your bot an HTTP only bot. This WILL + * break some methods and is only recommended to be set to true if you know what you are doing. Otherwise, leave it to false or don't set it. + * HTTP-only bots (or Interaction-only bots) are bots that do not connect to the gateway, and therefore cannot receive events. They receive + * interactions through POST requests to a specified endpoint of your bot. This is useful if you want to make a bot that only uses slash commands. + * Voice will not work, neither will {@link #setStatus(Status)} & most gateway events. + * Interaction-based events will still be delivered as usual. + * For a full tutorial, see the README.md file. + * @param httpOnlyInfo The information needed to make your bot HTTP only. This is only needed if you set httpOnly to true, otherwise set to null. + * See the above parameter for more information. + * @param debug Should the bot be in debug mode? + * @throws ExecutionException If an error occurs while connecting to the gateway + * @throws InterruptedException If an error occurs while connecting to the gateway + */ + private DiscordJar(String token, EnumSet intents, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo, boolean debug) throws ExecutionException, InterruptedException { System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); new RequestQueueHandler(this); this.token = token; @@ -203,6 +238,15 @@ public DiscordJar(String token, EnumSet intents, APIVersion version, boo RequestMethod.GET )); + this.memberCache = new Cache<>(this, Member.class, new DiscordRequest( + new JSONObject(), + new HashMap<>(), + URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER.replace("{guild.id}", "%s").replace("{user.id}", "%s"), + this, + URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER, + RequestMethod.GET + )); + this.eventDispatcher = new EventDispatcher(this); if (httpOnly) { @@ -611,6 +655,14 @@ public Cache getUserCache() { return userCache; } + /** + * Returns the cache for storing guild members + */ + @NotNull + public Cache getMemberCache() { + return memberCache; + } + /** * Returns the event dispatcher */ diff --git a/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java b/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java new file mode 100644 index 00000000..f3324595 --- /dev/null +++ b/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java @@ -0,0 +1,9 @@ +package com.seailz.discordjar; + +/** + * Creates instances of {@link DiscordJar} with all settings customizable. + */ +public class DiscordJarBuilder { + + +} diff --git a/src/main/java/com/seailz/discordjar/cache/Cache.java b/src/main/java/com/seailz/discordjar/cache/Cache.java index 335c2cb0..f37023e3 100644 --- a/src/main/java/com/seailz/discordjar/cache/Cache.java +++ b/src/main/java/com/seailz/discordjar/cache/Cache.java @@ -91,13 +91,16 @@ public List getCache() { return cache; } + public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { + return getById(id, null); + } /** * Gets an item from the cache * * @param id The id of the item to get * @return The item */ - public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { + public T getById(String id, String id2) throws DiscordRequest.UnhandledDiscordAPIErrorException { AtomicReference returnObject = new AtomicReference<>(); ArrayList cacheCopy = new ArrayList<>(cache); cacheCopy.forEach(t -> { @@ -118,9 +121,11 @@ public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorExcept if (returnObject.get() == null) { // request from discord + String url = discordRequest.url().replaceAll("%s", id); + if (id2 != null) url = url.replaceAll("%s", id2); DiscordResponse response; response = new DiscordRequest( - discordRequest.body(), discordRequest.headers(), discordRequest.url().replaceAll("%s", id), discordJar, discordRequest.url(), RequestMethod.GET + discordRequest.body(), discordRequest.headers(), url, discordJar, discordRequest.url(), RequestMethod.GET ).invoke(); Method decompile; try { diff --git a/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java b/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java index 67090b05..5efd23b8 100644 --- a/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java +++ b/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java @@ -68,10 +68,18 @@ public enum DispatchedEvents { Guild guild = Guild.decompile(p.getJSONObject("d"), g); g.getGuildCache().cache(guild); + // Cache all the channels JSONArray arr = p.getJSONObject("d").getJSONArray("channels"); arr.forEach(o -> g.getChannelCache().cache( Channel.decompile((JSONObject) o, g) )); + + // Cache all the members + arr = p.getJSONObject("d").getJSONArray("members"); + arr.forEach(o -> g.getMemberCache().cache( + Member.decompile((JSONObject) o, g, guild.id(), guild) + )); + return GuildCreateEvent.class; }), @@ -240,9 +248,21 @@ public enum DispatchedEvents { return null; }), - GUILD_MEMBER_ADD((p, g, d) -> GuildMemberAddEvent.class), - GUILD_MEMBER_UPDATE((p, g, d) -> GuildMemberUpdateEvent.class), - GUILD_MEMBER_REMOVE((p, g, d) -> GuildMemberRemoveEvent.class), + GUILD_MEMBER_ADD((p, g, d) -> { + Member member = Member.decompile(p.getJSONObject("d"), d, p.getJSONObject("d").getString("guild_id"), d.getGuildById(p.getJSONObject("d").getString("guild_id"))); + d.getMemberCache().cache(member); + return GuildMemberAddEvent.class; + }), + GUILD_MEMBER_UPDATE((p, g, d) -> { + Member member = Member.decompile(p.getJSONObject("d"), d, p.getJSONObject("d").getString("guild_id"), d.getGuildById(p.getJSONObject("d").getString("guild_id"))); + d.getMemberCache().cache(member); + return GuildMemberUpdateEvent.class; + }), + GUILD_MEMBER_REMOVE((p, g, d) -> { + Member member = Member.decompile(p.getJSONObject("d"), d, p.getJSONObject("d").getString("guild_id"), d.getGuildById(p.getJSONObject("d").getString("guild_id"))); + d.getMemberCache().remove(member); + return GuildMemberRemoveEvent.class; + }), /* Unknown */ UNKNOWN((p, g, d) -> null), ; diff --git a/src/main/java/com/seailz/discordjar/model/guild/Guild.java b/src/main/java/com/seailz/discordjar/model/guild/Guild.java index efb237aa..0573f75f 100644 --- a/src/main/java/com/seailz/discordjar/model/guild/Guild.java +++ b/src/main/java/com/seailz/discordjar/model/guild/Guild.java @@ -670,29 +670,31 @@ public AutomodRuleModifyAction modifyAutomodRule(String id) { return new AutomodRuleModifyAction(id, this, discordJar); } + /** + * Given an id, returns the {@link Member member} object + * @param id The id of the member + * @return A {@link Member} object, usually from the cache, but if the member was not in the cache, it will be requested from the Discord API. + * @throws DiscordRequest.UnhandledDiscordAPIErrorException If a member was not in the cache and there was an error getting the member from the Discord API + */ + @Nullable public Member getMemberById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { - DiscordResponse req = new DiscordRequest( - new JSONObject(), - new HashMap<>(), - URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER.replace( - "{guild.id}", - this.id - ).replace( - "{user.id}", - id - ), - discordJar, - URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER, - RequestMethod.GET - ).invoke(); + Checker.isSnowflake(id, "Given id is not a snowflake"); + return discordJar.getMemberCache().getById(id, this.id); + } - if (req.body() == null) return null; - return Member.decompile( - req.body(), - discordJar, - this.id, - this - ); + /** + * Returns members from the member cache rather than through the Gateway or REST API + * @return A {@link List} of members from the cache. + */ + @NotNull + public List getMembersFromCache() { + List members = new ArrayList<>(); + for (Member member : discordJar.getMemberCache().getCache()) { + if (member.guildId() != null && member.guildId().equals(id)) { + members.add(member); + } + } + return members; } public List getMembers(int limit, String after) throws DiscordRequest.UnhandledDiscordAPIErrorException { From cc82ee3d45ff00d1906834852f8cbc9455ed8ee2 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 4 May 2023 20:58:26 +0100 Subject: [PATCH 19/59] fix: Fixed member caching --- .../com/seailz/discordjar/cache/Cache.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/cache/Cache.java b/src/main/java/com/seailz/discordjar/cache/Cache.java index f37023e3..745a752e 100644 --- a/src/main/java/com/seailz/discordjar/cache/Cache.java +++ b/src/main/java/com/seailz/discordjar/cache/Cache.java @@ -1,6 +1,7 @@ package com.seailz.discordjar.cache; import com.seailz.discordjar.DiscordJar; +import com.seailz.discordjar.model.guild.Member; import com.seailz.discordjar.utils.rest.DiscordRequest; import com.seailz.discordjar.utils.rest.DiscordResponse; import org.jetbrains.annotations.NotNull; @@ -29,11 +30,13 @@ public class Cache { private final DiscordJar discordJar; private final Class clazz; private final DiscordRequest discordRequest; + private final boolean isMember; public Cache(DiscordJar discordJar, Class clazz, DiscordRequest request) { this.discordJar = discordJar; this.clazz = clazz; this.discordRequest = request; + isMember = clazz == Member.class; new Thread(() -> { while (true) { @@ -55,7 +58,9 @@ public Cache(DiscordJar discordJar, Class clazz, DiscordRequest request) { public void cache(@NotNull T t) { String id; try { - id = (String) t.getClass().getMethod("id").invoke(t); + if (isMember) { + id = ((Member) t).user().id(); + } else id = (String) t.getClass().getMethod("id").invoke(t); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } @@ -63,7 +68,9 @@ public void cache(@NotNull T t) { for (T cacheMember : cache) { String cacheId; try { - cacheId = (String) cacheMember.getClass().getMethod("id").invoke(cacheMember); + if (isMember) { + cacheId = ((Member) cacheMember).user().id(); + } else cacheId = (String) t.getClass().getMethod("id").invoke(t); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } @@ -106,14 +113,20 @@ public T getById(String id, String id2) throws DiscordRequest.UnhandledDiscordAP cacheCopy.forEach(t -> { String itemId; - for (Method method : clazz.getMethods()) { - if (method.getName().equals("id")) { - try { - itemId = (String) method.invoke(t); - if (Objects.equals(itemId, id)) - returnObject.set(t); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); + if (isMember) { + itemId = ((Member) t).user().id(); + if (Objects.equals(itemId, id)) + returnObject.set(t); + } else { + for (Method method : clazz.getMethods()) { + if (method.getName().equals("id")) { + try { + itemId = (String) method.invoke(t); + if (Objects.equals(itemId, id)) + returnObject.set(t); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } } } } From 3b2f214c83239954bff286f84bf591e9c9e38bd3 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 4 May 2023 21:12:17 +0100 Subject: [PATCH 20/59] Revert "feat: Deprecated old constructors for DiscordJar to make way for the DiscordJarBuilder class to be completed in a future PR, and added member caching." This reverts commit ef8d5d6c8fc0358a2e7e3739c9bffe4934be67b1. --- .../com/seailz/discordjar/DiscordJar.java | 100 +++++------------- .../seailz/discordjar/DiscordJarBuilder.java | 9 -- .../com/seailz/discordjar/cache/Cache.java | 9 +- .../gateway/events/DispatchedEvents.java | 26 +---- .../seailz/discordjar/model/guild/Guild.java | 44 ++++---- 5 files changed, 50 insertions(+), 138 deletions(-) delete mode 100644 src/main/java/com/seailz/discordjar/DiscordJarBuilder.java diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index 71da0565..f0f3f384 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -25,7 +25,6 @@ import com.seailz.discordjar.model.emoji.sticker.Sticker; import com.seailz.discordjar.model.emoji.sticker.StickerPack; import com.seailz.discordjar.model.guild.Guild; -import com.seailz.discordjar.model.guild.Member; import com.seailz.discordjar.model.invite.Invite; import com.seailz.discordjar.model.invite.internal.InviteImpl; import com.seailz.discordjar.model.status.Status; @@ -77,27 +76,18 @@ public class DiscordJar { * Intents the bot will use when connecting to the gateway */ private final EnumSet intents; - - /* Caches */ - /** - * Used for caching {@link Guild guilds} in memory + * Used for caching guilds in memory */ private final Cache guildCache; /** - * Used for caching {@link User users} in memory + * Used for caching users in memory */ private final Cache userCache; /** - * Used for caching {@link Channel Channels} in memory + * Used for caching channels in memory */ private final Cache channelCache; - /** - * Used for caching {@link com.seailz.discordjar.model.guild.Member Guild Members} in memory - */ - private final Cache memberCache; - - /** * Manages dispatching events to listeners */ @@ -127,72 +117,47 @@ public class DiscordJar { public int gatewayConnections = 0; public List gatewayFactories = new ArrayList<>(); - /** - * Creates a new instance of discord.jar. - * @deprecated Use {@link DiscordJarBuilder} instead. - */ - @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, EnumSet intents, APIVersion version) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, false); } - /** - * Creates a new instance of discord.jar. - * @deprecated Use {@link DiscordJarBuilder} instead. - */ - @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, EnumSet intents, APIVersion version, boolean debug) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, debug); } - /** - * Creates a new instance of discord.jar. - * @deprecated Use {@link DiscordJarBuilder} instead. - */ - @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, APIVersion version) throws ExecutionException, InterruptedException { this(token, EnumSet.of(Intent.ALL), version, false, null, false); } - /** - * Creates a new instance of discord.jar. - * @deprecated Use {@link DiscordJarBuilder} instead. - */ - @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) throws ExecutionException, InterruptedException { this(token, EnumSet.noneOf(Intent.class), version, httpOnly, httpOnlyInfo, false); } - /** - * Creates a new instance of discord.jar. - * @deprecated Use {@link DiscordJarBuilder} instead. - */ - @Deprecated(since = "1.0", forRemoval = true) public DiscordJar(String token, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) throws ExecutionException, InterruptedException { this(token, EnumSet.noneOf(Intent.class), APIVersion.getLatest(), httpOnly, httpOnlyInfo, false); } - /** - * Creates a new instance of the DiscordJar class - * This will start the connection to the Discord gateway, set caches, set the event dispatcher, set the logger, set up eliminate handling, and initiates no shutdown - * - * @param token The token of the bot - * @param intents The intents the bot will use - * @param version The version of the Discord API the bot will use - * @param httpOnly Makes your bot an HTTP only bot. This WILL - * break some methods and is only recommended to be set to true if you know what you are doing. Otherwise, leave it to false or don't set it. - * HTTP-only bots (or Interaction-only bots) are bots that do not connect to the gateway, and therefore cannot receive events. They receive - * interactions through POST requests to a specified endpoint of your bot. This is useful if you want to make a bot that only uses slash commands. - * Voice will not work, neither will {@link #setStatus(Status)} & most gateway events. - * Interaction-based events will still be delivered as usual. - * For a full tutorial, see the README.md file. - * @param httpOnlyInfo The information needed to make your bot HTTP only. This is only needed if you set httpOnly to true, otherwise set to null. - * See the above parameter for more information. - * @param debug Should the bot be in debug mode? - * @throws ExecutionException If an error occurs while connecting to the gateway - * @throws InterruptedException If an error occurs while connecting to the gateway - */ - private DiscordJar(String token, EnumSet intents, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo, boolean debug) throws ExecutionException, InterruptedException { + /** + * Creates a new instance of the DiscordJar class + * This will start the connection to the Discord gateway, set caches, set the event dispatcher, set the logger, set up eliminate handling, and initiates no shutdown + * + * @param token The token of the bot + * @param intents The intents the bot will use + * @param version The version of the Discord API the bot will use + * @param httpOnly Makes your bot an HTTP only bot. This WILL + * break some methods and is only recommended to be set to true if you know what you are doing. Otherwise, leave it to false or don't set it. + * HTTP-only bots (or Interaction-only bots) are bots that do not connect to the gateway, and therefore cannot receive events. They receive + * interactions through POST requests to a specified endpoint of your bot. This is useful if you want to make a bot that only uses slash commands. + * Voice will not work, neither will {@link #setStatus(Status)} & most gateway events. + * Interaction-based events will still be delivered as usual. + * For a full tutorial, see the README.md file. + * @param httpOnlyInfo The information needed to make your bot HTTP only. This is only needed if you set httpOnly to true, otherwise set to null. + * See the above parameter for more information. + * @param debug Should the bot be in debug mode? + * @throws ExecutionException If an error occurs while connecting to the gateway + * @throws InterruptedException If an error occurs while connecting to the gateway + */ + public DiscordJar(String token, EnumSet intents, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo, boolean debug) throws ExecutionException, InterruptedException { System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); new RequestQueueHandler(this); this.token = token; @@ -238,15 +203,6 @@ private DiscordJar(String token, EnumSet intents, APIVersion version, bo RequestMethod.GET )); - this.memberCache = new Cache<>(this, Member.class, new DiscordRequest( - new JSONObject(), - new HashMap<>(), - URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER.replace("{guild.id}", "%s").replace("{user.id}", "%s"), - this, - URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER, - RequestMethod.GET - )); - this.eventDispatcher = new EventDispatcher(this); if (httpOnly) { @@ -655,14 +611,6 @@ public Cache getUserCache() { return userCache; } - /** - * Returns the cache for storing guild members - */ - @NotNull - public Cache getMemberCache() { - return memberCache; - } - /** * Returns the event dispatcher */ diff --git a/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java b/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java deleted file mode 100644 index f3324595..00000000 --- a/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.seailz.discordjar; - -/** - * Creates instances of {@link DiscordJar} with all settings customizable. - */ -public class DiscordJarBuilder { - - -} diff --git a/src/main/java/com/seailz/discordjar/cache/Cache.java b/src/main/java/com/seailz/discordjar/cache/Cache.java index 745a752e..6f39a146 100644 --- a/src/main/java/com/seailz/discordjar/cache/Cache.java +++ b/src/main/java/com/seailz/discordjar/cache/Cache.java @@ -98,16 +98,13 @@ public List getCache() { return cache; } - public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { - return getById(id, null); - } /** * Gets an item from the cache * * @param id The id of the item to get * @return The item */ - public T getById(String id, String id2) throws DiscordRequest.UnhandledDiscordAPIErrorException { + public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { AtomicReference returnObject = new AtomicReference<>(); ArrayList cacheCopy = new ArrayList<>(cache); cacheCopy.forEach(t -> { @@ -134,11 +131,9 @@ public T getById(String id, String id2) throws DiscordRequest.UnhandledDiscordAP if (returnObject.get() == null) { // request from discord - String url = discordRequest.url().replaceAll("%s", id); - if (id2 != null) url = url.replaceAll("%s", id2); DiscordResponse response; response = new DiscordRequest( - discordRequest.body(), discordRequest.headers(), url, discordJar, discordRequest.url(), RequestMethod.GET + discordRequest.body(), discordRequest.headers(), discordRequest.url().replaceAll("%s", id), discordJar, discordRequest.url(), RequestMethod.GET ).invoke(); Method decompile; try { diff --git a/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java b/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java index 5efd23b8..67090b05 100644 --- a/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java +++ b/src/main/java/com/seailz/discordjar/gateway/events/DispatchedEvents.java @@ -68,18 +68,10 @@ public enum DispatchedEvents { Guild guild = Guild.decompile(p.getJSONObject("d"), g); g.getGuildCache().cache(guild); - // Cache all the channels JSONArray arr = p.getJSONObject("d").getJSONArray("channels"); arr.forEach(o -> g.getChannelCache().cache( Channel.decompile((JSONObject) o, g) )); - - // Cache all the members - arr = p.getJSONObject("d").getJSONArray("members"); - arr.forEach(o -> g.getMemberCache().cache( - Member.decompile((JSONObject) o, g, guild.id(), guild) - )); - return GuildCreateEvent.class; }), @@ -248,21 +240,9 @@ public enum DispatchedEvents { return null; }), - GUILD_MEMBER_ADD((p, g, d) -> { - Member member = Member.decompile(p.getJSONObject("d"), d, p.getJSONObject("d").getString("guild_id"), d.getGuildById(p.getJSONObject("d").getString("guild_id"))); - d.getMemberCache().cache(member); - return GuildMemberAddEvent.class; - }), - GUILD_MEMBER_UPDATE((p, g, d) -> { - Member member = Member.decompile(p.getJSONObject("d"), d, p.getJSONObject("d").getString("guild_id"), d.getGuildById(p.getJSONObject("d").getString("guild_id"))); - d.getMemberCache().cache(member); - return GuildMemberUpdateEvent.class; - }), - GUILD_MEMBER_REMOVE((p, g, d) -> { - Member member = Member.decompile(p.getJSONObject("d"), d, p.getJSONObject("d").getString("guild_id"), d.getGuildById(p.getJSONObject("d").getString("guild_id"))); - d.getMemberCache().remove(member); - return GuildMemberRemoveEvent.class; - }), + GUILD_MEMBER_ADD((p, g, d) -> GuildMemberAddEvent.class), + GUILD_MEMBER_UPDATE((p, g, d) -> GuildMemberUpdateEvent.class), + GUILD_MEMBER_REMOVE((p, g, d) -> GuildMemberRemoveEvent.class), /* Unknown */ UNKNOWN((p, g, d) -> null), ; diff --git a/src/main/java/com/seailz/discordjar/model/guild/Guild.java b/src/main/java/com/seailz/discordjar/model/guild/Guild.java index 0573f75f..efb237aa 100644 --- a/src/main/java/com/seailz/discordjar/model/guild/Guild.java +++ b/src/main/java/com/seailz/discordjar/model/guild/Guild.java @@ -670,31 +670,29 @@ public AutomodRuleModifyAction modifyAutomodRule(String id) { return new AutomodRuleModifyAction(id, this, discordJar); } - /** - * Given an id, returns the {@link Member member} object - * @param id The id of the member - * @return A {@link Member} object, usually from the cache, but if the member was not in the cache, it will be requested from the Discord API. - * @throws DiscordRequest.UnhandledDiscordAPIErrorException If a member was not in the cache and there was an error getting the member from the Discord API - */ - @Nullable public Member getMemberById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { - Checker.isSnowflake(id, "Given id is not a snowflake"); - return discordJar.getMemberCache().getById(id, this.id); - } + DiscordResponse req = new DiscordRequest( + new JSONObject(), + new HashMap<>(), + URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER.replace( + "{guild.id}", + this.id + ).replace( + "{user.id}", + id + ), + discordJar, + URLS.GET.GUILDS.MEMBERS.GET_GUILD_MEMBER, + RequestMethod.GET + ).invoke(); - /** - * Returns members from the member cache rather than through the Gateway or REST API - * @return A {@link List} of members from the cache. - */ - @NotNull - public List getMembersFromCache() { - List members = new ArrayList<>(); - for (Member member : discordJar.getMemberCache().getCache()) { - if (member.guildId() != null && member.guildId().equals(id)) { - members.add(member); - } - } - return members; + if (req.body() == null) return null; + return Member.decompile( + req.body(), + discordJar, + this.id, + this + ); } public List getMembers(int limit, String after) throws DiscordRequest.UnhandledDiscordAPIErrorException { From 328e9c52b0800839c8b5556e795cde46ea36ccd4 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 5 May 2023 09:59:50 +0100 Subject: [PATCH 21/59] feat: Added a @RequireCustomId annotation that can be put on listener methods and the listener will not run unless the custom id == the custom id set in the annotation --- .../discordjar/events/EventDispatcher.java | 10 +++++++ .../model/interaction/CustomIdable.java | 10 +++++++ .../button/ButtonInteractionEvent.java | 4 ++- .../modal/ModalInteractionEvent.java | 4 ++- .../StringSelectMenuInteractionEvent.java | 4 ++- .../ChannelSelectMenuInteractionEvent.java | 4 ++- .../RoleSelectMenuInteractionEvent.java | 4 ++- .../UserSelectMenuInteractionEvent.java | 4 ++- .../utils/annotation/RequireCustomId.java | 28 +++++++++++++++++++ 9 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/seailz/discordjar/events/model/interaction/CustomIdable.java create mode 100644 src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 3179b8f9..127c8eb9 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -3,7 +3,10 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.events.annotation.EventMethod; import com.seailz.discordjar.events.model.Event; +import com.seailz.discordjar.events.model.interaction.CustomIdable; +import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.events.model.message.MessageCreateEvent; +import com.seailz.discordjar.utils.annotation.RequireCustomId; import com.seailz.discordjar.utils.rest.DiscordRequest; import java.lang.reflect.Method; @@ -65,6 +68,13 @@ public void dispatchEvent(Event event, Class type, DiscordJar d for (Method i : listeners.values()) { if (i.getParameterCount() == 1 && i.getParameterTypes()[0].equals(type) && Modifier.isPublic(i.getModifiers())) { try { + if (event instanceof CustomIdable) { + if (i.isAnnotationPresent(RequireCustomId.class)) { + if (!((CustomIdable) event).getCustomId().equals(i.getAnnotation(RequireCustomId.class).value())) + continue; + } + } + i.setAccessible(true); i.invoke(listeners.keySet().toArray()[index], event); } catch (Exception e) { diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/CustomIdable.java b/src/main/java/com/seailz/discordjar/events/model/interaction/CustomIdable.java new file mode 100644 index 00000000..79d2faad --- /dev/null +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/CustomIdable.java @@ -0,0 +1,10 @@ +package com.seailz.discordjar.events.model.interaction; + +/** + * Marks an interaction event that has a custom id. + * @author Seailz + * @since 1.0 + */ +public interface CustomIdable { + String getCustomId(); +} diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/button/ButtonInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/button/ButtonInteractionEvent.java index e64c1aec..837cfcbd 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/button/ButtonInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/button/ButtonInteractionEvent.java @@ -2,6 +2,7 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.action.interaction.ModalInteractionCallbackAction; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.model.interaction.callback.InteractionCallbackType; import com.seailz.discordjar.model.interaction.data.message.MessageComponentInteractionData; @@ -12,7 +13,7 @@ import org.jetbrains.annotations.NotNull; import org.json.JSONObject; -public class ButtonInteractionEvent extends InteractionEvent { +public class ButtonInteractionEvent extends InteractionEvent implements CustomIdable { public ButtonInteractionEvent(@NotNull DiscordJar bot, long sequence, @NotNull JSONObject data) { super(bot, sequence, data); // First checks the button registry for any actions that match the custom id. @@ -41,6 +42,7 @@ public MessageComponentInteractionData getInteractionData() { * @return {@link String} object containing the custom id. */ @NotNull + @Override public String getCustomId() { return getInteractionData().customId(); } diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/modal/ModalInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/modal/ModalInteractionEvent.java index 8b41bf7d..91529d9d 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/modal/ModalInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/modal/ModalInteractionEvent.java @@ -2,6 +2,7 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.events.DiscordListener; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.model.component.modal.ResolvedModalComponent; import com.seailz.discordjar.model.interaction.data.modal.ModalSubmitInteractionData; @@ -24,7 +25,7 @@ * @see Modal * @since 1.0 */ -public class ModalInteractionEvent extends InteractionEvent { +public class ModalInteractionEvent extends InteractionEvent implements CustomIdable { public ModalInteractionEvent(@NotNull DiscordJar bot, long sequence, @NotNull JSONObject data) { super(bot, sequence, data); } @@ -49,6 +50,7 @@ public ModalSubmitInteractionData getInteractionData() { * @return {@link String} object containing the custom id. */ @NotNull + @Override public String getCustomId() { return getInteractionData().customId(); } diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/select/StringSelectMenuInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/select/StringSelectMenuInteractionEvent.java index 73bfff61..6e9da571 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/select/StringSelectMenuInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/select/StringSelectMenuInteractionEvent.java @@ -3,6 +3,7 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.action.interaction.ModalInteractionCallbackAction; import com.seailz.discordjar.events.DiscordListener; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.model.component.select.SelectOption; import com.seailz.discordjar.model.component.select.string.StringSelectMenu; @@ -28,7 +29,7 @@ * @see com.seailz.discordjar.model.component.select.string.StringSelectMenu * @since 1.0 */ -public class StringSelectMenuInteractionEvent extends InteractionEvent { +public class StringSelectMenuInteractionEvent extends InteractionEvent implements CustomIdable { public StringSelectMenuInteractionEvent(DiscordJar bot, long sequence, JSONObject data) { super(bot, sequence, data); @@ -66,6 +67,7 @@ public List getSelectedOptions() { * @return {@link String} object containing the custom id. */ @NotNull + @Override public String getCustomId() { return getInteractionData().customId(); } diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/ChannelSelectMenuInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/ChannelSelectMenuInteractionEvent.java index bdc1e832..bd7c6887 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/ChannelSelectMenuInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/ChannelSelectMenuInteractionEvent.java @@ -3,6 +3,7 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.action.interaction.ModalInteractionCallbackAction; import com.seailz.discordjar.events.DiscordListener; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.model.channel.Channel; import com.seailz.discordjar.model.component.select.entity.ChannelSelectMenu; @@ -29,7 +30,7 @@ * @see com.seailz.discordjar.model.component.select.entity.ChannelSelectMenu * @since 1.0 */ -public class ChannelSelectMenuInteractionEvent extends InteractionEvent { +public class ChannelSelectMenuInteractionEvent extends InteractionEvent implements CustomIdable { public ChannelSelectMenuInteractionEvent(DiscordJar bot, long sequence, JSONObject data) { super(bot, sequence, data); @@ -73,6 +74,7 @@ public List getSelectedChannels() { * @return {@link String} object containing the custom id. */ @NotNull + @Override public String getCustomId() { return getInteractionData().customId(); } diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/RoleSelectMenuInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/RoleSelectMenuInteractionEvent.java index abb3f50a..45d16717 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/RoleSelectMenuInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/RoleSelectMenuInteractionEvent.java @@ -3,6 +3,7 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.action.interaction.ModalInteractionCallbackAction; import com.seailz.discordjar.events.DiscordListener; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.model.component.select.entity.RoleSelectMenu; import com.seailz.discordjar.model.interaction.callback.InteractionCallbackType; @@ -28,7 +29,7 @@ * @see com.seailz.discordjar.model.component.select.entity.RoleSelectMenu * @since 1.0 */ -public class RoleSelectMenuInteractionEvent extends InteractionEvent { +public class RoleSelectMenuInteractionEvent extends InteractionEvent implements CustomIdable { public RoleSelectMenuInteractionEvent(DiscordJar bot, long sequence, JSONObject data) { super(bot, sequence, data); @@ -70,6 +71,7 @@ public List getSelectedRoles() { * @return {@link String} object containing the custom id. */ @NotNull + @Override public String getCustomId() { return getInteractionData().customId(); } diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/UserSelectMenuInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/UserSelectMenuInteractionEvent.java index 76dc1eb8..36178c63 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/UserSelectMenuInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/select/entity/UserSelectMenuInteractionEvent.java @@ -3,6 +3,7 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.action.interaction.ModalInteractionCallbackAction; import com.seailz.discordjar.events.DiscordListener; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.model.component.select.entity.UserSelectMenu; import com.seailz.discordjar.model.interaction.callback.InteractionCallbackType; @@ -29,7 +30,7 @@ * @see com.seailz.discordjar.model.component.select.entity.UserSelectMenu * @since 1.0 */ -public class UserSelectMenuInteractionEvent extends InteractionEvent { +public class UserSelectMenuInteractionEvent extends InteractionEvent implements CustomIdable { public UserSelectMenuInteractionEvent(DiscordJar bot, long sequence, JSONObject data) { super(bot, sequence, data); @@ -69,6 +70,7 @@ public List getSelectedUsers() throws DiscordRequest.UnhandledDiscordAPIEr * @return {@link String} object containing the custom id. */ @NotNull + @Override public String getCustomId() { return getInteractionData().customId(); } diff --git a/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java b/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java new file mode 100644 index 00000000..2e9c7dd5 --- /dev/null +++ b/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java @@ -0,0 +1,28 @@ +package com.seailz.discordjar.utils.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * If you annotate a method with this and that method is a interaction event listener method, + *
then the event will only run if the customid of the event == the value of this annotation. + *
Example: + *
+ * + *
@EventMethod + *
@RequireCustomId("my_custom_id") + *
public void onModalInteractionEvent(@NotNull ModalInteractionEvent event) { + *
// This will only run if the custom id of the event is "my_custom_id" + *
event.reply("The custom ID is my_custom_id!").run(); + *
} + *
+ * + * @author Seailz + * @since 1.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({java.lang.annotation.ElementType.METHOD}) +public @interface RequireCustomId { + String value(); +} From d585b63fd51f7d81226942352b8be83ed8ab3f5e Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 5 May 2023 11:48:31 +0100 Subject: [PATCH 22/59] fix: fixed event dispatcher --- .../seailz/discordjar/events/EventDispatcher.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 127c8eb9..9fed3258 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -64,24 +64,23 @@ public void dispatchEvent(Event event, Class type, DiscordJar d } } - int index = 0; - for (Method i : listeners.values()) { - if (i.getParameterCount() == 1 && i.getParameterTypes()[0].equals(type) && Modifier.isPublic(i.getModifiers())) { + for (DiscordListener listener : listeners.keySet()) { + Method method = listeners.get(listener); + if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(type) && Modifier.isPublic(method.getModifiers())) { try { if (event instanceof CustomIdable) { - if (i.isAnnotationPresent(RequireCustomId.class)) { - if (!((CustomIdable) event).getCustomId().equals(i.getAnnotation(RequireCustomId.class).value())) + if (method.isAnnotationPresent(RequireCustomId.class)) { + if (!((CustomIdable) event).getCustomId().equals(method.getAnnotation(RequireCustomId.class).value())) continue; } } - i.setAccessible(true); - i.invoke(listeners.keySet().toArray()[index], event); + method.setAccessible(true); + method.invoke(listener, event); } catch (Exception e) { e.printStackTrace(); } } - index++; } }, "EventDispatcher").start(); } From 84ac83a3ac6de3ac35e21733b0380d09eaaddb5f Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 5 May 2023 14:15:15 +0100 Subject: [PATCH 23/59] fix: Fixed command registration --- .../com/seailz/discordjar/DiscordJar.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index f0f3f384..4f845934 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -686,19 +686,25 @@ public void registerCommands(CommandListener... listeners) throws DiscordRequest Permission[] defaultMemberPermissions = (ann instanceof SlashCommandInfo) ? ((SlashCommandInfo) ann).defaultMemberPermissions() : ((ContextCommandInfo) ann).defaultMemberPermissions(); boolean canUseInDms = (ann instanceof SlashCommandInfo) ? ((SlashCommandInfo) ann).canUseInDms() : ((ContextCommandInfo) ann).canUseInDms(); boolean nsfw = (ann instanceof SlashCommandInfo) ? ((SlashCommandInfo) ann).nsfw() : ((ContextCommandInfo) ann).nsfw(); - registerCommand( - new Command( - name, - listener.getType(), - description, - (listener instanceof SlashCommandListener) ? ((SlashCommandListener) listener).getOptions() : new ArrayList<>(), - nameLocales, - descriptionLocales, - defaultMemberPermissions, - canUseInDms, - nsfw - ) - ); + new Thread(() -> { + try { + registerCommand( + new Command( + name, + listener.getType(), + description, + (listener instanceof SlashCommandListener) ? ((SlashCommandListener) listener).getOptions() : new ArrayList<>(), + nameLocales, + descriptionLocales, + defaultMemberPermissions, + canUseInDms, + nsfw + ) + ); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + throw new RuntimeException(e); + } + }).start(); commandDispatcher.registerCommand(name, listener); if (!(listener instanceof SlashCommandListener slashCommandListener)) continue ; From a211dc043a054ca6340c64f2f8855f29567f43db Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 6 May 2023 15:23:33 +0100 Subject: [PATCH 24/59] fix: Fixed listeners still requiring the EventMethod annotation. --- .../java/com/seailz/discordjar/events/EventDispatcher.java | 4 +--- .../seailz/discordjar/events/annotation/EventMethod.java | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 9fed3258..d84f6787 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -38,9 +38,7 @@ public EventDispatcher(DiscordJar bot) { public void addListener(DiscordListener... listeners) { for (DiscordListener listener : listeners) { for (Method method : listener.getClass().getMethods()) { - if (method.isAnnotationPresent(EventMethod.class)) { - this.listeners.put(listener, method); - } + if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass().equals(Event.class)) this.listeners.put(listener, method); } } } diff --git a/src/main/java/com/seailz/discordjar/events/annotation/EventMethod.java b/src/main/java/com/seailz/discordjar/events/annotation/EventMethod.java index 77d79e90..dc991aec 100644 --- a/src/main/java/com/seailz/discordjar/events/annotation/EventMethod.java +++ b/src/main/java/com/seailz/discordjar/events/annotation/EventMethod.java @@ -6,8 +6,10 @@ import java.lang.annotation.Target; /** - * This annotation is used to mark methods that should be called when an event is fired. - * If a listener method isn't marked with this annotation, it will not be called. + * This annotation is used to mark methods that should be called when an event is fired. + * If a listener method isn't marked with this annotation, it will not be called. + * + * THIS ANNOTATION IS NO LONGER REQUIRED. It exists purely for backwards compatibility. * * @author Seailz * @see com.seailz.discordjar.events.DiscordListener From c908bfac300c38b114015827a3cd11833cf5e539 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 19:53:24 +0100 Subject: [PATCH 25/59] fix: better error handling --- .../channel/CreateGuildChannelAction.java | 21 +++-- .../discordjar/utils/rest/DiscordRequest.java | 56 +++++------ .../discordjar/utils/rest/Response.java | 93 +++++++++++++++++++ 3 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/seailz/discordjar/utils/rest/Response.java diff --git a/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java b/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java index 606f1dd7..d8a27da8 100644 --- a/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java +++ b/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java @@ -9,6 +9,7 @@ import com.seailz.discordjar.utils.URLS; import com.seailz.discordjar.utils.rest.DiscordRequest; import com.seailz.discordjar.utils.rest.DiscordResponse; +import com.seailz.discordjar.utils.rest.Response; import org.json.JSONObject; import org.springframework.web.bind.annotation.RequestMethod; @@ -78,11 +79,11 @@ public Guild getGuild() { return guild; } - public CompletableFuture run() { - CompletableFuture future = new CompletableFuture<>(); - future.completeAsync(() -> { + public Response run() { + Response res = new Response<>(); + new Thread(() -> { try { - return GuildChannel.decompile( + GuildChannel chan = GuildChannel.decompile( new DiscordRequest( new JSONObject() .put("name", name) @@ -99,12 +100,16 @@ public CompletableFuture run() { ).invoke().body(), discordJar ); + res.complete(chan); } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { - future.completeExceptionally(e); + res.completeError(new Response.Error( + e.getCode(), + e.getMessage(), + e.getBody() + )); } - return null; - }); - return future; + }).start(); + return res; } } diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 70b01571..853dfd08 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -236,8 +236,7 @@ else if (requestMethod == RequestMethod.DELETE) { } throw new UnhandledDiscordAPIErrorException( - responseCode, - "Unhandled Discord API Error. Please report this to the developer of DiscordJar." + error + new JSONObject(response.body()) ); } catch (InterruptedException | IOException | URISyntaxException e) { // attempt gateway reconnect @@ -360,38 +359,8 @@ public DiscordResponse invokeWithFiles(File... files) { JSONObject error = new JSONObject(response.body()); - JSONArray errorArray; - - try { - errorArray = error.getJSONArray("errors").getJSONArray(3); - } catch (JSONException e) { - try { - errorArray = error.getJSONArray("errors").getJSONArray(1); - } catch (JSONException ex) { - try { - errorArray = error.getJSONArray("errors").getJSONArray(0); - } catch (JSONException exx) { - throw new UnhandledDiscordAPIErrorException( - responseCode, - "Unhandled Discord API Error. Please report this to the developer of DiscordJar." + error - ); - } - } - } - errorArray.forEach(o -> { - JSONObject errorObject = (JSONObject) o; - try { - throw new DiscordAPIErrorException( - responseCode, - errorObject.getString("code"), - errorObject.getString("message"), - error.toString() - ); - } catch (DiscordAPIErrorException e) { - throw new RuntimeException(e); - } - }); + throw new UnhandledDiscordAPIErrorException(error); } catch (Exception e) { e.printStackTrace(); } @@ -406,8 +375,25 @@ public DiscordAPIErrorException(int code, String errorCode, String error, String } public static class UnhandledDiscordAPIErrorException extends Exception { - public UnhandledDiscordAPIErrorException(int code, String error) { - super("DiscordAPI [Error " + HttpStatus.valueOf(code) + "]: " + error); + private int code; + private JSONObject body; + private String error; + public UnhandledDiscordAPIErrorException(JSONObject body) { + this.body = body.getJSONObject("errors"); + this.code = body.getInt("code"); + this.error = body.getString("message"); + } + + public int getCode() { + return code; + } + + public JSONObject getBody() { + return body; + } + + public String getError() { + return error; } } diff --git a/src/main/java/com/seailz/discordjar/utils/rest/Response.java b/src/main/java/com/seailz/discordjar/utils/rest/Response.java new file mode 100644 index 00000000..2f81108e --- /dev/null +++ b/src/main/java/com/seailz/discordjar/utils/rest/Response.java @@ -0,0 +1,93 @@ +package com.seailz.discordjar.utils.rest; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Response from Discord's API. This is similar to a {@link java.util.concurrent.CompletableFuture CompletableFuture}. + * @see DiscordRequest + * @author Seailz + * @since 1.0.0 + */ +public class Response { + + public T response; + public Error error; + + public List> onCompletion = new ArrayList<>(); + public List> onError = new ArrayList<>(); + + public Response() {} + + public Response complete(T response) { + this.response = response; + for (Consumer tConsumer : onCompletion) { + tConsumer.accept(response); + } + return this; + } + + public Response completeError(Error error) { + this.error = error; + return this; + } + + public Response onCompletion(Consumer consumer) { + onCompletion.add(consumer); + return this; + } + + public Response onError(Consumer consumer) { + onError.add(consumer); + return this; + } + + public Response completeAsync(Supplier response) { + new Thread(() -> { + complete(response.get()); + }); + return this; + } + + /** + * Represents an error received from the Discord API. + *
TODO: Convert this to an enum + */ + public static class Error { + private int code; + private String message; + private JSONObject errors; + + public Error(int code, String message, JSONObject errors) { + this.code = code; + this.message = message; + this.errors = errors; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public JSONObject getErrors() { + return errors; + } + + @Override + public String toString() { + return "Error{" + + "code=" + code + + ", message='" + message + '\'' + + ", errors=" + errors + + '}'; + } + } +} From 061a7367af5ecc7916ba4808fb7e0391e6f2d2e3 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:06:56 +0100 Subject: [PATCH 26/59] feat(error handling): added blocking methods --- .../discordjar/utils/rest/Response.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/Response.java b/src/main/java/com/seailz/discordjar/utils/rest/Response.java index 2f81108e..2526b09f 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/Response.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/Response.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; @@ -18,22 +19,66 @@ public class Response { public T response; public Error error; + public boolean completed = false; public List> onCompletion = new ArrayList<>(); public List> onError = new ArrayList<>(); public Response() {} + /** + * Blocks the current thread until the response is received, or an error occurs. + * @return Either the response or null if an error occurred. + */ + public T awaitCompleted() { + while (!completed) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return response; // Response will be null if an error occurred + } + + /** + * Blocks the current thread until an error occurs, or the response is received. + * @return Either the error or null if the response was received. + */ + public Error awaitError() { + while (!completed) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return error; // Error will be null if no error occurred + } + + public Error getError() { + return error; + } + + public T getResponse() { + return response; + } + public Response complete(T response) { this.response = response; for (Consumer tConsumer : onCompletion) { tConsumer.accept(response); } + completed = true; return this; } public Response completeError(Error error) { this.error = error; + for (Consumer errorConsumer : onError) { + errorConsumer.accept(error); + } + completed = true; return this; } From cdf564a942ed0a1bd43d5971de048098217f4b1f Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:22:05 +0100 Subject: [PATCH 27/59] fix(event dispatching): Fixed an issue that would occur if the superclass was null. --- src/main/java/com/seailz/discordjar/events/EventDispatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index d84f6787..4353b634 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -38,7 +38,7 @@ public EventDispatcher(DiscordJar bot) { public void addListener(DiscordListener... listeners) { for (DiscordListener listener : listeners) { for (Method method : listener.getClass().getMethods()) { - if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass().equals(Event.class)) this.listeners.put(listener, method); + if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().equals(Event.class)) this.listeners.put(listener, method); } } } From d1209f3c8b4352e21b76fe03c2e29c91a48e2e3c Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:29:40 +0100 Subject: [PATCH 28/59] fix(event registering): double check superclass's superclass --- .../java/com/seailz/discordjar/events/EventDispatcher.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 4353b634..a5c61d87 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -38,7 +38,10 @@ public EventDispatcher(DiscordJar bot) { public void addListener(DiscordListener... listeners) { for (DiscordListener listener : listeners) { for (Method method : listener.getClass().getMethods()) { - if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().equals(Event.class)) this.listeners.put(listener, method); + if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass() != null && + method.getParameterTypes()[0].getSuperclass().equals(Event.class) || + (method.getParameterTypes()[0].getSuperclass().getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().getSuperclass().equals(Event.class))) + this.listeners.put(listener, method); } } } From fab7968d1a865d6869b17708a341a889642139d9 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:34:27 +0100 Subject: [PATCH 29/59] fix(event registering): double check superclass's superclass --- .../java/com/seailz/discordjar/events/EventDispatcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index a5c61d87..b5ade39b 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -39,8 +39,8 @@ public void addListener(DiscordListener... listeners) { for (DiscordListener listener : listeners) { for (Method method : listener.getClass().getMethods()) { if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass() != null && - method.getParameterTypes()[0].getSuperclass().equals(Event.class) || - (method.getParameterTypes()[0].getSuperclass().getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().getSuperclass().equals(Event.class))) + (method.getParameterTypes()[0].getSuperclass().equals(Event.class) || + (method.getParameterTypes()[0].getSuperclass().getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().getSuperclass().equals(Event.class)))) this.listeners.put(listener, method); } } From 0f38cccc4bc8746faf5796cba6d5a8041f74760d Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:37:45 +0100 Subject: [PATCH 30/59] fix(event registering): require event method again --- .../java/com/seailz/discordjar/events/EventDispatcher.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index b5ade39b..31041777 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -38,6 +38,10 @@ public EventDispatcher(DiscordJar bot) { public void addListener(DiscordListener... listeners) { for (DiscordListener listener : listeners) { for (Method method : listener.getClass().getMethods()) { + if (method.isAnnotationPresent(EventMethod.class)) { + this.listeners.put(listener, method); + continue; + } if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass() != null && (method.getParameterTypes()[0].getSuperclass().equals(Event.class) || (method.getParameterTypes()[0].getSuperclass().getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().getSuperclass().equals(Event.class)))) From 245b269924bfefc4185308976f1ddaac32545868 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:47:11 +0100 Subject: [PATCH 31/59] fix(event registering): require event method again --- src/main/java/com/seailz/discordjar/events/EventDispatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 31041777..d937171c 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -71,7 +71,7 @@ public void dispatchEvent(Event event, Class type, DiscordJar d for (DiscordListener listener : listeners.keySet()) { Method method = listeners.get(listener); - if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(type) && Modifier.isPublic(method.getModifiers())) { + if ((method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(type) && Modifier.isPublic(method.getModifiers())) || method.isAnnotationPresent(EventMethod.class)) { try { if (event instanceof CustomIdable) { if (method.isAnnotationPresent(RequireCustomId.class)) { From 63e3a79627ef1feef4726537ff61afac2f9af684 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:49:05 +0100 Subject: [PATCH 32/59] fix(event registering): require event method again --- .../java/com/seailz/discordjar/events/EventDispatcher.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index d937171c..0e0291c6 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -89,4 +89,8 @@ public void dispatchEvent(Event event, Class type, DiscordJar d } }, "EventDispatcher").start(); } + + public HashMap getListeners() { + return listeners; + } } From 4edb3373755bc429d4f3e1d87f61798e71d9be0e Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 8 May 2023 20:56:33 +0100 Subject: [PATCH 33/59] fix(event registering): require event method again --- .../discordjar/events/EventDispatcher.java | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 0e0291c6..7b6d55cb 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -3,14 +3,10 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.events.annotation.EventMethod; import com.seailz.discordjar.events.model.Event; -import com.seailz.discordjar.events.model.interaction.CustomIdable; -import com.seailz.discordjar.events.model.interaction.InteractionEvent; import com.seailz.discordjar.events.model.message.MessageCreateEvent; -import com.seailz.discordjar.utils.annotation.RequireCustomId; import com.seailz.discordjar.utils.rest.DiscordRequest; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.HashMap; /** @@ -40,12 +36,7 @@ public void addListener(DiscordListener... listeners) { for (Method method : listener.getClass().getMethods()) { if (method.isAnnotationPresent(EventMethod.class)) { this.listeners.put(listener, method); - continue; } - if (method.getParameterCount() == 1 && method.getParameterTypes()[0].getSuperclass() != null && - (method.getParameterTypes()[0].getSuperclass().equals(Event.class) || - (method.getParameterTypes()[0].getSuperclass().getSuperclass() != null && method.getParameterTypes()[0].getSuperclass().getSuperclass().equals(Event.class)))) - this.listeners.put(listener, method); } } } @@ -69,28 +60,20 @@ public void dispatchEvent(Event event, Class type, DiscordJar d } } - for (DiscordListener listener : listeners.keySet()) { - Method method = listeners.get(listener); - if ((method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(type) && Modifier.isPublic(method.getModifiers())) || method.isAnnotationPresent(EventMethod.class)) { - try { - if (event instanceof CustomIdable) { - if (method.isAnnotationPresent(RequireCustomId.class)) { - if (!((CustomIdable) event).getCustomId().equals(method.getAnnotation(RequireCustomId.class).value())) - continue; - } + int index = 0; + for (Method i : listeners.values()) { + if (i.isAnnotationPresent(EventMethod.class)) { + if (i.getParameterTypes()[0].equals(type)) { + try { + i.setAccessible(true); + i.invoke(listeners.keySet().toArray()[index], event); + } catch (Exception e) { + e.printStackTrace(); } - - method.setAccessible(true); - method.invoke(listener, event); - } catch (Exception e) { - e.printStackTrace(); } } + index++; } }, "EventDispatcher").start(); } - - public HashMap getListeners() { - return listeners; - } -} +} \ No newline at end of file From d9afeccd22de00e1f6e022090a377dd4eef47925 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Wed, 10 May 2023 19:15:05 +0100 Subject: [PATCH 34/59] fix(gateway): Added more prevention methods for duped event dispatches --- .../java/com/seailz/discordjar/gateway/GatewayFactory.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index 456c40a0..29b49a1a 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; @@ -61,6 +62,7 @@ public class GatewayFactory extends TextWebSocketHandler { private boolean readyForMessages = false; public HashMap memberRequestChunks = new HashMap<>(); private final boolean debug; + public UUID uuid = UUID.randomUUID(); public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionException, InterruptedException, DiscordRequest.UnhandledDiscordAPIErrorException { this.discordJar = discordJar; @@ -226,6 +228,10 @@ public void queueMessage(JSONObject payload) { protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { super.handleTextMessage(session, message); JSONObject payload = new JSONObject(message.getPayload()); + if (discordJar.getGateway() != this) { + logger.warning("[DISCORD.JAR] Received a message from a gateway that isn't the main gateway. This is usually a bug, please report it on discord.jar's GitHub with this log message. Payload: " + payload.toString()); + return; + } if (debug) { logger.info("[DISCORD.JAR - DEBUG] Received message: " + payload.toString()); From b43a43824ffe27e6dfd278e12a52a75b16e5e34d Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Wed, 10 May 2023 20:11:34 +0100 Subject: [PATCH 35/59] fix:(messagecreateevent): If guild was null, getGuild() would fail to run. --- .../discordjar/events/model/message/MessageCreateEvent.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/events/model/message/MessageCreateEvent.java b/src/main/java/com/seailz/discordjar/events/model/message/MessageCreateEvent.java index 93784d8b..9e174777 100644 --- a/src/main/java/com/seailz/discordjar/events/model/message/MessageCreateEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/message/MessageCreateEvent.java @@ -5,6 +5,7 @@ import com.seailz.discordjar.model.message.Message; import com.seailz.discordjar.utils.rest.DiscordRequest; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.json.JSONObject; /** @@ -39,12 +40,13 @@ public Message getMessage() { /** * The {@link Guild} the message was sent in - * This shouldn't return null. + * This will return null if the message was sent in a DM. * * @return A {@link Guild} object */ - @NotNull + @Nullable public Guild getGuild() throws DiscordRequest.UnhandledDiscordAPIErrorException { + if (!getJson().getJSONObject("d").has("guild_id") || getJson().getJSONObject("d").isNull("guild_id")) return null; return getBot().getGuildCache().getById((getJson().getJSONObject("d").getString("guild_id"))); } } From 1279372cec04b52b1addace71dcc25a617585aad Mon Sep 17 00:00:00 2001 From: seailz <81972974+seailz@users.noreply.github.com> Date: Thu, 11 May 2023 16:44:52 +0100 Subject: [PATCH 36/59] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1887a70a..a611cedb 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ License info can be found [here](https://github.com/discord-jar/discord.jar/blob Our official Discord server: https://discord.gg/tmvS8A57J4 -## Donations +## Support me :) Ghsponsors Singular badge From dc472d59cbfe6ac0608d3a4521c4f25140be82be Mon Sep 17 00:00:00 2001 From: seailz <81972974+seailz@users.noreply.github.com> Date: Thu, 11 May 2023 16:45:56 +0100 Subject: [PATCH 37/59] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index a611cedb..9a98d7e9 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,3 @@ https://discord.gg/tmvS8A57J4 Ghsponsors Singular badge - - - Kofi Singular badge - From 49b1dabe885d5063b1daa24eea89547fb63ed34b Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 11 May 2023 19:31:59 +0100 Subject: [PATCH 38/59] feat: Renamed replyModal to reply --- .../model/interaction/command/CommandInteractionEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/events/model/interaction/command/CommandInteractionEvent.java b/src/main/java/com/seailz/discordjar/events/model/interaction/command/CommandInteractionEvent.java index 63daca26..3ead9168 100644 --- a/src/main/java/com/seailz/discordjar/events/model/interaction/command/CommandInteractionEvent.java +++ b/src/main/java/com/seailz/discordjar/events/model/interaction/command/CommandInteractionEvent.java @@ -43,7 +43,7 @@ public ApplicationCommandInteractionData getCommandData() { return (ApplicationCommandInteractionData) getInteraction().data(); } - public ModalInteractionCallbackAction replyModal(Modal modal) { + public ModalInteractionCallbackAction reply(Modal modal) { return new ModalInteractionCallbackAction( InteractionCallbackType.MODAL, new InteractionModalResponse(modal.title(), modal.customId(), modal.components()), From e326edfd3733d45026b1c8d4305e6ede21130586 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 11 May 2023 20:39:57 +0100 Subject: [PATCH 39/59] feat(caching): Guild caching improvements --- pom.xml | 2 +- .../channel/CreateGuildChannelAction.java | 27 +- .../com/seailz/discordjar/cache/Cache.java | 4 +- .../discordjar/gateway/GatewayFactory.java | 1 - .../seailz/discordjar/model/guild/Guild.java | 413 ++++++++++++++---- .../discordjar/utils/rest/DiscordRequest.java | 151 ++++--- 6 files changed, 424 insertions(+), 174 deletions(-) diff --git a/pom.xml b/pom.xml index ab93b5c2..0f298ce5 100644 --- a/pom.xml +++ b/pom.xml @@ -123,7 +123,7 @@ com.squareup.okhttp3 okhttp - 5.0.0-alpha.11 + 4.9.1 diff --git a/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java b/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java index d8a27da8..ed455522 100644 --- a/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java +++ b/src/main/java/com/seailz/discordjar/action/guild/channel/CreateGuildChannelAction.java @@ -25,6 +25,7 @@ public class CreateGuildChannelAction { private int position; private List permissionOverwrites; private Category category; + private String categoryId; private final Guild guild; private final DiscordJar discordJar; @@ -35,20 +36,32 @@ public CreateGuildChannelAction(String name, ChannelType type, Guild guild, Disc this.discordJar = discordJar; } - public void setTopic(String topic) { + public CreateGuildChannelAction setTopic(String topic) { this.topic = topic; + return this; } - public void setPosition(int position) { + public CreateGuildChannelAction setPosition(int position) { this.position = position; + return this; } - public void setPermissionOverwrites(List permissionOverwrites) { + public CreateGuildChannelAction setPermissionOverwrites(List permissionOverwrites) { this.permissionOverwrites = permissionOverwrites; + return this; } - public void setCategory(Category category) { + public CreateGuildChannelAction setCategory(Category category) { this.category = category; + return this; + } + + /** + * This is generally not recommended to use. If you set this, it will take priority over {@link #setCategory(Category)}. + */ + public CreateGuildChannelAction setCategoryWithId(String id) { + this.categoryId = id; + return this; } public String getName() { @@ -81,7 +94,11 @@ public Guild getGuild() { public Response run() { Response res = new Response<>(); + new Thread(() -> { + String categoryId = null; + if (this.categoryId != null) categoryId = this.categoryId; + else if (this.category != null) categoryId = this.category.id(); try { GuildChannel chan = GuildChannel.decompile( new DiscordRequest( @@ -91,7 +108,7 @@ public Response run() { .put("topic", topic != null ? topic : JSONObject.NULL) .put("position", position) .put("permission_overwrites", permissionOverwrites) - .put("parent_id", category != null ? category.id() : JSONObject.NULL), + .put("parent_id", categoryId != null ? categoryId : JSONObject.NULL), new HashMap<>(), URLS.POST.GUILDS.CHANNELS.CREATE.replace("{guild.id}", guild.id()), discordJar, diff --git a/src/main/java/com/seailz/discordjar/cache/Cache.java b/src/main/java/com/seailz/discordjar/cache/Cache.java index 6f39a146..2895cefd 100644 --- a/src/main/java/com/seailz/discordjar/cache/Cache.java +++ b/src/main/java/com/seailz/discordjar/cache/Cache.java @@ -119,8 +119,9 @@ public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorExcept if (method.getName().equals("id")) { try { itemId = (String) method.invoke(t); - if (Objects.equals(itemId, id)) + if (Objects.equals(itemId, id)) { returnObject.set(t); + } } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } @@ -129,6 +130,7 @@ public T getById(String id) throws DiscordRequest.UnhandledDiscordAPIErrorExcept } }); + if (returnObject.get() == null) { // request from discord DiscordResponse response; diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index 29b49a1a..efaf5578 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -229,7 +229,6 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) super.handleTextMessage(session, message); JSONObject payload = new JSONObject(message.getPayload()); if (discordJar.getGateway() != this) { - logger.warning("[DISCORD.JAR] Received a message from a gateway that isn't the main gateway. This is usually a bug, please report it on discord.jar's GitHub with this log message. Payload: " + payload.toString()); return; } diff --git a/src/main/java/com/seailz/discordjar/model/guild/Guild.java b/src/main/java/com/seailz/discordjar/model/guild/Guild.java index efb237aa..25f13158 100644 --- a/src/main/java/com/seailz/discordjar/model/guild/Guild.java +++ b/src/main/java/com/seailz/discordjar/model/guild/Guild.java @@ -45,87 +45,287 @@ /** * Represents a guild. - * - * @param id The id of the guild - * @param name The name of the guild - * @param icon The icon hash of the guild - * @param iconHash The icon hash of the guild (included with template object) - * @param splash The splash hash of the guild - * @param discoverySplash The discovery splash hash of the guild - * @param isOwner Whether the user is the owner of the guild - * @param owner The owner of the guild - * @param permissions The total permissions of the user in the guild (excludes overwrites) - * @param afkChannel The afk channel of the guild - * @param afkTimeout The afk timeout of the guild - * @param isWidgetEnabled Whether the widget is enabled for the guild - * @param widgetChannel The widget channel of the guild - * @param verificationLevel The verification level of the guild - * @param defaultMessageNotificationLevel The default message notification level of the guild - * @param explicitContentFilterLevel The explicit content filter level of the guild - * @param roles The roles of the guild - * @param emojis The emojis of the guild - * @param features The features of the guild - * @param mfaLevel The mfa level of the guild - * @param applicationId The application id of the guild - * @param systemChannel The system channel of the guild - * @param maxPresences The maximum presences of the guild - * @param maxMembers The maximum members of the guild - * @param vanityUrlCode The vanity url code of the guild - * @param description The description of the guild - * @param banner The banner hash of the guild - * @param premiumTier The premium tier of the guild - * @param premiumSubscriptionCount The premium subscription count of the guild - * @param preferredLocale The preferred locale of the guild - * @param publicUpdatesChannel The public updates channel of the guild - * @param maxVideoChannelUsers The maximum video channel users of the guild - * @param approximateMemberCount The approximate member count of the guild - * @param approximatePresenceCount The approximate presence count of the guild - * @param welcomeScreen The welcome screen of the guild - * @param stickers The stickers of the guild - * @param premiumProgressBarEnabled Whether the premium progress bar is enabled for the guild */ -public record Guild( - String id, - String name, - String icon, - String iconHash, - String splash, - String discoverySplash, - boolean isOwner, - User owner, - String permissions, - Channel afkChannel, - int afkTimeout, - boolean isWidgetEnabled, - Channel widgetChannel, - VerificationLevel verificationLevel, - DefaultMessageNotificationLevel defaultMessageNotificationLevel, - ExplicitContentFilterLevel explicitContentFilterLevel, - List roles, - List emojis, - EnumSet features, - MFALevel mfaLevel, - String applicationId, - Channel systemChannel, - int maxPresences, - int maxMembers, - String vanityUrlCode, - String description, - String banner, - PremiumTier premiumTier, - int premiumSubscriptionCount, - String preferredLocale, - Channel publicUpdatesChannel, - int maxVideoChannelUsers, - int maxStageVideoChannelUsers, - int approximateMemberCount, - int approximatePresenceCount, - WelcomeScreen welcomeScreen, - List stickers, - boolean premiumProgressBarEnabled, - DiscordJar discordJar, - JsonCache roleCache -) implements Compilerable, Snowflake, CDNAble { +public class Guild implements Compilerable, Snowflake, CDNAble { + private final String id; + private final String name; + private final String icon; + private final String iconHash; + private final String splash; + private final String discoverySplash; + private final boolean isOwner; + private final User owner; + private final String permissions; + private final Channel afkChannel; + private final int afkTimeout; + private final boolean isWidgetEnabled; + private Channel widgetChannel = null; + private final String widgetChannelId; + private final VerificationLevel verificationLevel; + private final DefaultMessageNotificationLevel defaultMessageNotificationLevel; + private final ExplicitContentFilterLevel explicitContentFilterLevel; + private final List roles; + private final List emojis; + private final EnumSet features; + private final MFALevel mfaLevel; + private final String applicationId; + private Channel systemChannel; + private final String systemChannelId; + private final int maxPresences; + private final int maxMembers; + private final String vanityUrlCode; + private final String description; + private final String banner; + private final PremiumTier premiumTier; + private final int premiumSubscriptionCount; + private final String preferredLocale; + private Channel publicUpdatesChannel; + private final String publicUpdatesChannelId; + private final int maxVideoChannelUsers; + private final int maxStageVideoChannelUsers; + private final int approximateMemberCount; + private final int approximatePresenceCount; + private final WelcomeScreen welcomeScreen; + private final List stickers; + private final boolean premiumProgressBarEnabled; + private final DiscordJar discordJar; + private final JsonCache roleCache; + + public Guild( + String id, + String name, + String icon, + String iconHash, + String splash, + String discoverySplash, + boolean isOwner, + User owner, + String permissions, + Channel afkChannel, + int afkTimeout, + boolean isWidgetEnabled, + String widgetChannelId, + VerificationLevel verificationLevel, + DefaultMessageNotificationLevel defaultMessageNotificationLevel, + ExplicitContentFilterLevel explicitContentFilterLevel, + List roles, + List emojis, + EnumSet features, + MFALevel mfaLevel, + String applicationId, + Channel systemChannel, + String systemChannelId, + int maxPresences, + int maxMembers, + String vanityUrlCode, + String description, + String banner, + PremiumTier premiumTier, + int premiumSubscriptionCount, + String preferredLocale, + Channel publicUpdatesChannel, + String publicUpdatesChannelId, + int maxVideoChannelUsers, + int maxStageVideoChannelUsers, + int approximateMemberCount, + int approximatePresenceCount, + WelcomeScreen welcomeScreen, + List stickers, + boolean premiumProgressBarEnabled, + DiscordJar discordJar, + JsonCache roleCache + ) { + this.id = id; + this.name = name; + this.icon = icon; + this.iconHash = iconHash; + this.splash = splash; + this.discoverySplash = discoverySplash; + this.isOwner = isOwner; + this.owner = owner; + this.permissions = permissions; + this.afkChannel = afkChannel; + this.afkTimeout = afkTimeout; + this.isWidgetEnabled = isWidgetEnabled; + this.widgetChannelId = widgetChannelId; + this.verificationLevel = verificationLevel; + this.defaultMessageNotificationLevel = defaultMessageNotificationLevel; + this.explicitContentFilterLevel = explicitContentFilterLevel; + this.roles = roles; + this.emojis = emojis; + this.features = features; + this.mfaLevel = mfaLevel; + this.applicationId = applicationId; + this.systemChannel = systemChannel; + this.systemChannelId = systemChannelId; + this.maxPresences = maxPresences; + this.maxMembers = maxMembers; + this.vanityUrlCode = vanityUrlCode; + this.description = description; + this.banner = banner; + this.premiumTier = premiumTier; + this.premiumSubscriptionCount = premiumSubscriptionCount; + this.preferredLocale = preferredLocale; + this.publicUpdatesChannel = publicUpdatesChannel; + this.publicUpdatesChannelId = publicUpdatesChannelId; + this.maxVideoChannelUsers = maxVideoChannelUsers; + this.maxStageVideoChannelUsers = maxStageVideoChannelUsers; + this.approximateMemberCount = approximateMemberCount; + this.approximatePresenceCount = approximatePresenceCount; + this.welcomeScreen = welcomeScreen; + this.stickers = stickers; + this.premiumProgressBarEnabled = premiumProgressBarEnabled; + this.discordJar = discordJar; + this.roleCache = roleCache; + } + + public String id() { + return id; + } + + public String name() { + return name; + } + + public String icon() { + return icon; + } + + public String iconHash() { + return iconHash; + } + + public String splash() { + return splash; + } + + public String discoverySplash() { + return discoverySplash; + } + + public boolean isOwner() { + return isOwner; + } + + public User owner() { + return owner; + } + + public String permissions() { + return permissions; + } + + public Channel afkChannel() { + return afkChannel; + } + + public int afkTimeout() { + return afkTimeout; + } + + public boolean isWidgetEnabled() { + return isWidgetEnabled; + } + + public VerificationLevel verificationLevel() { + return verificationLevel; + } + + public DefaultMessageNotificationLevel defaultMessageNotificationLevel() { + return defaultMessageNotificationLevel; + } + + public ExplicitContentFilterLevel explicitContentFilterLevel() { + return explicitContentFilterLevel; + } + + public List emojis() { + return emojis; + } + + public EnumSet features() { + return features; + } + + public MFALevel mfaLevel() { + return mfaLevel; + } + + public String applicationId() { + return applicationId; + } + + public String systemChannelId() { + return systemChannelId; + } + + public int maxPresences() { + return maxPresences; + } + + public int maxMembers() { + return maxMembers; + } + + public String vanityUrlCode() { + return vanityUrlCode; + } + + public String description() { + return description; + } + + public String banner() { + return banner; + } + + public PremiumTier premiumTier() { + return premiumTier; + } + + public int premiumSubscriptionCount() { + return premiumSubscriptionCount; + } + + public String preferredLocale() { + return preferredLocale; + } + + public int maxVideoChannelUsers() { + return maxVideoChannelUsers; + } + + public int maxStageVideoChannelUsers() { + return maxStageVideoChannelUsers; + } + + public int approximateMemberCount() { + return approximateMemberCount; + } + + public int approximatePresenceCount() { + return approximatePresenceCount; + } + + public WelcomeScreen welcomeScreen() { + return welcomeScreen; + } + + public List stickers() { + return stickers; + } + + public boolean premiumProgressBarEnabled() { + return premiumProgressBarEnabled; + } + + public DiscordJar discordJar() { + return discordJar; + } + + public JsonCache roleCache() { + return roleCache; + } @Override @@ -152,7 +352,7 @@ public JSONObject compile() { .put("features", features) .put("mfa_level", mfaLevel.getCode()) .put("application_id", applicationId) - .put("system_channel_id", systemChannel.id()) + .put("system_channel_id", systemChannelId) .put("max_presences", maxPresences) .put("max_members", maxMembers) .put("vanity_url_code", vanityUrlCode) @@ -161,7 +361,7 @@ public JSONObject compile() { .put("premium_tier", premiumTier.getCode()) .put("premium_subscription_count", premiumSubscriptionCount) .put("preferred_locale", preferredLocale) - .put("public_updates_channel_id", publicUpdatesChannel.id()) + .put("public_updates_channel_id", publicUpdatesChannelId) .put("max_video_channel_users", maxVideoChannelUsers) .put("max_stage_video_channel_users", maxStageVideoChannelUsers) .put("approximate_member_count", approximateMemberCount) @@ -173,6 +373,7 @@ public JSONObject compile() { @NotNull public static Guild decompile(JSONObject obj, DiscordJar discordJar) { + long nano = System.nanoTime(); String id; String name; String icon; @@ -185,7 +386,7 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { Channel afkChannel; int afkTimeout; boolean isWidgetEnabled; - Channel widgetChannel; + String widgetChannelId; VerificationLevel verificationLevel; DefaultMessageNotificationLevel defaultMessageNotificationLevel; ExplicitContentFilterLevel explicitContentFilterLevel; @@ -194,7 +395,7 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { EnumSet features; MFALevel mfaLevel; String applicationId; - Channel systemChannel; + String systemChannelId; int maxPresences; int maxMembers; String vanityUrlCode; @@ -203,7 +404,7 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { PremiumTier premiumTier; int premiumSubscriptionCount; String preferredLocale; - Channel publicUpdatesChannel; + String publicUpdatesChannelId; int maxVideoChannelUsers; int maxStageVideoChannelUsers = 0; int approximateMemberCount; @@ -285,9 +486,9 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { } try { - widgetChannel = discordJar.getChannelById(obj.getString("widget_channel_id")); + widgetChannelId =obj.getString("widget_channel_id"); } catch (JSONException e) { - widgetChannel = null; + widgetChannelId = null; } try { @@ -347,9 +548,9 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { } try { - systemChannel = discordJar.getChannelById(obj.getString("system_channel_id")); + systemChannelId = obj.getString("system_channel_id"); } catch (IllegalArgumentException | JSONException e) { - systemChannel = null; + systemChannelId = null; } try { @@ -401,10 +602,9 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { } try { - publicUpdatesChannel = - discordJar.getChannelById(obj.getString("public_updates_channel_id")); + publicUpdatesChannelId = obj.getString("public_updates_channel_id"); } catch (Exception e) { - publicUpdatesChannel = null; + publicUpdatesChannelId = null; } try { @@ -466,7 +666,7 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { afkChannel, afkTimeout, isWidgetEnabled, - widgetChannel, + widgetChannelId, verificationLevel, defaultMessageNotificationLevel, explicitContentFilterLevel, @@ -475,7 +675,8 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { features, mfaLevel, applicationId, - systemChannel, + null, + systemChannelId, maxPresences, maxMembers, vanityUrlCode, @@ -484,7 +685,8 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { premiumTier, premiumSubscriptionCount, preferredLocale, - publicUpdatesChannel, + null, + publicUpdatesChannelId, maxVideoChannelUsers, maxStageVideoChannelUsers, approximateMemberCount, @@ -506,6 +708,27 @@ public static Guild decompile(JSONObject obj, DiscordJar discordJar) { return g; } + public Channel systemChannel() { + if (systemChannelId == null) return null; + if (this.systemChannel != null) return this.systemChannel; + this.systemChannel = discordJar.getChannelById(systemChannelId); + return this.systemChannel; + } + + public Channel publicUpdatesChannel() { + if (publicUpdatesChannelId == null) return null; + if (this.publicUpdatesChannel != null) return this.publicUpdatesChannel; + this.publicUpdatesChannel = discordJar.getChannelById(publicUpdatesChannelId); + return this.publicUpdatesChannel; + } + + public Channel widgetChannel() { + if (widgetChannelId == null) return null; + if (this.widgetChannel != null) return this.widgetChannel; + this.widgetChannel = discordJar.getChannelById(widgetChannelId); + return this.widgetChannel; + } + /** * Leaves a guild */ diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 853dfd08..6566340b 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -3,6 +3,8 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.utils.URLS; import com.seailz.discordjar.utils.rest.ratelimit.Bucket; +import okhttp3.*; +import okhttp3.Response; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -88,19 +90,15 @@ public void queueRequest(double resetAfter, Bucket bucket) { private DiscordResponse invoke(String contentType, boolean auth) throws UnhandledDiscordAPIErrorException { try { String url = URLS.BASE_URL + this.url; - URL obj = new URL(url); - - HttpRequest.Builder con = HttpRequest.newBuilder(); - con.uri(obj.toURI()); + OkHttpClient client = new OkHttpClient(); + Request.Builder requestBuilder = new Request.Builder().url(url); boolean useBaseUrlForRateLimit = !url.contains("channels") && !url.contains("guilds"); Bucket bucket = djv.getBucketForUrl(url); if (bucket != null) { - if (bucket.getRemaining() == 0 ) { + if (bucket.getRemaining() == 0) { if (djv.isDebug()) { - Logger.getLogger("RATELIMIT").info( - "[RATE LIMIT] Request has been rate-limited. It has been queued." - ); + Logger.getLogger("RATELIMIT").info("[RATE LIMIT] Request has been rate-limited. It has been queued."); } queueRequest(bucket.getResetAfter(), bucket); return new DiscordResponse(429, null, null, null); @@ -108,75 +106,94 @@ private DiscordResponse invoke(String contentType, boolean auth) throws Unhandle } String s = body != null ? body.toString() : aBody.toString(); + RequestBody requestBody = null; + + if (contentType == null) { + contentType = "application/json"; + } + if (requestMethod == RequestMethod.POST) { - con.POST(HttpRequest.BodyPublishers.ofString(s)); + requestBody = RequestBody.create(MediaType.parse(contentType), s); + requestBuilder.post(requestBody); } else if (requestMethod == RequestMethod.PATCH) { - con.method("PATCH", HttpRequest.BodyPublishers.ofString(s)); + requestBody = RequestBody.create(MediaType.parse(contentType), s); + requestBuilder.patch(requestBody); } else if (requestMethod == RequestMethod.PUT) { - con.method("PUT", HttpRequest.BodyPublishers.ofString(s)); - } - else if (requestMethod == RequestMethod.DELETE) { - con.method("DELETE", HttpRequest.BodyPublishers.ofString(s)); + requestBody = RequestBody.create(MediaType.parse(contentType), s); + requestBuilder.put(requestBody); + } else if (requestMethod == RequestMethod.DELETE) { + requestBody = RequestBody.create(MediaType.parse(contentType), s); + requestBuilder.delete(requestBody); } else if (requestMethod == RequestMethod.GET) { - con.GET(); + requestBuilder.get(); } else { - con.method(requestMethod.name(), HttpRequest.BodyPublishers.ofString(s)); + requestBody = RequestBody.create(MediaType.parse(contentType), s); + requestBuilder.method(requestMethod.name(), requestBody); } - con.header("User-Agent", "discord.jar (https://github.com/discord-jar/, 1.0.0)"); - if (auth) con.header("Authorization", "Bot " + djv.getToken()); - if (contentType == null) con.header("Content-Type", "application/json"); - if (contentType != null) con.header("Content-Type", contentType); // switch to url search params - headers.forEach(con::header); + requestBuilder.addHeader("User-Agent", "discord.jar (https://github.com/discord-jar/, 1.0.0)"); + if (auth) { + requestBuilder.addHeader("Authorization", "Bot " + djv.getToken()); + } + if (contentType == null) { + requestBuilder.addHeader("Content-Type", "application/json"); + } + if (contentType != null) { + requestBuilder.addHeader("Content-Type", contentType); + } + headers.forEach((key, value) -> requestBuilder.addHeader(key, value)); - HttpRequest request = con.build(); - HttpClient client = HttpClient.newHttpClient(); + Request request = requestBuilder.build(); + Response response = client.newCall(request).execute(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - int responseCode = response.statusCode(); + int responseCode = response.code(); + String sb = response.body().string(); if (djv.isDebug()) { - System.out.println(request.method() + " " + request.uri() + " with " + body + " returned " + responseCode + " with " + response.body()); + System.out.println(request.method() + " " + request.url() + " with " + body + " returned " + responseCode + " with " + sb); } HashMap headers = new HashMap<>(); - response.headers().map().forEach((key, value) -> headers.put(key, value.get(0))); + Headers responseHeaders = response.headers(); + for (String name : responseHeaders.names()) { + headers.put(name, responseHeaders.get(name)); + } // check headers for rate-limit - if (response.headers().firstValue(("X-RateLimit-Bucket")).isPresent()) { - String bucketId = response.headers().firstValue("X-RateLimit-Bucket").get(); + if (responseHeaders.get("X-RateLimit-Bucket") != null) { + String bucketId = responseHeaders.get("X-RateLimit-Bucket"); Bucket buck = djv.getBucket(bucketId); List affectedRoutes = buck == null ? new ArrayList<>() : new ArrayList<>(buck.getAffectedRoutes()); - if (useBaseUrlForRateLimit) affectedRoutes.add(baseUrl); - else affectedRoutes.add(url); + if (useBaseUrlForRateLimit) { + affectedRoutes.add(baseUrl); + } else { + affectedRoutes.add(url); + } djv.updateBucket(bucketId, new Bucket( - bucketId, Integer.parseInt(response.headers().firstValue("X-RateLimit-Remaining").get()), - Double.parseDouble(response.headers().firstValue( - "X-RateLimit-Reset" - ).get()) + bucketId, + Integer.parseInt(responseHeaders.get("X-RateLimit-Remaining")), + Double.parseDouble(responseHeaders.get("X-RateLimit-Reset")) ).setAffectedRoutes(affectedRoutes)); } - try { - if (response.body().startsWith("[")) { - new JSONArray(response.body()); + String responseBody = sb; + if (responseBody.startsWith("[")) { + new JSONArray(responseBody); } else { - new JSONObject(response.body()); + new JSONObject(responseBody); } } catch (JSONException err) { - System.out.println(response.body()); + System.out.println(sb); } if (responseCode == 429) { if (djv.isDebug()) { - Logger.getLogger("RateLimit").warning("[RATE LIMIT] Rate limit has been exceeded. Please make sure" + - " you are not sending too many requests."); + Logger.getLogger("RateLimit").warning("[RATE LIMIT] Rate limit has been exceeded. Please make sure you are not sending too many requests."); } - JSONObject body = new JSONObject(response.body()); - //queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); - + JSONObject body = new JSONObject(sb); if (body.has("retry_after")) { + String finalContentType = contentType; new Thread(() -> { try { Thread.sleep((long) (body.getFloat("retry_after") * 1000)); @@ -184,61 +201,53 @@ else if (requestMethod == RequestMethod.DELETE) { e.printStackTrace(); } try { - invoke(contentType, auth); + invoke(finalContentType, auth); } catch (UnhandledDiscordAPIErrorException e) { throw new RuntimeException(e); } }).start(); } else { - if (response.headers().firstValue("X-RateLimit-Bucket").isEmpty() || response.headers().firstValue("X-RateLimit-Reset").isEmpty()) { + if (responseHeaders.get("X-RateLimit-Bucket") == null || responseHeaders.get("X-RateLimit-Reset") == null) { return new DiscordResponse(429, null, null, null); } - Bucket exceededBucket = djv.getBucket(response.headers().firstValue("X-RateLimit-Bucket").get()); - queueRequest(Double.parseDouble(response.headers().firstValue("X-RateLimit-Reset").get()), exceededBucket); + Bucket exceededBucket = djv.getBucket(responseHeaders.get("X-RateLimit-Bucket")); + queueRequest(Double.parseDouble(responseHeaders.get("X-RateLimit-Reset")), exceededBucket); } if (body.getBoolean("global")) { - Logger.getLogger("RateLimit").severe( - "[RATE LIMIT] This seems to be a global rate limit. If you are not sending a huge amount" + - " of requests unexpectedly, and your bot is in a lot of servers (100k+), contact Discord" + - " developer support." - ); + Logger.getLogger("RateLimit").severe("[RATE LIMIT] This seems to be a global rate limit. If you are not sending a huge amount of requests unexpectedly, and your bot is in a lot of servers (100k+), contact Discord developer support."); } return new DiscordResponse(429, null, null, null); } if (responseCode == 200 || responseCode == 201) { - - var body = new Object(); - - if (response.body().startsWith("[")) { - body = new JSONArray(response.body()); + Object body; + String responseBody = sb; + if (responseBody.startsWith("[")) { + body = new JSONArray(responseBody); } else { - body = new JSONObject(response.body()); + body = new JSONObject(responseBody); } return new DiscordResponse(responseCode, (body instanceof JSONObject) ? (JSONObject) body : null, headers, (body instanceof JSONArray) ? (JSONArray) body : null); } - if (responseCode == 204) return null; - - if (responseCode == 201) return null; + if (responseCode == 204) { + return null; + } - if (!auth && responseCode == 401) { + if (responseCode == 401 && !auth) { return new DiscordResponse(401, null, null, null); } - JSONObject error = new JSONObject(response.body()); + JSONObject error = new JSONObject(sb); JSONArray errorArray; if (responseCode == 404) { - Logger.getLogger("DISCORDJAR") - .warning("Received 404 error from the Discord API. It's likely that you're trying to access a resource that doesn't exist."); + Logger.getLogger("DISCORDJAR").warning("Received 404 error from the Discord API. It's likely that you're trying to access a resource that doesn't exist."); return new DiscordResponse(404, null, null, null); } - throw new UnhandledDiscordAPIErrorException( - new JSONObject(response.body()) - ); - } catch (InterruptedException | IOException | URISyntaxException e) { + throw new UnhandledDiscordAPIErrorException(new JSONObject(sb)); + } catch (IOException e) { // attempt gateway reconnect throw new DiscordUnexpectedError(e); } From e217ef8b7628e3cfe96be27695c17c6f7724c371 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Thu, 11 May 2023 21:02:25 +0100 Subject: [PATCH 40/59] feat: update --- .../com/seailz/discordjar/DiscordJar.java | 4 ++++ .../discordjar/gateway/GatewayFactory.java | 21 ++++--------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index 4f845934..e333c723 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -312,6 +312,10 @@ protected void initiateShutdownHooks() { })); } + public void setGatewayFactory(GatewayFactory gatewayFactory) { + this.gatewayFactory = gatewayFactory; + } + public List getBuckets() { return buckets; } diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index efaf5578..591f41f1 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -68,23 +68,7 @@ public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionExce this.discordJar = discordJar; this.debug = debug; - new Thread(() -> { - if (discordJar.gatewayConnections > 0) { - // Kill all other connections - for (GatewayFactory gatewayFactory : discordJar.gatewayFactories) { - if (gatewayFactory != null && gatewayFactory.session.isOpen()) { - try { - gatewayFactory.killConnection(); - discordJar.gatewayFactories.remove(gatewayFactory); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - } - - discordJar.gatewayConnections = 1; - }).start(); + discordJar.setGatewayFactory(this); DiscordResponse response = new DiscordRequest( new JSONObject(), @@ -106,10 +90,12 @@ public void connect(String customUrl) throws ExecutionException, InterruptedExce if (debug) { logger.info("[DISCORD.JAR - DEBUG] Gateway connection established."); } + discordJar.setGatewayFactory(this); } public void connect() throws ExecutionException, InterruptedException { connect(gatewayUrl); + } @Override @@ -229,6 +215,7 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) super.handleTextMessage(session, message); JSONObject payload = new JSONObject(message.getPayload()); if (discordJar.getGateway() != this) { + logger.info("[DISCORD.JAR] Received message from a gateway that isn't the main gateway. This is usually a bug, please report it on discord.jar's GitHub with this log message. Payload: " + payload.toString()); return; } From b3547cc9dd290b7af501432c59ef3525cc7c2459 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 05:58:49 +0100 Subject: [PATCH 41/59] feat: update --- .../com/seailz/discordjar/utils/rest/DiscordRequest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 6566340b..16525211 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -225,7 +225,11 @@ private DiscordResponse invoke(String contentType, boolean auth) throws Unhandle if (responseBody.startsWith("[")) { body = new JSONArray(responseBody); } else { - body = new JSONObject(responseBody); + try { + body = new JSONObject(responseBody); + } catch (JSONException err) { + throw new DiscordUnexpectedError(new RuntimeException("Invalid JSON response from Discord API: " + responseBody)); + } } return new DiscordResponse(responseCode, (body instanceof JSONObject) ? (JSONObject) body : null, headers, (body instanceof JSONArray) ? (JSONArray) body : null); From e08a29c749da0c530c8eef189300a8fcad1138b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 05:10:12 +0000 Subject: [PATCH 42/59] build(deps): bump okhttp from 4.9.1 to 4.11.0 Bumps [okhttp](https://github.com/square/okhttp) from 4.9.1 to 4.11.0. - [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md) - [Commits](https://github.com/square/okhttp/compare/parent-4.9.1...parent-4.11.0) --- updated-dependencies: - dependency-name: com.squareup.okhttp3:okhttp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0f298ce5..cb81c9c0 100644 --- a/pom.xml +++ b/pom.xml @@ -123,7 +123,7 @@ com.squareup.okhttp3 okhttp - 4.9.1 + 4.11.0 From 0067eca02daaeb5c92e2578ec4afbed2eb729062 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 06:55:02 +0100 Subject: [PATCH 43/59] feat: update --- src/main/java/com/seailz/discordjar/DiscordJar.java | 2 +- src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index e333c723..a836b48f 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -222,7 +222,7 @@ public DiscordJar(String token, EnumSet intents, APIVersion version, boo throw new RuntimeException(e); } - if (gatewayFactory == null || !gatewayFactory.getSession().isOpen()) { + if ((gatewayFactory == null || !gatewayFactory.getSession().isOpen())) { restartGateway(); } } diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index 591f41f1..54c73ebb 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -326,7 +326,7 @@ public void killConnection() throws IOException { heartbeatManager = null; readyForMessages = false; // close connection - session.close(CloseStatus.SERVER_ERROR); + if (session != null) session.close(CloseStatus.SERVER_ERROR); if (debug) { logger.info("[DISCORD.JAR - DEBUG] Connection closed."); From 2f5f0df37f59a2031285d906c631de63869eb317 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 07:13:50 +0100 Subject: [PATCH 44/59] feat: update --- src/main/java/com/seailz/discordjar/DiscordJar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index a836b48f..f8f80a3d 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -222,7 +222,7 @@ public DiscordJar(String token, EnumSet intents, APIVersion version, boo throw new RuntimeException(e); } - if ((gatewayFactory == null || !gatewayFactory.getSession().isOpen())) { + if (gatewayFactory == null || (gatewayFactory.getSession() != null && !gatewayFactory.getSession().isOpen())) { restartGateway(); } } From 185beb0189095933f465f784fedf88e8423d0aa1 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 16:09:04 +0100 Subject: [PATCH 45/59] feat(builder): Added a builder class to discord.jar --- .../com/seailz/discordjar/DiscordJar.java | 39 ++++++++- .../seailz/discordjar/DiscordJarBuilder.java | 86 +++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/seailz/discordjar/DiscordJarBuilder.java diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index f8f80a3d..a2f1de8c 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -53,7 +53,9 @@ import java.util.logging.Logger; /** - * The main class of the discord.jar wrapper for the Discord API. + * The main class of the discord.jar wrapper for the Discord API. It is HIGHLY recommended that you use + * {@link DiscordJarBuilder} for creating new instances of this class as the other constructors are deprecated + * and will be set to protected/removed in the future. * * @author Seailz * @since 1.0 @@ -117,22 +119,42 @@ public class DiscordJar { public int gatewayConnections = 0; public List gatewayFactories = new ArrayList<>(); + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, EnumSet intents, APIVersion version) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, false); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, EnumSet intents, APIVersion version, boolean debug) throws ExecutionException, InterruptedException { this(token, intents, version, false, null, debug); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, APIVersion version) throws ExecutionException, InterruptedException { this(token, EnumSet.of(Intent.ALL), version, false, null, false); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) throws ExecutionException, InterruptedException { this(token, EnumSet.noneOf(Intent.class), version, httpOnly, httpOnlyInfo, false); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) throws ExecutionException, InterruptedException { this(token, EnumSet.noneOf(Intent.class), APIVersion.getLatest(), httpOnly, httpOnlyInfo, false); } @@ -156,7 +178,10 @@ public DiscordJar(String token, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo) thr * @param debug Should the bot be in debug mode? * @throws ExecutionException If an error occurs while connecting to the gateway * @throws InterruptedException If an error occurs while connecting to the gateway + * + * @deprecated Use {@link DiscordJarBuilder} instead. This constructor will be set to protected in the future. */ + @Deprecated public DiscordJar(String token, EnumSet intents, APIVersion version, boolean httpOnly, HTTPOnlyInfo httpOnlyInfo, boolean debug) throws ExecutionException, InterruptedException { System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); new RequestQueueHandler(this); @@ -229,14 +254,26 @@ public DiscordJar(String token, EnumSet intents, APIVersion version, boo }).start(); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token) throws ExecutionException, InterruptedException { this(token, EnumSet.of(Intent.ALL), APIVersion.getLatest()); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, boolean debug) throws ExecutionException, InterruptedException { this(token, EnumSet.of(Intent.ALL), APIVersion.getLatest(), debug); } + /** + * @deprecated Use {@link DiscordJarBuilder} instead. + */ + @Deprecated(forRemoval = true) public DiscordJar(String token, EnumSet intents) throws ExecutionException, InterruptedException { this(token, intents, APIVersion.getLatest()); } diff --git a/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java b/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java new file mode 100644 index 00000000..71025534 --- /dev/null +++ b/src/main/java/com/seailz/discordjar/DiscordJarBuilder.java @@ -0,0 +1,86 @@ +package com.seailz.discordjar; + +import com.seailz.discordjar.model.application.Intent; +import com.seailz.discordjar.utils.HTTPOnlyInfo; +import com.seailz.discordjar.utils.version.APIVersion; + +import java.util.EnumSet; +import java.util.concurrent.ExecutionException; + +/** + * Factory class for creating a {@link DiscordJar} instance. + * + * @author Seailz + * @since 1.0.0 + */ +public class DiscordJarBuilder { + + private final String token; + private EnumSet intents; + private APIVersion apiVersion = APIVersion.getLatest(); + private boolean httpOnly; + private HTTPOnlyInfo httpOnlyInfo; + private boolean debug; + + public DiscordJarBuilder(String token) { + this.token = token; + } + + public DiscordJarBuilder setIntents(EnumSet intents) { + this.intents = intents; + return this; + } + + /** + * Resets back to default intents. + */ + public DiscordJarBuilder defaultIntents() { + if (this.intents == null) this.intents = EnumSet.noneOf(Intent.class); + this.intents.clear(); + this.intents.add(Intent.ALL); + return this; + } + + public DiscordJarBuilder addIntent(Intent intent) { + if (this.intents == null) this.intents = EnumSet.noneOf(Intent.class); + this.intents.add(intent); + return this; + } + + public DiscordJarBuilder removeIntent(Intent intent) { + if (this.intents == null) return this; + this.intents.remove(intent); + return this; + } + + public DiscordJarBuilder setAPIVersion(APIVersion apiVersion) { + this.apiVersion = apiVersion; + return this; + } + + public DiscordJarBuilder setHTTPOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + return this; + } + + public DiscordJarBuilder setHTTPOnlyInfo(HTTPOnlyInfo httpOnlyInfo) { + this.httpOnlyInfo = httpOnlyInfo; + return this; + } + + public DiscordJarBuilder setDebug(boolean debug) { + this.debug = debug; + return this; + } + + @SuppressWarnings("deprecation") // Deprecation warning is suppressed here because the intended use of that constructor is here. + public DiscordJar build() throws ExecutionException, InterruptedException { + if (intents == null) defaultIntents(); + if (httpOnly && httpOnlyInfo == null) throw new IllegalStateException("HTTPOnly is enabled but no HTTPOnlyInfo was provided."); + return new DiscordJar(token, intents, apiVersion, httpOnly, httpOnlyInfo, debug); + } + + + + +} From 2f9d350d06b847f6465d29b4de7543a4df8423af Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 16:15:41 +0100 Subject: [PATCH 46/59] doc(README): updated README.md to reflect this change --- README.md | 19 ++++++++++++------- .../seailz/discordjar/DiscordJarBuilder.java | 8 ++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9a98d7e9..08351105 100644 --- a/README.md +++ b/README.md @@ -40,15 +40,19 @@ account [here](https://discord.com/developers/applications). To initialize a bot that uses the gateway (is able to receive events), you can use the following code: ```java -new DiscordJar("token"); + +DiscordJar djar = new DiscordJarBuilder("token").build(); ``` You can specify intents to use with the gateway by using the following code: ```java -new DiscordJar("token", EnumSet.of(Intent.GUILDS, Intent.GUILD_MESSAGES)); +DiscordJar djar = new DiscordJarBuilder("token") + .addIntents(Intent.GUILDS, Intent.GUILD_MESSAGES).build(); ``` +Doing so will override the default intents. + Note: You can use the `Intent.ALL` constant to specify all intents. This does not include privileged intents. ### Creating an HTTP-Only bot @@ -57,11 +61,12 @@ To make your bot an Date: Fri, 12 May 2023 16:22:58 +0100 Subject: [PATCH 47/59] fix(transcripts): added a fix for transcripts where if an author's image hash was invalid, then attempting to use CDNAble#imageUrl would throw a NullPointerException --- src/main/java/com/seailz/discordjar/model/user/User.java | 5 +++++ src/main/java/com/seailz/discordjar/utils/CDNAble.java | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/com/seailz/discordjar/model/user/User.java b/src/main/java/com/seailz/discordjar/model/user/User.java index 3ece8c75..f659b789 100644 --- a/src/main/java/com/seailz/discordjar/model/user/User.java +++ b/src/main/java/com/seailz/discordjar/model/user/User.java @@ -235,4 +235,9 @@ public String getMentionablePrefix() { public StringFormatter formatter() { return new StringFormatter("avatars", id, avatarHash()); } + + @Override + public String iconHash() { + return avatarHash; + } } diff --git a/src/main/java/com/seailz/discordjar/utils/CDNAble.java b/src/main/java/com/seailz/discordjar/utils/CDNAble.java index 2dc346e5..2c0c70d5 100644 --- a/src/main/java/com/seailz/discordjar/utils/CDNAble.java +++ b/src/main/java/com/seailz/discordjar/utils/CDNAble.java @@ -9,8 +9,10 @@ public interface CDNAble { StringFormatter formatter(); + String iconHash(); default String imageUrl() { + if (iconHash() == null) return null; return formatter().format("https://cdn.discordapp.com/{0}/{1}/{2}.png"); } From ce3fc3c86605d9eafdfdde258d2dff60d918c141 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 19:13:39 +0100 Subject: [PATCH 48/59] feat(response): updated response class --- .../discordjar/utils/rest/Response.java | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/Response.java b/src/main/java/com/seailz/discordjar/utils/rest/Response.java index 2526b09f..d1c81262 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/Response.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/Response.java @@ -1,11 +1,10 @@ package com.seailz.discordjar.utils.rest; -import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; @@ -17,9 +16,9 @@ */ public class Response { - public T response; - public Error error; - public boolean completed = false; + public volatile T response; + public volatile Error error; + public volatile boolean completed = false; public List> onCompletion = new ArrayList<>(); public List> onError = new ArrayList<>(); @@ -31,16 +30,21 @@ public Response() {} * @return Either the response or null if an error occurred. */ public T awaitCompleted() { - while (!completed) { + while(true) { + synchronized(this) { + if (this.completed) { + break; + } + } try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); + Thread.sleep(1L); + } catch (InterruptedException var2) { + var2.printStackTrace(); } } - return response; // Response will be null if an error occurred - } + return this.response; + } /** * Blocks the current thread until an error occurs, or the response is received. * @return Either the error or null if the response was received. @@ -65,11 +69,17 @@ public T getResponse() { } public Response complete(T response) { - this.response = response; - for (Consumer tConsumer : onCompletion) { - tConsumer.accept(response); + synchronized(this) { + this.response = response; + Iterator var2 = this.onCompletion.iterator(); + + while(var2.hasNext()) { + Consumer tConsumer = (Consumer) var2.next(); + tConsumer.accept(response); + } + + this.completed = true; } - completed = true; return this; } @@ -95,7 +105,7 @@ public Response onError(Consumer consumer) { public Response completeAsync(Supplier response) { new Thread(() -> { complete(response.get()); - }); + }).start(); return this; } From 5b923ac7b9b7904fdb7d003d5a029f9fdae810f1 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 19:30:44 +0100 Subject: [PATCH 49/59] feat(response): Response.java now uses completeable futures instead of implementing that logic itself. --- .../discordjar/utils/rest/Response.java | 80 ++++--------------- 1 file changed, 15 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/Response.java b/src/main/java/com/seailz/discordjar/utils/rest/Response.java index d1c81262..698b0c84 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/Response.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/Response.java @@ -5,114 +5,64 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Supplier; /** - * Response from Discord's API. This is similar to a {@link java.util.concurrent.CompletableFuture CompletableFuture}. + * Response from Discord's API. * @see DiscordRequest * @author Seailz * @since 1.0.0 */ public class Response { - public volatile T response; - public volatile Error error; - public volatile boolean completed = false; - - public List> onCompletion = new ArrayList<>(); - public List> onError = new ArrayList<>(); + private final CompletableFuture responseFuture = new CompletableFuture<>(); + private final CompletableFuture errorFuture = new CompletableFuture<>(); public Response() {} - /** - * Blocks the current thread until the response is received, or an error occurs. - * @return Either the response or null if an error occurred. - */ public T awaitCompleted() { - while(true) { - synchronized(this) { - if (this.completed) { - break; - } - } - try { - Thread.sleep(1L); - } catch (InterruptedException var2) { - var2.printStackTrace(); - } - } - - return this.response; + return responseFuture.join(); } - /** - * Blocks the current thread until an error occurs, or the response is received. - * @return Either the error or null if the response was received. - */ + public Error awaitError() { - while (!completed) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - return error; // Error will be null if no error occurred + return errorFuture.join(); } public Error getError() { - return error; + return errorFuture.getNow(null); } public T getResponse() { - return response; + return responseFuture.getNow(null); } public Response complete(T response) { - synchronized(this) { - this.response = response; - Iterator var2 = this.onCompletion.iterator(); - - while(var2.hasNext()) { - Consumer tConsumer = (Consumer) var2.next(); - tConsumer.accept(response); - } - - this.completed = true; - } + responseFuture.complete(response); return this; } public Response completeError(Error error) { - this.error = error; - for (Consumer errorConsumer : onError) { - errorConsumer.accept(error); - } - completed = true; + errorFuture.complete(error); return this; } public Response onCompletion(Consumer consumer) { - onCompletion.add(consumer); + responseFuture.thenAccept(consumer); return this; } public Response onError(Consumer consumer) { - onError.add(consumer); + errorFuture.thenAccept(consumer); return this; } public Response completeAsync(Supplier response) { - new Thread(() -> { - complete(response.get()); - }).start(); + CompletableFuture.supplyAsync(response).thenAccept(this::complete); return this; } - /** - * Represents an error received from the Discord API. - *
TODO: Convert this to an enum - */ public static class Error { private int code; private String message; @@ -145,4 +95,4 @@ public String toString() { '}'; } } -} +} \ No newline at end of file From 8dbb8c2854e6547b08b23f31c9b4773aac123daf Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Fri, 12 May 2023 19:31:26 +0100 Subject: [PATCH 50/59] feat(response): Response.java now uses completeable futures instead of implementing that logic itself. --- src/main/java/com/seailz/discordjar/utils/rest/Response.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/Response.java b/src/main/java/com/seailz/discordjar/utils/rest/Response.java index 698b0c84..6586475d 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/Response.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/Response.java @@ -2,9 +2,6 @@ import org.json.JSONObject; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Supplier; From 9e56e0d888450afa600b624fb0e35222e2b54980 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 13 May 2023 16:06:36 +0100 Subject: [PATCH 51/59] fix(exception): Fixed the UnhandledDiscordAPIErrorException class so it in itself no longer throws an error. feat(response): added a throwOnError method which will tell the class to throw a runtime exception if the Response is completed with an error. --- .../discordjar/utils/rest/DiscordRequest.java | 2 +- .../discordjar/utils/rest/Response.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java index 16525211..f8d5446d 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/DiscordRequest.java @@ -392,7 +392,7 @@ public static class UnhandledDiscordAPIErrorException extends Exception { private JSONObject body; private String error; public UnhandledDiscordAPIErrorException(JSONObject body) { - this.body = body.getJSONObject("errors"); + this.body = body.has("errors") ? body.getJSONObject("errors") : body; this.code = body.getInt("code"); this.error = body.getString("message"); } diff --git a/src/main/java/com/seailz/discordjar/utils/rest/Response.java b/src/main/java/com/seailz/discordjar/utils/rest/Response.java index 6586475d..9d298888 100644 --- a/src/main/java/com/seailz/discordjar/utils/rest/Response.java +++ b/src/main/java/com/seailz/discordjar/utils/rest/Response.java @@ -16,6 +16,7 @@ public class Response { private final CompletableFuture responseFuture = new CompletableFuture<>(); private final CompletableFuture errorFuture = new CompletableFuture<>(); + private boolean throwOnError = false; public Response() {} @@ -35,6 +36,14 @@ public T getResponse() { return responseFuture.getNow(null); } + /** + * Tells the class to throw a runtime exception if an error is received. + */ + public Response throwOnError() { + throwOnError = true; + return this; + } + public Response complete(T response) { responseFuture.complete(response); return this; @@ -42,6 +51,9 @@ public Response complete(T response) { public Response completeError(Error error) { errorFuture.complete(error); + if (throwOnError) { + throw new DiscordResponseError(error); + } return this; } @@ -60,6 +72,19 @@ public Response completeAsync(Supplier response) { return this; } + public static class DiscordResponseError extends RuntimeException { + private final Error error; + + public DiscordResponseError(Error error) { + super(error.getMessage()); + this.error = error; + } + + public Error getError() { + return error; + } + } + public static class Error { private int code; private String message; From 605fb2b42f7fb9b11c8c968febc77259b2146651 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sat, 13 May 2023 16:48:08 +0100 Subject: [PATCH 52/59] performance(event dispatching): Performance improvements for event dispatching. feat(interactions): migrated interactions to the Response class. --- .../EditInteractionMessageAction.java | 14 +- .../InteractionCallbackAction.java | 13 +- .../followup/InteractionFollowupAction.java | 31 +++-- .../discordjar/events/EventDispatcher.java | 66 +++++++--- .../internal/InteractionHandlerImpl.java | 120 ++++++++++-------- .../utils/annotation/RequireCustomId.java | 6 +- 6 files changed, 157 insertions(+), 93 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/action/interaction/EditInteractionMessageAction.java b/src/main/java/com/seailz/discordjar/action/interaction/EditInteractionMessageAction.java index 13e7e17b..fb5e927d 100644 --- a/src/main/java/com/seailz/discordjar/action/interaction/EditInteractionMessageAction.java +++ b/src/main/java/com/seailz/discordjar/action/interaction/EditInteractionMessageAction.java @@ -7,6 +7,7 @@ import com.seailz.discordjar.model.message.Message; import com.seailz.discordjar.utils.URLS; import com.seailz.discordjar.utils.rest.DiscordRequest; +import com.seailz.discordjar.utils.rest.Response; import org.jetbrains.annotations.Nullable; import org.json.JSONArray; import org.json.JSONObject; @@ -124,9 +125,9 @@ public EditInteractionMessageAction addComponent(DisplayComponent component) { return this; } - public CompletableFuture run() { - CompletableFuture future = new CompletableFuture<>(); - future.completeAsync(() -> { + public Response run() { + Response future = new Response<>(); + new Thread(() -> { JSONObject obj = new JSONObject(); if (content != null) obj.put("content", content); if (embeds != null) { @@ -175,12 +176,11 @@ public CompletableFuture run() { ); try { - return Message.decompile(request.invoke().body(), discordJar); + future.complete(Message.decompile(request.invoke().body(), discordJar)); } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { - future.completeExceptionally(e); - return null; + future.completeError(new Response.Error(e.getCode(), e.getMessage(), e.getBody())); } - }); + }).start(); return future; } diff --git a/src/main/java/com/seailz/discordjar/action/interaction/InteractionCallbackAction.java b/src/main/java/com/seailz/discordjar/action/interaction/InteractionCallbackAction.java index b79d98f6..97c0f02a 100644 --- a/src/main/java/com/seailz/discordjar/action/interaction/InteractionCallbackAction.java +++ b/src/main/java/com/seailz/discordjar/action/interaction/InteractionCallbackAction.java @@ -6,6 +6,7 @@ import com.seailz.discordjar.model.interaction.reply.InteractionReply; import com.seailz.discordjar.utils.URLS; import com.seailz.discordjar.utils.rest.DiscordRequest; +import com.seailz.discordjar.utils.rest.Response; import org.json.JSONObject; import org.springframework.web.bind.annotation.RequestMethod; @@ -43,7 +44,7 @@ public InteractionReply getReply() { return reply; } - public InteractionHandler run() throws DiscordRequest.UnhandledDiscordAPIErrorException { + public Response run() { JSONObject json = new JSONObject(); json.put("type", this.type.getCode()); json.put("data", this.reply.compile()); @@ -53,8 +54,14 @@ public InteractionHandler run() throws DiscordRequest.UnhandledDiscordAPIErrorEx URLS.POST.INTERACTIONS.CALLBACK.replace("{interaction.id}", this.id) .replace("{interaction.token}", this.token), discordJar, URLS.POST.INTERACTIONS.CALLBACK, RequestMethod.POST); - request.invoke(); - return InteractionHandler.from(token, id, discordJar); + Response response = new Response<>(); + try { + request.invoke(); + response.complete(InteractionHandler.from(token, id, discordJar)); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + response.completeError(new Response.Error(e.getCode(), e.getMessage(), e.getBody())); + } + return response; } diff --git a/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java b/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java index ebdb263d..e2a6fcf0 100644 --- a/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java +++ b/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java @@ -9,6 +9,7 @@ import com.seailz.discordjar.model.message.Attachment; import com.seailz.discordjar.utils.URLS; import com.seailz.discordjar.utils.rest.DiscordRequest; +import com.seailz.discordjar.utils.rest.Response; import org.springframework.web.bind.annotation.RequestMethod; import java.util.HashMap; @@ -142,17 +143,23 @@ public InteractionMessageResponse getReply() { return reply; } - public InteractionHandler run() throws DiscordRequest.UnhandledDiscordAPIErrorException { - new DiscordRequest( - getReply().compile(), - new HashMap<>(), - URLS.POST.INTERACTIONS.FOLLOWUP - .replaceAll("application.id", discordJar.getSelfInfo().id()) - .replaceAll("interaction.token", token), - discordJar, - URLS.POST.INTERACTIONS.FOLLOWUP, - RequestMethod.POST - ).invoke(); - return InteractionHandler.from(token, id, discordJar); + public Response run() { + Response response = new Response<>(); + try { + new DiscordRequest( + getReply().compile(), + new HashMap<>(), + URLS.POST.INTERACTIONS.FOLLOWUP + .replaceAll("application.id", discordJar.getSelfInfo().id()) + .replaceAll("interaction.token", token), + discordJar, + URLS.POST.INTERACTIONS.FOLLOWUP, + RequestMethod.POST + ).invoke(); + response.complete(InteractionHandler.from(token, id, discordJar)); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + response.completeError(new Response.Error(e.getCode(), e.getMessage(), e.getBody())); + } + return response; } } diff --git a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java index 7b6d55cb..b8e4906a 100644 --- a/src/main/java/com/seailz/discordjar/events/EventDispatcher.java +++ b/src/main/java/com/seailz/discordjar/events/EventDispatcher.java @@ -3,11 +3,17 @@ import com.seailz.discordjar.DiscordJar; import com.seailz.discordjar.events.annotation.EventMethod; import com.seailz.discordjar.events.model.Event; +import com.seailz.discordjar.events.model.interaction.CustomIdable; import com.seailz.discordjar.events.model.message.MessageCreateEvent; +import com.seailz.discordjar.utils.annotation.RequireCustomId; import com.seailz.discordjar.utils.rest.DiscordRequest; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * This class is used to dispatch events to the correct listeners. @@ -18,10 +24,22 @@ */ public class EventDispatcher { - private final HashMap listeners; + // Map: Event type -> List of pairs (Listener, Method) + private final Map, List> listenersByEventType = new HashMap<>(); + + // Pair of listener instance and method to call + private static class ListenerMethodPair { + final DiscordListener listener; + final Method method; + + ListenerMethodPair(DiscordListener listener, Method method) { + this.listener = listener; + this.method = method; + } + } + public EventDispatcher(DiscordJar bot) { - listeners = new HashMap<>(); } /** @@ -35,7 +53,8 @@ public void addListener(DiscordListener... listeners) { for (DiscordListener listener : listeners) { for (Method method : listener.getClass().getMethods()) { if (method.isAnnotationPresent(EventMethod.class)) { - this.listeners.put(listener, method); + Class eventType = (Class) method.getParameterTypes()[0]; + listenersByEventType.computeIfAbsent(eventType, k -> new ArrayList<>()).add(new ListenerMethodPair(listener, method)); } } } @@ -51,29 +70,36 @@ public void addListener(DiscordListener... listeners) { */ public void dispatchEvent(Event event, Class type, DiscordJar djv) { new Thread(() -> { - if (event instanceof MessageCreateEvent) { - try { - if (((MessageCreateEvent) event).getMessage().author().id().equals(djv.getSelfUser().id())) - return; - } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { - throw new RuntimeException(e); - } + long start = System.currentTimeMillis(); + List listenersForEventType = listenersByEventType.get(type); + if (listenersForEventType == null) { + return; } - int index = 0; - for (Method i : listeners.values()) { - if (i.isAnnotationPresent(EventMethod.class)) { - if (i.getParameterTypes()[0].equals(type)) { - try { - i.setAccessible(true); - i.invoke(listeners.keySet().toArray()[index], event); - } catch (Exception e) { - e.printStackTrace(); + for (ListenerMethodPair listenerMethodPair : listenersForEventType) { + Method method = listenerMethodPair.method; + if (method.isAnnotationPresent(RequireCustomId.class)) { + if (event instanceof CustomIdable) { + if (((CustomIdable) event).getCustomId() == null) { + continue; + } + + if (!((CustomIdable) event).getCustomId().matches(method.getAnnotation(RequireCustomId.class).value())) { + continue; } } } - index++; + + method.setAccessible(true); + new Thread(() -> { + try { + method.invoke(listenerMethodPair.listener, event); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + }).start(); } + System.out.println("Event " + event.getClass().getSimpleName() + " took " + (System.currentTimeMillis() - start) + "ms to dispatch."); }, "EventDispatcher").start(); } } \ No newline at end of file diff --git a/src/main/java/com/seailz/discordjar/model/interaction/callback/internal/InteractionHandlerImpl.java b/src/main/java/com/seailz/discordjar/model/interaction/callback/internal/InteractionHandlerImpl.java index c160b9e0..a8c8fd4a 100644 --- a/src/main/java/com/seailz/discordjar/model/interaction/callback/internal/InteractionHandlerImpl.java +++ b/src/main/java/com/seailz/discordjar/model/interaction/callback/internal/InteractionHandlerImpl.java @@ -31,68 +31,88 @@ public InteractionFollowupAction followup(String content) { } @Override - public Message getOriginalResponse() throws DiscordRequest.UnhandledDiscordAPIErrorException { - return Message.decompile( - new DiscordRequest( - new JSONObject(), - new HashMap<>(), - URLS.GET.INTERACTIONS.GET_ORIGINAL_INTERACTION_RESPONSE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()), - discordJar, - URLS.GET.INTERACTIONS.GET_ORIGINAL_INTERACTION_RESPONSE, - RequestMethod.GET - ).invoke().body(), discordJar - ); + public Message getOriginalResponse() { + try { + return Message.decompile( + new DiscordRequest( + new JSONObject(), + new HashMap<>(), + URLS.GET.INTERACTIONS.GET_ORIGINAL_INTERACTION_RESPONSE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()), + discordJar, + URLS.GET.INTERACTIONS.GET_ORIGINAL_INTERACTION_RESPONSE, + RequestMethod.GET + ).invoke().body(), discordJar + ); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + throw new RuntimeException(e); + } } @Override - public void deleteOriginalResponse() throws DiscordRequest.UnhandledDiscordAPIErrorException { - new DiscordRequest( - new JSONObject(), - new HashMap<>(), - URLS.DELETE.INTERACTION.DELETE_ORIGINAL_INTERACTION_RESPONSE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()), - discordJar, - URLS.DELETE.INTERACTION.DELETE_ORIGINAL_INTERACTION_RESPONSE, - RequestMethod.DELETE - ).invoke(); + public void deleteOriginalResponse() { + try { + new DiscordRequest( + new JSONObject(), + new HashMap<>(), + URLS.DELETE.INTERACTION.DELETE_ORIGINAL_INTERACTION_RESPONSE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()), + discordJar, + URLS.DELETE.INTERACTION.DELETE_ORIGINAL_INTERACTION_RESPONSE, + RequestMethod.DELETE + ).invoke(); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + throw new RuntimeException(e); + } } @Override - public Message getFollowup(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { - return Message.decompile( - new DiscordRequest( - new JSONObject(), - new HashMap<>(), - URLS.GET.INTERACTIONS.GET_FOLLOWUP_MESSAGE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()) - .replace("{message.id}", id), - discordJar, - URLS.GET.INTERACTIONS.GET_FOLLOWUP_MESSAGE, - RequestMethod.GET - ).invoke().body(), discordJar - ); + public Message getFollowup(String id) { + try { + return Message.decompile( + new DiscordRequest( + new JSONObject(), + new HashMap<>(), + URLS.GET.INTERACTIONS.GET_FOLLOWUP_MESSAGE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()) + .replace("{message.id}", id), + discordJar, + URLS.GET.INTERACTIONS.GET_FOLLOWUP_MESSAGE, + RequestMethod.GET + ).invoke().body(), discordJar + ); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + throw new RuntimeException(e); + } } @Override - public void deleteFollowup(String id) throws DiscordRequest.UnhandledDiscordAPIErrorException { - new DiscordRequest( - new JSONObject(), - new HashMap<>(), - URLS.DELETE.INTERACTION.DELETE_FOLLOWUP_MESSAGE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()) - .replace("{message.id}", id), - discordJar, - URLS.DELETE.INTERACTION.DELETE_FOLLOWUP_MESSAGE, - RequestMethod.DELETE - ).invoke(); + public void deleteFollowup(String id) { + try { + new DiscordRequest( + new JSONObject(), + new HashMap<>(), + URLS.DELETE.INTERACTION.DELETE_FOLLOWUP_MESSAGE.replace("{interaction.token}", token).replace("{application.id}", discordJar.getSelfInfo().id()) + .replace("{message.id}", id), + discordJar, + URLS.DELETE.INTERACTION.DELETE_FOLLOWUP_MESSAGE, + RequestMethod.DELETE + ).invoke(); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + throw new RuntimeException(e); + } } @Override - public EditInteractionMessageAction editOriginalResponse() throws DiscordRequest.UnhandledDiscordAPIErrorException { - return new EditInteractionMessageAction( - discordJar.getSelfInfo().id(), - token, - discordJar, - true, - null - ); + public EditInteractionMessageAction editOriginalResponse() { + try { + return new EditInteractionMessageAction( + discordJar.getSelfInfo().id(), + token, + discordJar, + true, + null + ); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { + throw new RuntimeException(e); + } } @Override diff --git a/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java b/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java index 2e9c7dd5..0794edbf 100644 --- a/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java +++ b/src/main/java/com/seailz/discordjar/utils/annotation/RequireCustomId.java @@ -17,7 +17,11 @@ *
event.reply("The custom ID is my_custom_id!").run(); *
} * - * + *

+ * It's also a regex, so you can do things like this: @RequireCustomId("my_custom_id|my_custom_id2") which will match both "my_custom_id" and "my_custom_id2" , + * or @RequireCustomId("my_custom_id-.*") which will match any custom id that starts with "my_custom_id-". + *

+ * Using this annotation, however, will cause some performance loss, so use it sparingly or if time is not a concern. * @author Seailz * @since 1.0 */ From 5806a47cd25f44e80efb783938c46dd36ef3c884 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 14 May 2023 16:30:15 +0100 Subject: [PATCH 53/59] performance(command dispatcher): Improved command dispatching performance. --- .../discordjar/command/CommandDispatcher.java | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java b/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java index 823f4594..eb91ebee 100644 --- a/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java +++ b/src/main/java/com/seailz/discordjar/command/CommandDispatcher.java @@ -43,42 +43,44 @@ public void registerSubCommand(SlashCommandListener top, SlashSubCommand sub, Su } public void dispatch(String name, CommandInteractionEvent event) { - Class eventClass = (event instanceof SlashCommandInteractionEvent ? SlashCommandInteractionEvent.class : CommandInteractionEvent.class); - event.getBot().getEventDispatcher().dispatchEvent(event, eventClass, event.getBot()); - if ((event instanceof SlashCommandInteractionEvent) && ((SlashCommandInteractionEvent) event).getOptionsInternal() != null && !((SlashCommandInteractionEvent) event).getOptionsInternal().isEmpty()) { - for (ResolvedCommandOption option : ((SlashCommandInteractionEvent) event).getOptionsInternal()) { - if (option.type() == CommandOptionType.SUB_COMMAND) { - for (ArrayList detailsList : subListeners.values()) { - for (SlashSubCommandDetails details : detailsList) { - if (details.sub.getName().equals(option.name())) { - SlashCommandListener top = subListeners.keySet().stream().toList() - .get(subListeners.values().stream().toList().indexOf(detailsList)); + new Thread(() -> { + Class eventClass = (event instanceof SlashCommandInteractionEvent ? SlashCommandInteractionEvent.class : CommandInteractionEvent.class); + event.getBot().getEventDispatcher().dispatchEvent(event, eventClass, event.getBot()); + if ((event instanceof SlashCommandInteractionEvent) && ((SlashCommandInteractionEvent) event).getOptionsInternal() != null && !((SlashCommandInteractionEvent) event).getOptionsInternal().isEmpty()) { + for (ResolvedCommandOption option : ((SlashCommandInteractionEvent) event).getOptionsInternal()) { + if (option.type() == CommandOptionType.SUB_COMMAND) { + for (ArrayList detailsList : subListeners.values()) { + for (SlashSubCommandDetails details : detailsList) { + if (details.sub.getName().equals(option.name())) { + SlashCommandListener top = subListeners.keySet().stream().toList() + .get(subListeners.values().stream().toList().indexOf(detailsList)); - if (Objects.equals(name, top.getClass().getAnnotation(SlashCommandInfo.class).name())) { - details.listener().onCommand(event); - } - return; + if (Objects.equals(name, top.getClass().getAnnotation(SlashCommandInfo.class).name())) { + new Thread(() -> details.listener().onCommand(event)).start(); + } + return; /*if (event.getName().startsWith(top.getClass().getAnnotation(SlashCommandInfo.class).name())) { }*/ + } } } - } - } else if (option.type() == CommandOptionType.SUB_COMMAND_GROUP) { - List subOptions = new ArrayList<>(); + } else if (option.type() == CommandOptionType.SUB_COMMAND_GROUP) { + List subOptions = new ArrayList<>(); - for (int i = 1; i < option.options().size(); i++) { - subOptions.add(option.options().get(i)); - } + for (int i = 1; i < option.options().size(); i++) { + subOptions.add(option.options().get(i)); + } - for (ResolvedCommandOption subs : option.options()) { - for (ArrayList detailsList : subListeners.values()) { - for (SlashSubCommandDetails details : detailsList) { - if (details.sub.getName().equals(subs.name())) { - SlashCommandListener top = subListeners.keySet().stream().toList() - .get(subListeners.values().stream().toList().indexOf(detailsList)); + for (ResolvedCommandOption subs : option.options()) { + for (ArrayList detailsList : subListeners.values()) { + for (SlashSubCommandDetails details : detailsList) { + if (details.sub.getName().equals(subs.name())) { + SlashCommandListener top = subListeners.keySet().stream().toList() + .get(subListeners.values().stream().toList().indexOf(detailsList)); - if (Objects.equals(name, top.getClass().getAnnotation(SlashCommandInfo.class).name())) { - details.listener().onCommand(event); + if (Objects.equals(name, top.getClass().getAnnotation(SlashCommandInfo.class).name())) { + new Thread(() -> details.listener().onCommand(event)).start(); + } } } } @@ -86,8 +88,8 @@ public void dispatch(String name, CommandInteractionEvent event) { } } } - } - listeners.get(name).onCommand(event); + new Thread(() -> listeners.get(name).onCommand(event)).start(); + }, "Command Dispatcher (discord.jar)").start(); } record SlashSubCommandDetails( From 221c9c945127713fc8f4383b80f387979f04a705 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Sun, 14 May 2023 17:43:32 +0100 Subject: [PATCH 54/59] feat(messages, permissions): added necessary additions for voice messages --- .../action/message/MessageCreateAction.java | 32 ++++++++++++++++--- .../action/message/MessageEditAction.java | 3 +- .../discordjar/model/message/Message.java | 3 +- .../discordjar/model/message/MessageFlag.java | 3 +- .../utils/permission/Permission.java | 4 ++- 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/action/message/MessageCreateAction.java b/src/main/java/com/seailz/discordjar/action/message/MessageCreateAction.java index c7f6e4e4..510ddfd1 100644 --- a/src/main/java/com/seailz/discordjar/action/message/MessageCreateAction.java +++ b/src/main/java/com/seailz/discordjar/action/message/MessageCreateAction.java @@ -18,10 +18,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.concurrent.CompletableFuture; /** @@ -50,6 +47,8 @@ public class MessageCreateAction { private final DiscordJar discordJar; private boolean silent = false; private AllowedMentions allowedMentions; + private byte[] waveform; + private float duration = -1; public MessageCreateAction(@Nullable String text, @NotNull String channelId, @NotNull DiscordJar discordJar) { this.text = text; @@ -128,6 +127,22 @@ public MessageCreateAction setText(@Nullable String text) { return this; } + /** + * For voice messages + */ + public MessageCreateAction setWaveform(byte[] waveform) { + this.waveform = waveform; + return this; + } + + /** + * For voice messages + */ + public MessageCreateAction setDuration(float dur) { + this.duration = dur; + return this; + } + public MessageCreateAction setAllowedMentions(AllowedMentions allowedMentions) { this.allowedMentions = allowedMentions; return this; @@ -256,6 +271,15 @@ public CompletableFuture run() { if (this.nonce != null) payload.put("nonce", this.nonce); if (this.tts) payload.put("tts", true); if (this.messageReference != null) payload.put("message_reference", this.messageReference.compile()); + if (this.waveform != null) { + // Encode base64 + String encoded = Base64.getEncoder().encodeToString(this.waveform); + payload.put("waveform", encoded); + } + + if (this.duration != -1) { + payload.put("duration", this.duration); + } JSONArray components = new JSONArray(); if (this.components != null && !this.components.isEmpty()) { diff --git a/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java b/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java index 635be97c..f2029443 100644 --- a/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java +++ b/src/main/java/com/seailz/discordjar/action/message/MessageEditAction.java @@ -43,10 +43,11 @@ public class MessageEditAction { private final DiscordJar discordJar; private final String messageId; - public MessageEditAction(@NotNull String channelId, @NotNull DiscordJar discordJar, String messageId) { + public MessageEditAction(@NotNull String channelId, @NotNull DiscordJar discordJar, String messageId, boolean isVoiceMessage) { this.channelId = channelId; this.discordJar = discordJar; this.messageId = messageId; + if (isVoiceMessage) throw new IllegalArgumentException("Cannot edit a voice message"); } public MessageEditAction(ArrayList components, @NotNull String channelId, @NotNull DiscordJar discordJar, String messageId) { diff --git a/src/main/java/com/seailz/discordjar/model/message/Message.java b/src/main/java/com/seailz/discordjar/model/message/Message.java index e6448849..061e0925 100644 --- a/src/main/java/com/seailz/discordjar/model/message/Message.java +++ b/src/main/java/com/seailz/discordjar/model/message/Message.java @@ -28,6 +28,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -416,7 +417,7 @@ public void delete() throws DiscordRequest.UnhandledDiscordAPIErrorException { * @return The MessageEditAction object. */ public MessageEditAction edit() { - return new MessageEditAction(channelId, discordJar, id); + return new MessageEditAction(channelId, discordJar, id, Arrays.stream(flags).toList().contains(MessageFlag.IS_VOICE_MESSAGE)); } /** diff --git a/src/main/java/com/seailz/discordjar/model/message/MessageFlag.java b/src/main/java/com/seailz/discordjar/model/message/MessageFlag.java index 943707fa..2f666409 100644 --- a/src/main/java/com/seailz/discordjar/model/message/MessageFlag.java +++ b/src/main/java/com/seailz/discordjar/model/message/MessageFlag.java @@ -29,7 +29,8 @@ public enum MessageFlag { // this message failed to mention some roles and add their members to the thread FAILED_THREAD_MEMBER_ADD(8, false), // this message is "silent" - SUPPRESS_NOTICICATIONS(12, true) + SUPPRESS_NOTICICATIONS(12, true), + IS_VOICE_MESSAGE(13, true) ; private final int id; diff --git a/src/main/java/com/seailz/discordjar/utils/permission/Permission.java b/src/main/java/com/seailz/discordjar/utils/permission/Permission.java index fe12a401..f9c54018 100644 --- a/src/main/java/com/seailz/discordjar/utils/permission/Permission.java +++ b/src/main/java/com/seailz/discordjar/utils/permission/Permission.java @@ -97,7 +97,9 @@ public enum Permission implements Bitwiseable { // Allows for viewing role subscription insights VIEW_CREATOR_MONETIZATION_ANALYTICS(41), // Allows for using soundboard in a voice channel - USE_SOUNDBOARD(42) + USE_SOUNDBOARD(42), + // Allows sending voice messages + SEND_VOICE_MESSAGES(46), ; private final int code; From 9d954ef384ff857b1d957b20736c010211cc07cc Mon Sep 17 00:00:00 2001 From: seailz <81972974+seailz@users.noreply.github.com> Date: Sun, 14 May 2023 17:46:31 +0100 Subject: [PATCH 55/59] workflows: removed codesee (it's annoying) --- .github/workflows/codesee-arch-diagram.yml | 23 ---------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/codesee-arch-diagram.yml diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml deleted file mode 100644 index 806d41d1..00000000 --- a/.github/workflows/codesee-arch-diagram.yml +++ /dev/null @@ -1,23 +0,0 @@ -# This workflow was added by CodeSee. Learn more at https://codesee.io/ -# This is v2.0 of this workflow file -on: - push: - branches: - - main - pull_request_target: - types: [opened, synchronize, reopened] - -name: CodeSee - -permissions: read-all - -jobs: - codesee: - runs-on: ubuntu-latest - continue-on-error: true - name: Analyze the repo with CodeSee - steps: - - uses: Codesee-io/codesee-action@v2 - with: - codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - codesee-url: https://app.codesee.io From 25ad188a81a4910a14a93c0df4ff443ac76f0001 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 15 May 2023 12:32:25 +0100 Subject: [PATCH 56/59] fix(gateway): When the bot reconnects to the gateway, it will now retain its status - also added a backup gateway url in cased the /gateway request fails. --- .../java/com/seailz/discordjar/DiscordJar.java | 16 +++++++++++----- .../discordjar/gateway/GatewayFactory.java | 18 +++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index a2f1de8c..91e3b5a6 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -115,6 +115,10 @@ public class DiscordJar { * List of rate-limit buckets */ private List buckets; + /** + * The current status of the bot, or null if not set. + */ + private Status status; public int gatewayConnections = 0; public List gatewayFactories = new ArrayList<>(); @@ -302,15 +306,13 @@ public GatewayFactory getGateway() { * Kills the gateway connection and destroys the {@link GatewayFactory} instance. * This method will also initiate garbage collection to avoid memory leaks. This probably shouldn't be used unless in {@link #restartGateway()}. */ - public Status killGateway() { - Status status = gatewayFactory == null ? null : gatewayFactory.getStatus(); + public void killGateway() { try { if (gatewayFactory != null) gatewayFactory.killConnection(); } catch (IOException ignored) {} gatewayFactory = null; // init garbage collection to avoid memory leaks System.gc(); - return status; } /** @@ -322,14 +324,13 @@ public Status killGateway() { * @see #killGateway() */ public void restartGateway() { - Status stat = killGateway(); + killGateway(); try { gatewayFactory = new GatewayFactory(this, debug); gatewayFactories.add(gatewayFactory); } catch (ExecutionException | InterruptedException | DiscordRequest.UnhandledDiscordAPIErrorException e) { throw new RuntimeException(e); } - if (stat != null) setStatus(stat); } protected void initiateShutdownHooks() { @@ -398,6 +399,11 @@ public void setStatus(@NotNull Status status) { json.put("op", 3); gatewayFactory.queueMessage(json); gatewayFactory.setStatus(status); + this.status = status; + } + + public Status getStatus() { + return status; } /** diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index 54c73ebb..faaa4625 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -77,8 +77,18 @@ public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionExce discordJar, "/gateway", RequestMethod.GET ).invoke(); - this.gatewayUrl = response.body().getString("url"); + if (response == null || response.body() == null || !response.body().has("url")) { + // In case the request fails, we can attempt to use the backup gateway URL instead. + this.gatewayUrl = URLS.GATEWAY.BASE_URL; + } else this.gatewayUrl = response.body().getString("url"); connect(); + + if (discordJar.getStatus() != null) { + JSONObject json = new JSONObject(); + json.put("d", discordJar.getStatus().compile()); + json.put("op", 3); + queueMessage(json); + } } public void connect(String customUrl) throws ExecutionException, InterruptedException { @@ -189,6 +199,12 @@ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) } } + + /** + * Do not use this method - it is for internal use only. + * @param status The status to set. + */ + @Deprecated public void setStatus(Status status) { this.status = status; } From 7f644b8bef91a75796d9a9fce0f6690a9b6ac25d Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 15 May 2023 12:33:46 +0100 Subject: [PATCH 57/59] fix(gateway): if /gateway request throws a DDiscordRequest.UnhandledDiscordAPIErrorException, then it will go to the backup url instead. --- .../discordjar/gateway/GatewayFactory.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index faaa4625..d45e66b6 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -64,19 +64,22 @@ public class GatewayFactory extends TextWebSocketHandler { private final boolean debug; public UUID uuid = UUID.randomUUID(); - public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionException, InterruptedException, DiscordRequest.UnhandledDiscordAPIErrorException { + public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionException, InterruptedException { this.discordJar = discordJar; this.debug = debug; discordJar.setGatewayFactory(this); - DiscordResponse response = new DiscordRequest( - new JSONObject(), - new HashMap<>(), - "/gateway", - discordJar, - "/gateway", RequestMethod.GET - ).invoke(); + DiscordResponse response = null; + try { + response = new DiscordRequest( + new JSONObject(), + new HashMap<>(), + "/gateway", + discordJar, + "/gateway", RequestMethod.GET + ).invoke(); + } catch (DiscordRequest.UnhandledDiscordAPIErrorException ignored) {} if (response == null || response.body() == null || !response.body().has("url")) { // In case the request fails, we can attempt to use the backup gateway URL instead. this.gatewayUrl = URLS.GATEWAY.BASE_URL; From c0e8977ea880030cac4a170ced10f03feb0ac981 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 15 May 2023 12:45:48 +0100 Subject: [PATCH 58/59] fix(gateway): fixed syntax errors --- src/main/java/com/seailz/discordjar/DiscordJar.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java index 91e3b5a6..84a405b6 100644 --- a/src/main/java/com/seailz/discordjar/DiscordJar.java +++ b/src/main/java/com/seailz/discordjar/DiscordJar.java @@ -197,11 +197,7 @@ public DiscordJar(String token, EnumSet intents, APIVersion version, boo this.queuedRequests = new ArrayList<>(); this.buckets = new ArrayList<>(); if (!httpOnly) { - try { - this.gatewayFactory = new GatewayFactory(this, debug); - } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) { - throw new RuntimeException(e); - } + this.gatewayFactory = new GatewayFactory(this, debug); } this.debug = debug; this.guildCache = new Cache<>(this, Guild.class, @@ -328,7 +324,7 @@ public void restartGateway() { try { gatewayFactory = new GatewayFactory(this, debug); gatewayFactories.add(gatewayFactory); - } catch (ExecutionException | InterruptedException | DiscordRequest.UnhandledDiscordAPIErrorException e) { + } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } } From d7139d7edda6ee5e625e6fcae9033373c817e303 Mon Sep 17 00:00:00 2001 From: IoyoCode Date: Mon, 15 May 2023 15:12:30 +0100 Subject: [PATCH 59/59] fix(gateway): fixed syntax errors --- .../seailz/discordjar/gateway/GatewayFactory.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java index d45e66b6..b9406f8e 100644 --- a/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java +++ b/src/main/java/com/seailz/discordjar/gateway/GatewayFactory.java @@ -85,13 +85,6 @@ public GatewayFactory(DiscordJar discordJar, boolean debug) throws ExecutionExce this.gatewayUrl = URLS.GATEWAY.BASE_URL; } else this.gatewayUrl = response.body().getString("url"); connect(); - - if (discordJar.getStatus() != null) { - JSONObject json = new JSONObject(); - json.put("d", discordJar.getStatus().compile()); - json.put("op", 3); - queueMessage(json); - } } public void connect(String customUrl) throws ExecutionException, InterruptedException { @@ -317,6 +310,13 @@ private void handleDispatched(JSONObject payload) throws NoSuchMethodException, this.sessionId = payload.getJSONObject("d").getString("session_id"); this.resumeUrl = payload.getJSONObject("d").getString("resume_gateway_url"); readyForMessages = true; + + if (discordJar.getStatus() != null) { + JSONObject json = new JSONObject(); + json.put("d", discordJar.getStatus().compile()); + json.put("op", 3); + queueMessage(json); + } break; case GUILD_CREATE: discordJar.getGuildCache().cache(Guild.decompile(payload.getJSONObject("d"), discordJar));