From e09543603d0687c71d50d23ef181abea8977140a Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:52:59 +0700 Subject: [PATCH 1/6] feat: add webhooks wrapper for spring boot --- .../webhooks/springboot/DBLWebhooks.java | 105 ++++++++++++++++++ .../springboot/DBLWebhooksListener.java | 28 +++++ 2 files changed, 133 insertions(+) create mode 100644 src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java create mode 100644 src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java diff --git a/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java b/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java new file mode 100644 index 0000000..e6ebee0 --- /dev/null +++ b/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java @@ -0,0 +1,105 @@ +package org.discordbots.webhooks.springboot; + +import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HexFormat; +import java.util.stream.Collectors; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import org.discordbots.webhooks.payload.IntegrationCreatePayload; +import org.discordbots.webhooks.payload.IntegrationDeletePayload; +import org.discordbots.webhooks.payload.Payload; +import org.discordbots.webhooks.payload.TestPayload; +import org.discordbots.webhooks.payload.VoteCreatePayload; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class DBLWebhooks implements DBLWebhooksListener { + private byte[] secret; + private final Gson gson; + + public DBLWebhooks(final String secret) { + this.secret = secret.getBytes(StandardCharsets.UTF_8); + this.gson = + new GsonBuilder() + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeConverter()) + .create(); + } + + public String getSecret() { + return new String(secret, StandardCharsets.UTF_8); + } + + public void setSecret(final String newSecret) { + secret = newSecret.getBytes(StandardCharsets.UTF_8); + } + + @SuppressWarnings("UseSpecificCatch") + protected ResponseEntity dispatch( + final String body, final String signatureHeader, final String trace) { + try { + final HashMap parsedSignature = + Arrays.stream(signatureHeader.split(",")) + .map(part -> part.split("=", 2)) + .collect( + Collectors.toMap( + part -> part[0].trim(), + part -> part[1].trim(), + (existing, replacement) -> replacement, + HashMap::new)); + + final String signature = parsedSignature.get("v1"); + final String timestamp = parsedSignature.get("t"); + + assert signature != null && timestamp != null; + + final SecretKeySpec key = new SecretKeySpec(secret, "HmacSHA256"); + final Mac hmac = Mac.getInstance("HmacSHA256"); + + hmac.init(key); + + final byte[] digest = + hmac.doFinal(String.format("%s.%s", timestamp, body).getBytes(StandardCharsets.UTF_8)); + + if (!signature.equals(HexFormat.of().formatHex(digest))) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + final Payload payload = gson.fromJson(body, Payload.class); + + try { + return switch (payload.getType()) { + case "integration.create" -> + onIntegrationCreate(payload.getData(gson, IntegrationCreatePayload.class), trace); + case "integration.delete" -> + onIntegrationDelete(payload.getData(gson, IntegrationDeletePayload.class), trace); + case "webhook.test" -> onTest(payload.getData(gson, TestPayload.class), trace); + case "vote.create" -> onVoteCreate(payload.getData(gson, VoteCreatePayload.class), trace); + default -> ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + }; + } catch (final Throwable ignored) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } catch (final NoSuchAlgorithmException + | InvalidKeyException + | ArrayIndexOutOfBoundsException + | AssertionError + | JsonSyntaxException + | JsonIOException error) { + return ResponseEntity.status( + (error instanceof NoSuchAlgorithmException || error instanceof InvalidKeyException) + ? HttpStatus.INTERNAL_SERVER_ERROR + : HttpStatus.BAD_REQUEST) + .build(); + } + } +} diff --git a/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java b/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java new file mode 100644 index 0000000..c1f2df6 --- /dev/null +++ b/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java @@ -0,0 +1,28 @@ +package org.discordbots.webhooks.springboot; + +import org.discordbots.webhooks.payload.IntegrationCreatePayload; +import org.discordbots.webhooks.payload.IntegrationDeletePayload; +import org.discordbots.webhooks.payload.TestPayload; +import org.discordbots.webhooks.payload.VoteCreatePayload; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public interface DBLWebhooksListener { + default ResponseEntity onIntegrationCreate( + final IntegrationCreatePayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + default ResponseEntity onIntegrationDelete( + final IntegrationDeletePayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + default ResponseEntity onTest(final TestPayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + default ResponseEntity onVoteCreate(final VoteCreatePayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } +} From 874b864c80014f77075d512f32aee0144798c22a Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:57:21 +0700 Subject: [PATCH 2/6] feat: add webhooks wrapper for eclipse jetty --- .../webhooks/eclipsejetty/DBLWebhooks.java | 128 ++++++++++++++++++ .../eclipsejetty/DBLWebhooksListener.java | 33 +++++ 2 files changed, 161 insertions(+) create mode 100644 src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java create mode 100644 src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java diff --git a/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java b/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java new file mode 100644 index 0000000..c27d3f7 --- /dev/null +++ b/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java @@ -0,0 +1,128 @@ +package org.discordbots.webhooks.eclipsejetty; + +import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HexFormat; +import java.util.stream.Collectors; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import org.discordbots.webhooks.payload.IntegrationCreatePayload; +import org.discordbots.webhooks.payload.IntegrationDeletePayload; +import org.discordbots.webhooks.payload.Payload; +import org.discordbots.webhooks.payload.TestPayload; +import org.discordbots.webhooks.payload.VoteCreatePayload; + +public class DBLWebhooks extends HttpServlet implements DBLWebhooksListener { + private byte[] secret; + private final Gson gson; + + public DBLWebhooks(final String secret) { + this.secret = secret.getBytes(StandardCharsets.UTF_8); + this.gson = + new GsonBuilder() + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeConverter()) + .create(); + } + + public String getSecret() { + return new String(secret, StandardCharsets.UTF_8); + } + + public void setSecret(final String newSecret) { + secret = newSecret.getBytes(StandardCharsets.UTF_8); + } + + @Override + @SuppressWarnings("UseSpecificCatch") + protected void doPost(final HttpServletRequest request, final HttpServletResponse response) + throws IOException, ServletException { + try { + final String signatureHeader = request.getHeader("x-topgg-signature"); + + assert signatureHeader != null; + + final HashMap parsedSignature = + Arrays.stream(signatureHeader.split(",")) + .map(part -> part.split("=", 2)) + .collect( + Collectors.toMap( + part -> part[0].trim(), + part -> part[1].trim(), + (existing, replacement) -> replacement, + HashMap::new)); + + final String signature = parsedSignature.get("v1"); + final String timestamp = parsedSignature.get("t"); + + assert signature != null && timestamp != null; + + final SecretKeySpec key = new SecretKeySpec(secret, "HmacSHA256"); + final Mac hmac = Mac.getInstance("HmacSHA256"); + + hmac.init(key); + + final String body = + new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + final byte[] digest = + hmac.doFinal(String.format("%s.%s", timestamp, body).getBytes(StandardCharsets.UTF_8)); + + if (!signature.equals(HexFormat.of().formatHex(digest))) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Invalid Authorization"); + + return; + } + + final Payload payload = gson.fromJson(body, Payload.class); + final String trace = request.getHeader("x-topgg-trace"); + + try { + switch (payload.getType()) { + case "integration.create" -> + onIntegrationCreate( + response, payload.getData(gson, IntegrationCreatePayload.class), trace); + case "integration.delete" -> + onIntegrationDelete( + response, payload.getData(gson, IntegrationDeletePayload.class), trace); + case "webhook.test" -> onTest(response, payload.getData(gson, TestPayload.class), trace); + case "vote.create" -> + onVoteCreate(response, payload.getData(gson, VoteCreatePayload.class), trace); + default -> { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("Bad Request"); + } + } + } catch (final Throwable ignored) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("Internal Server Error"); + } + } catch (final NoSuchAlgorithmException + | InvalidKeyException + | ArrayIndexOutOfBoundsException + | AssertionError + | JsonSyntaxException + | JsonIOException + | IOException error) { + if (error instanceof NoSuchAlgorithmException || error instanceof InvalidKeyException) { + throw new ServletException("Unable to find HMAC SHA-256 algorithm", error); + } else { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("Bad Request"); + } + } + } +} diff --git a/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java b/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java new file mode 100644 index 0000000..cee3a90 --- /dev/null +++ b/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java @@ -0,0 +1,33 @@ +package org.discordbots.webhooks.eclipsejetty; + +import jakarta.servlet.http.HttpServletResponse; +import org.discordbots.webhooks.payload.IntegrationCreatePayload; +import org.discordbots.webhooks.payload.IntegrationDeletePayload; +import org.discordbots.webhooks.payload.TestPayload; +import org.discordbots.webhooks.payload.VoteCreatePayload; + +public interface DBLWebhooksListener { + default void onIntegrationCreate( + final HttpServletResponse response, + final IntegrationCreatePayload payload, + final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + default void onIntegrationDelete( + final HttpServletResponse response, + final IntegrationDeletePayload payload, + final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + default void onTest( + final HttpServletResponse response, final TestPayload payload, final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + default void onVoteCreate( + final HttpServletResponse response, final VoteCreatePayload payload, final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } +} From 3bb707564da1b00fa635c4559c404c15becbc889 Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:59:45 +0700 Subject: [PATCH 3/6] feat: add webhooks wrapper for dropwizard --- .../webhooks/dropwizard/DBLWebhooks.java | 121 ++++++++++++++++++ .../dropwizard/DBLWebhooksListener.java | 25 ++++ 2 files changed, 146 insertions(+) create mode 100644 src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java create mode 100644 src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java diff --git a/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java b/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java new file mode 100644 index 0000000..817c98e --- /dev/null +++ b/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java @@ -0,0 +1,121 @@ +package org.discordbots.webhooks.dropwizard; + +import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HexFormat; +import java.util.stream.Collectors; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import org.discordbots.webhooks.payload.IntegrationCreatePayload; +import org.discordbots.webhooks.payload.IntegrationDeletePayload; +import org.discordbots.webhooks.payload.Payload; +import org.discordbots.webhooks.payload.TestPayload; +import org.discordbots.webhooks.payload.VoteCreatePayload; + +public abstract class DBLWebhooks implements DBLWebhooksListener { + private byte[] secret; + private final Gson gson; + + public DBLWebhooks(final String secret) { + this.secret = secret.getBytes(StandardCharsets.UTF_8); + this.gson = + new GsonBuilder() + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeConverter()) + .create(); + } + + public String getSecret() { + return new String(secret, StandardCharsets.UTF_8); + } + + public void setSecret(final String newSecret) { + secret = newSecret.getBytes(StandardCharsets.UTF_8); + } + + @POST + @SuppressWarnings("UseSpecificCatch") + public Response handle(@Context HttpServletRequest request) throws WebApplicationException { + try { + final String signatureHeader = request.getHeader("x-topgg-signature"); + + assert signatureHeader != null; + + final HashMap parsedSignature = + Arrays.stream(signatureHeader.split(",")) + .map(part -> part.split("=", 2)) + .collect( + Collectors.toMap( + part -> part[0].trim(), + part -> part[1].trim(), + (existing, replacement) -> replacement, + HashMap::new)); + + final String signature = parsedSignature.get("v1"); + final String timestamp = parsedSignature.get("t"); + + assert signature != null && timestamp != null; + + final SecretKeySpec key = new SecretKeySpec(secret, "HmacSHA256"); + final Mac hmac = Mac.getInstance("HmacSHA256"); + + hmac.init(key); + + final String body = + new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + final byte[] digest = + hmac.doFinal(String.format("%s.%s", timestamp, body).getBytes(StandardCharsets.UTF_8)); + + if (!signature.equals(HexFormat.of().formatHex(digest))) { + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Invalid Authorization") + .build(); + } + + final Payload payload = gson.fromJson(body, Payload.class); + final String trace = request.getHeader("x-topgg-trace"); + + try { + return switch (payload.getType()) { + case "integration.create" -> + onIntegrationCreate(payload.getData(gson, IntegrationCreatePayload.class), trace); + case "integration.delete" -> + onIntegrationDelete(payload.getData(gson, IntegrationDeletePayload.class), trace); + case "webhook.test" -> onTest(payload.getData(gson, TestPayload.class), trace); + case "vote.create" -> onVoteCreate(payload.getData(gson, VoteCreatePayload.class), trace); + default -> Response.status(Response.Status.BAD_REQUEST).entity("Bad Request").build(); + }; + } catch (final Throwable ignored) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Internal Server Error") + .build(); + } + } catch (final NoSuchAlgorithmException + | InvalidKeyException + | ArrayIndexOutOfBoundsException + | AssertionError + | JsonSyntaxException + | JsonIOException + | IOException error) { + if (error instanceof NoSuchAlgorithmException || error instanceof InvalidKeyException) { + throw new WebApplicationException("Unable to find HMAC SHA-256 algorithm", error); + } else { + return Response.status(Response.Status.BAD_REQUEST).entity("Bad Request").build(); + } + } + } +} diff --git a/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java b/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java new file mode 100644 index 0000000..5a76818 --- /dev/null +++ b/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java @@ -0,0 +1,25 @@ +package org.discordbots.webhooks.dropwizard; + +import jakarta.ws.rs.core.Response; +import org.discordbots.webhooks.payload.IntegrationCreatePayload; +import org.discordbots.webhooks.payload.IntegrationDeletePayload; +import org.discordbots.webhooks.payload.TestPayload; +import org.discordbots.webhooks.payload.VoteCreatePayload; + +public interface DBLWebhooksListener { + default Response onIntegrationCreate(final IntegrationCreatePayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + + default Response onIntegrationDelete(final IntegrationDeletePayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + + default Response onTest(final TestPayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + + default Response onVoteCreate(final VoteCreatePayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } +} From da736c76a3017978f930d3f0d3278edcc4b98e53 Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:00:30 +0700 Subject: [PATCH 4/6] feat: add webhook payload classes --- .../webhooks/entity/PartialProject.java | 30 ++++++++++++ .../discordbots/webhooks/entity/Platform.java | 8 ++++ .../webhooks/entity/ProjectType.java | 11 +++++ .../org/discordbots/webhooks/entity/User.java | 31 +++++++++++++ .../payload/IntegrationCreatePayload.java | 33 +++++++++++++ .../payload/IntegrationDeletePayload.java | 12 +++++ .../discordbots/webhooks/payload/Payload.java | 19 ++++++++ .../webhooks/payload/TestPayload.java | 18 ++++++++ .../webhooks/payload/VoteCreatePayload.java | 46 +++++++++++++++++++ 9 files changed, 208 insertions(+) create mode 100644 src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/entity/Platform.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/entity/User.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/payload/Payload.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java create mode 100644 src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java b/src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java new file mode 100644 index 0000000..85fff1d --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java @@ -0,0 +1,30 @@ +package org.discordbots.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public class PartialProject { + private String id; + + private ProjectType type; + + private Platform platform; + + @SerializedName("platform_id") + private String platformId; + + public String getId() { + return id; + } + + public ProjectType getType() { + return type; + } + + public Platform getPlatform() { + return platform; + } + + public String getPlatformId() { + return platformId; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/Platform.java b/src/webhooks/java/org/discordbots/webhooks/entity/Platform.java new file mode 100644 index 0000000..8cb1ad2 --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/entity/Platform.java @@ -0,0 +1,8 @@ +package org.discordbots.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public enum Platform { + @SerializedName("discord") + DISCORD +} diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java b/src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java new file mode 100644 index 0000000..675f7df --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java @@ -0,0 +1,11 @@ +package org.discordbots.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public enum ProjectType { + @SerializedName("bot") + DISCORD_BOT, + + @SerializedName("server") + DISCORD_SERVER +} diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/User.java b/src/webhooks/java/org/discordbots/webhooks/entity/User.java new file mode 100644 index 0000000..0be2afb --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/entity/User.java @@ -0,0 +1,31 @@ +package org.discordbots.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public class User { + private String id; + + private String name; + + @SerializedName("avatar_url") + private String avatar; + + @SerializedName("platform_id") + private String platformId; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getAvatar() { + return avatar; + } + + public String getPlatformId() { + return platformId; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java b/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java new file mode 100644 index 0000000..99303c1 --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java @@ -0,0 +1,33 @@ +package org.discordbots.webhooks.payload; + +import com.google.gson.annotations.SerializedName; +import org.discordbots.webhooks.entity.PartialProject; +import org.discordbots.webhooks.entity.User; + +public class IntegrationCreatePayload { + @SerializedName("connection_id") + private String connectionId; + + @SerializedName("webhook_secret") + private String secret; + + private PartialProject project; + + private User user; + + public String getConnectionId() { + return connectionId; + } + + public String getSecret() { + return secret; + } + + public PartialProject getProject() { + return project; + } + + public User getUser() { + return user; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java b/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java new file mode 100644 index 0000000..2653cda --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java @@ -0,0 +1,12 @@ +package org.discordbots.webhooks.payload; + +import com.google.gson.annotations.SerializedName; + +public class IntegrationDeletePayload { + @SerializedName("connection_id") + private String connectionId; + + public String getConnectionId() { + return connectionId; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/Payload.java b/src/webhooks/java/org/discordbots/webhooks/payload/Payload.java new file mode 100644 index 0000000..424ea39 --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/payload/Payload.java @@ -0,0 +1,19 @@ +package org.discordbots.webhooks.payload; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; + +public class Payload { + private String type; + + private JsonObject data; + + public String getType() { + return type; + } + + public T getData(final Gson gson, final Class cls) throws JsonSyntaxException { + return gson.fromJson(data, cls); + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java b/src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java new file mode 100644 index 0000000..6a90483 --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java @@ -0,0 +1,18 @@ +package org.discordbots.webhooks.payload; + +import org.discordbots.webhooks.entity.PartialProject; +import org.discordbots.webhooks.entity.User; + +public class TestPayload { + private PartialProject project; + + private User user; + + public PartialProject getProject() { + return project; + } + + public User getUser() { + return user; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java b/src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java new file mode 100644 index 0000000..9f8e819 --- /dev/null +++ b/src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java @@ -0,0 +1,46 @@ +package org.discordbots.webhooks.payload; + +import com.google.gson.annotations.SerializedName; +import java.time.OffsetDateTime; +import org.discordbots.webhooks.entity.PartialProject; +import org.discordbots.webhooks.entity.User; + +public class VoteCreatePayload { + private String id; + + private int weight; + + @SerializedName("created_at") + private OffsetDateTime votedAt; + + @SerializedName("expires_at") + private OffsetDateTime expiresAt; + + private PartialProject project; + + private User user; + + public String getId() { + return id; + } + + public int getWeight() { + return weight; + } + + public OffsetDateTime getVotedAt() { + return votedAt; + } + + public OffsetDateTime getExpiredAt() { + return expiresAt; + } + + public PartialProject getProject() { + return project; + } + + public User getUser() { + return user; + } +} From 6394dcf8c777448eb534ee8595e73c9e31df354a Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:16:54 +0700 Subject: [PATCH 5/6] meta: remove all mentions of DBL --- .../TopggWebhookEventListener.java} | 50 +++++----- .../webhooks/dropwizard/TopggWebhooks.java} | 39 ++++---- .../TopggWebhookEventListener.java} | 66 ++++++------- .../webhooks/eclipsejetty/TopggWebhooks.java} | 37 ++++---- .../TopggWebhookEventListener.java} | 56 +++++------ .../webhooks/springboot/TopggWebhooks.java} | 30 +++--- .../top}/webhooks/entity/PartialProject.java | 60 ++++++------ .../top}/webhooks/entity/Platform.java | 16 ++-- .../top}/webhooks/entity/ProjectType.java | 22 ++--- .../top}/webhooks/entity/User.java | 62 ++++++------ .../payload/IntegrationCreatePayload.java | 67 ++++++------- .../payload/IntegrationDeletePayload.java | 24 ++--- .../top}/webhooks/payload/Payload.java | 38 ++++---- .../top}/webhooks/payload/TestPayload.java | 36 +++---- .../webhooks/payload/VoteCreatePayload.java | 94 ++++++++++--------- 15 files changed, 355 insertions(+), 342 deletions(-) rename src/dropwizardWebhooks/java/{org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java => gg/top/webhooks/dropwizard/TopggWebhookEventListener.java} (65%) rename src/dropwizardWebhooks/java/{org/discordbots/webhooks/dropwizard/DBLWebhooks.java => gg/top/webhooks/dropwizard/TopggWebhooks.java} (90%) rename src/eclipseJettyWebhooks/java/{org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java => gg/top/webhooks/eclipsejetty/TopggWebhookEventListener.java} (70%) rename src/eclipseJettyWebhooks/java/{org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java => gg/top/webhooks/eclipsejetty/TopggWebhooks.java} (90%) rename src/springBootWebhooks/java/{org/discordbots/webhooks/springboot/DBLWebhooksListener.java => gg/top/webhooks/springboot/TopggWebhookEventListener.java} (68%) rename src/springBootWebhooks/java/{org/discordbots/webhooks/springboot/DBLWebhooks.java => gg/top/webhooks/springboot/TopggWebhooks.java} (88%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/entity/PartialProject.java (86%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/entity/Platform.java (70%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/entity/ProjectType.java (76%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/entity/User.java (86%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/payload/IntegrationCreatePayload.java (74%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/payload/IntegrationDeletePayload.java (80%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/payload/Payload.java (85%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/payload/TestPayload.java (55%) rename src/webhooks/java/{org/discordbots => gg/top}/webhooks/payload/VoteCreatePayload.java (78%) diff --git a/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java b/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhookEventListener.java similarity index 65% rename from src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java rename to src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhookEventListener.java index 5a76818..3b2228f 100644 --- a/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooksListener.java +++ b/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhookEventListener.java @@ -1,25 +1,25 @@ -package org.discordbots.webhooks.dropwizard; - -import jakarta.ws.rs.core.Response; -import org.discordbots.webhooks.payload.IntegrationCreatePayload; -import org.discordbots.webhooks.payload.IntegrationDeletePayload; -import org.discordbots.webhooks.payload.TestPayload; -import org.discordbots.webhooks.payload.VoteCreatePayload; - -public interface DBLWebhooksListener { - default Response onIntegrationCreate(final IntegrationCreatePayload payload, final String trace) { - return Response.status(Response.Status.NO_CONTENT).build(); - } - - default Response onIntegrationDelete(final IntegrationDeletePayload payload, final String trace) { - return Response.status(Response.Status.NO_CONTENT).build(); - } - - default Response onTest(final TestPayload payload, final String trace) { - return Response.status(Response.Status.NO_CONTENT).build(); - } - - default Response onVoteCreate(final VoteCreatePayload payload, final String trace) { - return Response.status(Response.Status.NO_CONTENT).build(); - } -} +package gg.top.webhooks.dropwizard; + +import jakarta.ws.rs.core.Response; +import gg.top.webhooks.payload.IntegrationCreatePayload; +import gg.top.webhooks.payload.IntegrationDeletePayload; +import gg.top.webhooks.payload.TestPayload; +import gg.top.webhooks.payload.VoteCreatePayload; + +public interface TopggWebhookEventListener { + default Response onIntegrationCreate(final IntegrationCreatePayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + + default Response onIntegrationDelete(final IntegrationDeletePayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + + default Response onTest(final TestPayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + + default Response onVoteCreate(final VoteCreatePayload payload, final String trace) { + return Response.status(Response.Status.NO_CONTENT).build(); + } +} diff --git a/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java b/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java similarity index 90% rename from src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java rename to src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java index 817c98e..bfa6076 100644 --- a/src/dropwizardWebhooks/java/org/discordbots/webhooks/dropwizard/DBLWebhooks.java +++ b/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java @@ -1,15 +1,5 @@ -package org.discordbots.webhooks.dropwizard; +package gg.top.webhooks.dropwizard; -import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.Response; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -19,19 +9,32 @@ import java.util.HashMap; import java.util.HexFormat; import java.util.stream.Collectors; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import org.discordbots.webhooks.payload.IntegrationCreatePayload; -import org.discordbots.webhooks.payload.IntegrationDeletePayload; -import org.discordbots.webhooks.payload.Payload; -import org.discordbots.webhooks.payload.TestPayload; -import org.discordbots.webhooks.payload.VoteCreatePayload; -public abstract class DBLWebhooks implements DBLWebhooksListener { +import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +import gg.top.webhooks.payload.IntegrationCreatePayload; +import gg.top.webhooks.payload.IntegrationDeletePayload; +import gg.top.webhooks.payload.Payload; +import gg.top.webhooks.payload.TestPayload; +import gg.top.webhooks.payload.VoteCreatePayload; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; + +public abstract class TopggWebhooks implements TopggWebhookEventListener { private byte[] secret; private final Gson gson; - public DBLWebhooks(final String secret) { + public TopggWebhooks(final String secret) { this.secret = secret.getBytes(StandardCharsets.UTF_8); this.gson = new GsonBuilder() diff --git a/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java b/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhookEventListener.java similarity index 70% rename from src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java rename to src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhookEventListener.java index cee3a90..22e9914 100644 --- a/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooksListener.java +++ b/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhookEventListener.java @@ -1,33 +1,33 @@ -package org.discordbots.webhooks.eclipsejetty; - -import jakarta.servlet.http.HttpServletResponse; -import org.discordbots.webhooks.payload.IntegrationCreatePayload; -import org.discordbots.webhooks.payload.IntegrationDeletePayload; -import org.discordbots.webhooks.payload.TestPayload; -import org.discordbots.webhooks.payload.VoteCreatePayload; - -public interface DBLWebhooksListener { - default void onIntegrationCreate( - final HttpServletResponse response, - final IntegrationCreatePayload payload, - final String trace) { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - } - - default void onIntegrationDelete( - final HttpServletResponse response, - final IntegrationDeletePayload payload, - final String trace) { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - } - - default void onTest( - final HttpServletResponse response, final TestPayload payload, final String trace) { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - } - - default void onVoteCreate( - final HttpServletResponse response, final VoteCreatePayload payload, final String trace) { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - } -} +package gg.top.webhooks.eclipsejetty; + +import jakarta.servlet.http.HttpServletResponse; +import gg.top.webhooks.payload.IntegrationCreatePayload; +import gg.top.webhooks.payload.IntegrationDeletePayload; +import gg.top.webhooks.payload.TestPayload; +import gg.top.webhooks.payload.VoteCreatePayload; + +public interface TopggWebhookEventListener { + default void onIntegrationCreate( + final HttpServletResponse response, + final IntegrationCreatePayload payload, + final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + default void onIntegrationDelete( + final HttpServletResponse response, + final IntegrationDeletePayload payload, + final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + default void onTest( + final HttpServletResponse response, final TestPayload payload, final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + default void onVoteCreate( + final HttpServletResponse response, final VoteCreatePayload payload, final String trace) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } +} diff --git a/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java b/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java similarity index 90% rename from src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java rename to src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java index c27d3f7..994e104 100644 --- a/src/eclipseJettyWebhooks/java/org/discordbots/webhooks/eclipsejetty/DBLWebhooks.java +++ b/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java @@ -1,14 +1,5 @@ -package org.discordbots.webhooks.eclipsejetty; +package gg.top.webhooks.eclipsejetty; -import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -18,19 +9,31 @@ import java.util.HashMap; import java.util.HexFormat; import java.util.stream.Collectors; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import org.discordbots.webhooks.payload.IntegrationCreatePayload; -import org.discordbots.webhooks.payload.IntegrationDeletePayload; -import org.discordbots.webhooks.payload.Payload; -import org.discordbots.webhooks.payload.TestPayload; -import org.discordbots.webhooks.payload.VoteCreatePayload; -public class DBLWebhooks extends HttpServlet implements DBLWebhooksListener { +import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +import gg.top.webhooks.payload.IntegrationCreatePayload; +import gg.top.webhooks.payload.IntegrationDeletePayload; +import gg.top.webhooks.payload.Payload; +import gg.top.webhooks.payload.TestPayload; +import gg.top.webhooks.payload.VoteCreatePayload; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class TopggWebhooks extends HttpServlet implements TopggWebhookEventListener { private byte[] secret; private final Gson gson; - public DBLWebhooks(final String secret) { + public TopggWebhooks(final String secret) { this.secret = secret.getBytes(StandardCharsets.UTF_8); this.gson = new GsonBuilder() diff --git a/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java b/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhookEventListener.java similarity index 68% rename from src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java rename to src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhookEventListener.java index c1f2df6..89b7a26 100644 --- a/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooksListener.java +++ b/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhookEventListener.java @@ -1,28 +1,28 @@ -package org.discordbots.webhooks.springboot; - -import org.discordbots.webhooks.payload.IntegrationCreatePayload; -import org.discordbots.webhooks.payload.IntegrationDeletePayload; -import org.discordbots.webhooks.payload.TestPayload; -import org.discordbots.webhooks.payload.VoteCreatePayload; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -public interface DBLWebhooksListener { - default ResponseEntity onIntegrationCreate( - final IntegrationCreatePayload payload, final String trace) { - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); - } - - default ResponseEntity onIntegrationDelete( - final IntegrationDeletePayload payload, final String trace) { - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); - } - - default ResponseEntity onTest(final TestPayload payload, final String trace) { - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); - } - - default ResponseEntity onVoteCreate(final VoteCreatePayload payload, final String trace) { - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); - } -} +package gg.top.webhooks.springboot; + +import gg.top.webhooks.payload.IntegrationCreatePayload; +import gg.top.webhooks.payload.IntegrationDeletePayload; +import gg.top.webhooks.payload.TestPayload; +import gg.top.webhooks.payload.VoteCreatePayload; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public interface TopggWebhookEventListener { + default ResponseEntity onIntegrationCreate( + final IntegrationCreatePayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + default ResponseEntity onIntegrationDelete( + final IntegrationDeletePayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + default ResponseEntity onTest(final TestPayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + default ResponseEntity onVoteCreate(final VoteCreatePayload payload, final String trace) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } +} diff --git a/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java b/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java similarity index 88% rename from src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java rename to src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java index e6ebee0..d57c5fb 100644 --- a/src/springBootWebhooks/java/org/discordbots/webhooks/springboot/DBLWebhooks.java +++ b/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java @@ -1,10 +1,5 @@ -package org.discordbots.webhooks.springboot; +package gg.top.webhooks.springboot; -import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -13,21 +8,30 @@ import java.util.HashMap; import java.util.HexFormat; import java.util.stream.Collectors; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import org.discordbots.webhooks.payload.IntegrationCreatePayload; -import org.discordbots.webhooks.payload.IntegrationDeletePayload; -import org.discordbots.webhooks.payload.Payload; -import org.discordbots.webhooks.payload.TestPayload; -import org.discordbots.webhooks.payload.VoteCreatePayload; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -public class DBLWebhooks implements DBLWebhooksListener { +import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +import gg.top.webhooks.payload.IntegrationCreatePayload; +import gg.top.webhooks.payload.IntegrationDeletePayload; +import gg.top.webhooks.payload.Payload; +import gg.top.webhooks.payload.TestPayload; +import gg.top.webhooks.payload.VoteCreatePayload; + +public class TopggWebhooks implements TopggWebhookEventListener { private byte[] secret; private final Gson gson; - public DBLWebhooks(final String secret) { + public TopggWebhooks(final String secret) { this.secret = secret.getBytes(StandardCharsets.UTF_8); this.gson = new GsonBuilder() diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java b/src/webhooks/java/gg/top/webhooks/entity/PartialProject.java similarity index 86% rename from src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java rename to src/webhooks/java/gg/top/webhooks/entity/PartialProject.java index 85fff1d..0a4557d 100644 --- a/src/webhooks/java/org/discordbots/webhooks/entity/PartialProject.java +++ b/src/webhooks/java/gg/top/webhooks/entity/PartialProject.java @@ -1,30 +1,30 @@ -package org.discordbots.webhooks.entity; - -import com.google.gson.annotations.SerializedName; - -public class PartialProject { - private String id; - - private ProjectType type; - - private Platform platform; - - @SerializedName("platform_id") - private String platformId; - - public String getId() { - return id; - } - - public ProjectType getType() { - return type; - } - - public Platform getPlatform() { - return platform; - } - - public String getPlatformId() { - return platformId; - } -} +package gg.top.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public class PartialProject { + private String id; + + private ProjectType type; + + private Platform platform; + + @SerializedName("platform_id") + private String platformId; + + public String getId() { + return id; + } + + public ProjectType getType() { + return type; + } + + public Platform getPlatform() { + return platform; + } + + public String getPlatformId() { + return platformId; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/Platform.java b/src/webhooks/java/gg/top/webhooks/entity/Platform.java similarity index 70% rename from src/webhooks/java/org/discordbots/webhooks/entity/Platform.java rename to src/webhooks/java/gg/top/webhooks/entity/Platform.java index 8cb1ad2..b576d73 100644 --- a/src/webhooks/java/org/discordbots/webhooks/entity/Platform.java +++ b/src/webhooks/java/gg/top/webhooks/entity/Platform.java @@ -1,8 +1,8 @@ -package org.discordbots.webhooks.entity; - -import com.google.gson.annotations.SerializedName; - -public enum Platform { - @SerializedName("discord") - DISCORD -} +package gg.top.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public enum Platform { + @SerializedName("discord") + DISCORD +} diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java b/src/webhooks/java/gg/top/webhooks/entity/ProjectType.java similarity index 76% rename from src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java rename to src/webhooks/java/gg/top/webhooks/entity/ProjectType.java index 675f7df..46873dc 100644 --- a/src/webhooks/java/org/discordbots/webhooks/entity/ProjectType.java +++ b/src/webhooks/java/gg/top/webhooks/entity/ProjectType.java @@ -1,11 +1,11 @@ -package org.discordbots.webhooks.entity; - -import com.google.gson.annotations.SerializedName; - -public enum ProjectType { - @SerializedName("bot") - DISCORD_BOT, - - @SerializedName("server") - DISCORD_SERVER -} +package gg.top.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public enum ProjectType { + @SerializedName("bot") + DISCORD_BOT, + + @SerializedName("server") + DISCORD_SERVER +} diff --git a/src/webhooks/java/org/discordbots/webhooks/entity/User.java b/src/webhooks/java/gg/top/webhooks/entity/User.java similarity index 86% rename from src/webhooks/java/org/discordbots/webhooks/entity/User.java rename to src/webhooks/java/gg/top/webhooks/entity/User.java index 0be2afb..8469a3f 100644 --- a/src/webhooks/java/org/discordbots/webhooks/entity/User.java +++ b/src/webhooks/java/gg/top/webhooks/entity/User.java @@ -1,31 +1,31 @@ -package org.discordbots.webhooks.entity; - -import com.google.gson.annotations.SerializedName; - -public class User { - private String id; - - private String name; - - @SerializedName("avatar_url") - private String avatar; - - @SerializedName("platform_id") - private String platformId; - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public String getAvatar() { - return avatar; - } - - public String getPlatformId() { - return platformId; - } -} +package gg.top.webhooks.entity; + +import com.google.gson.annotations.SerializedName; + +public class User { + private String id; + + private String name; + + @SerializedName("avatar_url") + private String avatar; + + @SerializedName("platform_id") + private String platformId; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getAvatar() { + return avatar; + } + + public String getPlatformId() { + return platformId; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java b/src/webhooks/java/gg/top/webhooks/payload/IntegrationCreatePayload.java similarity index 74% rename from src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java rename to src/webhooks/java/gg/top/webhooks/payload/IntegrationCreatePayload.java index 99303c1..dfb0c8e 100644 --- a/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationCreatePayload.java +++ b/src/webhooks/java/gg/top/webhooks/payload/IntegrationCreatePayload.java @@ -1,33 +1,34 @@ -package org.discordbots.webhooks.payload; - -import com.google.gson.annotations.SerializedName; -import org.discordbots.webhooks.entity.PartialProject; -import org.discordbots.webhooks.entity.User; - -public class IntegrationCreatePayload { - @SerializedName("connection_id") - private String connectionId; - - @SerializedName("webhook_secret") - private String secret; - - private PartialProject project; - - private User user; - - public String getConnectionId() { - return connectionId; - } - - public String getSecret() { - return secret; - } - - public PartialProject getProject() { - return project; - } - - public User getUser() { - return user; - } -} +package gg.top.webhooks.payload; + +import com.google.gson.annotations.SerializedName; + +import gg.top.webhooks.entity.PartialProject; +import gg.top.webhooks.entity.User; + +public class IntegrationCreatePayload { + @SerializedName("connection_id") + private String connectionId; + + @SerializedName("webhook_secret") + private String secret; + + private PartialProject project; + + private User user; + + public String getConnectionId() { + return connectionId; + } + + public String getSecret() { + return secret; + } + + public PartialProject getProject() { + return project; + } + + public User getUser() { + return user; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java b/src/webhooks/java/gg/top/webhooks/payload/IntegrationDeletePayload.java similarity index 80% rename from src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java rename to src/webhooks/java/gg/top/webhooks/payload/IntegrationDeletePayload.java index 2653cda..10e2f5c 100644 --- a/src/webhooks/java/org/discordbots/webhooks/payload/IntegrationDeletePayload.java +++ b/src/webhooks/java/gg/top/webhooks/payload/IntegrationDeletePayload.java @@ -1,12 +1,12 @@ -package org.discordbots.webhooks.payload; - -import com.google.gson.annotations.SerializedName; - -public class IntegrationDeletePayload { - @SerializedName("connection_id") - private String connectionId; - - public String getConnectionId() { - return connectionId; - } -} +package gg.top.webhooks.payload; + +import com.google.gson.annotations.SerializedName; + +public class IntegrationDeletePayload { + @SerializedName("connection_id") + private String connectionId; + + public String getConnectionId() { + return connectionId; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/Payload.java b/src/webhooks/java/gg/top/webhooks/payload/Payload.java similarity index 85% rename from src/webhooks/java/org/discordbots/webhooks/payload/Payload.java rename to src/webhooks/java/gg/top/webhooks/payload/Payload.java index 424ea39..a32d109 100644 --- a/src/webhooks/java/org/discordbots/webhooks/payload/Payload.java +++ b/src/webhooks/java/gg/top/webhooks/payload/Payload.java @@ -1,19 +1,19 @@ -package org.discordbots.webhooks.payload; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; - -public class Payload { - private String type; - - private JsonObject data; - - public String getType() { - return type; - } - - public T getData(final Gson gson, final Class cls) throws JsonSyntaxException { - return gson.fromJson(data, cls); - } -} +package gg.top.webhooks.payload; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; + +public class Payload { + private String type; + + private JsonObject data; + + public String getType() { + return type; + } + + public T getData(final Gson gson, final Class cls) throws JsonSyntaxException { + return gson.fromJson(data, cls); + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java b/src/webhooks/java/gg/top/webhooks/payload/TestPayload.java similarity index 55% rename from src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java rename to src/webhooks/java/gg/top/webhooks/payload/TestPayload.java index 6a90483..0d9865d 100644 --- a/src/webhooks/java/org/discordbots/webhooks/payload/TestPayload.java +++ b/src/webhooks/java/gg/top/webhooks/payload/TestPayload.java @@ -1,18 +1,18 @@ -package org.discordbots.webhooks.payload; - -import org.discordbots.webhooks.entity.PartialProject; -import org.discordbots.webhooks.entity.User; - -public class TestPayload { - private PartialProject project; - - private User user; - - public PartialProject getProject() { - return project; - } - - public User getUser() { - return user; - } -} +package gg.top.webhooks.payload; + +import gg.top.webhooks.entity.PartialProject; +import gg.top.webhooks.entity.User; + +public class TestPayload { + private PartialProject project; + + private User user; + + public PartialProject getProject() { + return project; + } + + public User getUser() { + return user; + } +} diff --git a/src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java b/src/webhooks/java/gg/top/webhooks/payload/VoteCreatePayload.java similarity index 78% rename from src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java rename to src/webhooks/java/gg/top/webhooks/payload/VoteCreatePayload.java index 9f8e819..05c30e4 100644 --- a/src/webhooks/java/org/discordbots/webhooks/payload/VoteCreatePayload.java +++ b/src/webhooks/java/gg/top/webhooks/payload/VoteCreatePayload.java @@ -1,46 +1,48 @@ -package org.discordbots.webhooks.payload; - -import com.google.gson.annotations.SerializedName; -import java.time.OffsetDateTime; -import org.discordbots.webhooks.entity.PartialProject; -import org.discordbots.webhooks.entity.User; - -public class VoteCreatePayload { - private String id; - - private int weight; - - @SerializedName("created_at") - private OffsetDateTime votedAt; - - @SerializedName("expires_at") - private OffsetDateTime expiresAt; - - private PartialProject project; - - private User user; - - public String getId() { - return id; - } - - public int getWeight() { - return weight; - } - - public OffsetDateTime getVotedAt() { - return votedAt; - } - - public OffsetDateTime getExpiredAt() { - return expiresAt; - } - - public PartialProject getProject() { - return project; - } - - public User getUser() { - return user; - } -} +package gg.top.webhooks.payload; + +import java.time.OffsetDateTime; + +import com.google.gson.annotations.SerializedName; + +import gg.top.webhooks.entity.PartialProject; +import gg.top.webhooks.entity.User; + +public class VoteCreatePayload { + private String id; + + private int weight; + + @SerializedName("created_at") + private OffsetDateTime votedAt; + + @SerializedName("expires_at") + private OffsetDateTime expiresAt; + + private PartialProject project; + + private User user; + + public String getId() { + return id; + } + + public int getWeight() { + return weight; + } + + public OffsetDateTime getVotedAt() { + return votedAt; + } + + public OffsetDateTime getExpiredAt() { + return expiresAt; + } + + public PartialProject getProject() { + return project; + } + + public User getUser() { + return user; + } +} From 12a416aea2b50ac4c8f86bde9b3e4cb1e94f4140 Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:37:14 +0700 Subject: [PATCH 6/6] fix: add warning + return 204 for payload JSON deserialize failure and add 2 MiB body length limit --- .../top/webhooks/dropwizard/TopggWebhooks.java | 17 +++++++++++++---- .../webhooks/eclipsejetty/TopggWebhooks.java | 13 +++++++++++-- .../top/webhooks/springboot/TopggWebhooks.java | 9 +++++++++ .../gg/top/webhooks/entity/ProjectType.java | 4 ++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java b/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java index bfa6076..00f2cd8 100644 --- a/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java +++ b/src/dropwizardWebhooks/java/gg/top/webhooks/dropwizard/TopggWebhooks.java @@ -8,12 +8,14 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HexFormat; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.common.io.ByteStreams; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonIOException; @@ -31,6 +33,8 @@ import jakarta.ws.rs.core.Response; public abstract class TopggWebhooks implements TopggWebhookEventListener { + private static final Logger logger = Logger.getLogger(TopggWebhooks.class.getName()); + private byte[] secret; private final Gson gson; @@ -53,6 +57,8 @@ public void setSecret(final String newSecret) { @POST @SuppressWarnings("UseSpecificCatch") public Response handle(@Context HttpServletRequest request) throws WebApplicationException { + String body = ""; + try { final String signatureHeader = request.getHeader("x-topgg-signature"); @@ -78,8 +84,7 @@ public Response handle(@Context HttpServletRequest request) throws WebApplicatio hmac.init(key); - final String body = - new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + body = new String(ByteStreams.limit(request.getInputStream(), 2 * 1024 * 1024).readAllBytes(), StandardCharsets.UTF_8); final byte[] digest = hmac.doFinal(String.format("%s.%s", timestamp, body).getBytes(StandardCharsets.UTF_8)); @@ -116,9 +121,13 @@ public Response handle(@Context HttpServletRequest request) throws WebApplicatio | IOException error) { if (error instanceof NoSuchAlgorithmException || error instanceof InvalidKeyException) { throw new WebApplicationException("Unable to find HMAC SHA-256 algorithm", error); - } else { - return Response.status(Response.Status.BAD_REQUEST).entity("Bad Request").build(); + } else if (error instanceof JsonSyntaxException) { + logger.warning(String.format("Unable to parse Top.gg webhook payload. Please report this bug to the SDK maintainers.\nCause: %s\n--- BEGIN BODY DUMP ---\n%s\n--- END BODY DUMP ---", error.getMessage(), body)); + + return Response.status(Response.Status.NO_CONTENT).build(); } + + return Response.status(Response.Status.BAD_REQUEST).entity("Bad Request").build(); } } } diff --git a/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java b/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java index 994e104..b003011 100644 --- a/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java +++ b/src/eclipseJettyWebhooks/java/gg/top/webhooks/eclipsejetty/TopggWebhooks.java @@ -8,12 +8,14 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HexFormat; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter; +import com.google.common.io.ByteStreams; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonIOException; @@ -30,6 +32,8 @@ import jakarta.servlet.http.HttpServletResponse; public class TopggWebhooks extends HttpServlet implements TopggWebhookEventListener { + private static final Logger logger = Logger.getLogger(TopggWebhooks.class.getName()); + private byte[] secret; private final Gson gson; @@ -53,6 +57,8 @@ public void setSecret(final String newSecret) { @SuppressWarnings("UseSpecificCatch") protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { + String body = ""; + try { final String signatureHeader = request.getHeader("x-topgg-signature"); @@ -78,8 +84,7 @@ protected void doPost(final HttpServletRequest request, final HttpServletRespons hmac.init(key); - final String body = - new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + body = new String(ByteStreams.limit(request.getInputStream(), 2 * 1024 * 1024).readAllBytes(), StandardCharsets.UTF_8); final byte[] digest = hmac.doFinal(String.format("%s.%s", timestamp, body).getBytes(StandardCharsets.UTF_8)); @@ -122,6 +127,10 @@ protected void doPost(final HttpServletRequest request, final HttpServletRespons | IOException error) { if (error instanceof NoSuchAlgorithmException || error instanceof InvalidKeyException) { throw new ServletException("Unable to find HMAC SHA-256 algorithm", error); + } else if (error instanceof JsonSyntaxException) { + logger.warning(String.format("Unable to parse Top.gg webhook payload. Please report this bug to the SDK maintainers.\nCause: %s\n--- BEGIN BODY DUMP ---\n%s\n--- END BODY DUMP ---", error.getMessage(), body)); + + response.setStatus(HttpServletResponse.SC_NO_CONTENT); } else { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().write("Bad Request"); diff --git a/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java b/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java index d57c5fb..d30f425 100644 --- a/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java +++ b/src/springBootWebhooks/java/gg/top/webhooks/springboot/TopggWebhooks.java @@ -7,6 +7,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HexFormat; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.crypto.Mac; @@ -28,6 +29,8 @@ import gg.top.webhooks.payload.VoteCreatePayload; public class TopggWebhooks implements TopggWebhookEventListener { + private static final Logger logger = Logger.getLogger(TopggWebhooks.class.getName()); + private byte[] secret; private final Gson gson; @@ -99,6 +102,12 @@ protected ResponseEntity dispatch( | AssertionError | JsonSyntaxException | JsonIOException error) { + if (error instanceof JsonSyntaxException) { + logger.warning(String.format("Unable to parse Top.gg webhook payload. Please report this bug to the SDK maintainers.\nCause: %s\n--- BEGIN BODY DUMP ---\n%s\n--- END BODY DUMP ---", error.getMessage(), body)); + + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + return ResponseEntity.status( (error instanceof NoSuchAlgorithmException || error instanceof InvalidKeyException) ? HttpStatus.INTERNAL_SERVER_ERROR diff --git a/src/webhooks/java/gg/top/webhooks/entity/ProjectType.java b/src/webhooks/java/gg/top/webhooks/entity/ProjectType.java index 46873dc..7b5225c 100644 --- a/src/webhooks/java/gg/top/webhooks/entity/ProjectType.java +++ b/src/webhooks/java/gg/top/webhooks/entity/ProjectType.java @@ -4,8 +4,8 @@ public enum ProjectType { @SerializedName("bot") - DISCORD_BOT, + BOT, @SerializedName("server") - DISCORD_SERVER + SERVER }