diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..a69fe3b
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+* eol=lf
+**/*.jar binary
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..192221b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.gradle/
+build/
\ No newline at end of file
diff --git a/README.md b/README.md
index fdd1f5b..31a374e 100644
--- a/README.md
+++ b/README.md
@@ -1,106 +1,396 @@
-# DBL Java Library
-A Java wrapper for the [top.gg API](https://top.gg/api/docs)
+# Top.gg Java SDK
+
+The community-maintained Java SDK for Top.gg.
+
+## Chapters
+
+- [Installation](#installation)
+- [Capabilities](#capabilities)
+- [Setting up](#setting-up)
+- [Usage](#usage)
+ - [Getting your project's information](#getting-your-projects-information)
+ - [Getting your project's vote information of a user](#getting-your-projects-vote-information-of-a-user)
+ - [Getting a cursor-based paginated list of votes for your project](#getting-a-cursor-based-paginated-list-of-votes-for-your-project)
+ - [Posting your bot's application commands list](#posting-your-bots-application-commands-list)
+ - [Generating widget URLs](#generating-widget-urls)
+ - [Webhooks](#webhooks)
+
+## Installation
+
+### Gradle
+
+Add the following line to the `dependencies` section of your `build.gradle`:
+
+```groovy
+implementation 'gg.top:SDK:1.0.0'
+```
+
+### Maven
+
+Add the following line to the `dependencies` section of your `pom.xml`:
+
+```xml
+
+ gg.top
+ SDK
+ 1.0.0
+
+```
+
+## Capabilities
+
+This SDK provides several capabilities that can be enabled/disabled, such as:
+
+- **`jdaWrapper`**: Additional wrappers for working with [JDA](https://github.com/discord-jda/JDA).
+- **`discord4jWrapper`**: Additional wrappers for working with [Discord4J](https://github.com/Discord4J/Discord4J).
+- **`webhooks`**: Accessing deserializable webhook payload classes.
+ - **`dropwizardWebhooks`**: Wrapper for working with the [Dropwizard](https://www.dropwizard.io/en/stable/) web framework.
+ - **`eclipseJettyWebhooks`**: Wrapper for working with the [Eclipse Jetty](https://jetty.org/index.html) web framework.
+ - **`springBootWebhooks`**: Wrapper for working with the [Spring Boot](https://spring.io/projects/spring-boot/) web framework.
+
+## Setting up
+
+```java
+import gg.top.api.TopggAPI;
+
+final TopggAPI client = new TopggAPI(System.getenv("TOPGG_TOKEN"));
+```
## Usage
-First, build a DiscordBotListAPI object.
+### Getting your project's information
```java
-DiscordBotListAPI api = new DiscordBotListAPI.Builder()
- .token("token")
- .botId("botId")
- .build();
+client.getSelf().whenComplete((project, error) -> {
+ if (error != null) {
+ System.err.println("Error: " + error.getMessage());
+ } else {
+ // ...
+ }
+});
```
-#### Posting stats
+### Getting your project's vote information of a user
+
+#### Discord ID
+
+```java
+import gg.top.api.entity.UserSource;
+
+client.getVote(UserSource.DISCORD, "661200758510977084").whenComplete((vote, error) -> {
+ if (error != null) {
+ System.err.println("Error: " + error.getMessage());
+ } else {
+ // ...
+ }
+});
+```
-DBL provides three ways to post your bots stats.
+#### Top.gg ID
-**#1**
-Posts the server count for the whole bot.
```java
-int serverCount = ...; // the total amount of servers across all shards
+import gg.top.api.entity.UserSource;
-api.setStats(serverCount);
+client.getVote(UserSource.TOPGG, "8226924471638491136").whenComplete((vote, error) -> {
+ if (error != null) {
+ System.err.println("Error: " + error.getMessage());
+ } else {
+ // ...
+ }
+});
```
-**#2**
-Posts the server count for an individual shard.
+### Getting a cursor-based paginated list of votes for your project
+
```java
-int shardId = ...; // the id of this shard
-int shardCount = ...; // the amount of shards
-int serverCount = ...; // the server count of this shard
+import gg.top.api.entity.Vote;
+
+final OffsetDateTime since = OffsetDateTime.of(2026, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
+
+client.getVotes(since).whenComplete((firstPage, error) -> {
+ if (error != null) {
+ System.err.println("Error: " + error.getMessage());
+ } else {
+ final List firstPageVotes = firstPage.getVotes();
+
+ firstPage.next().whenComplete((secondPage, secondError) => {
+ if (secondError != null) {
+ System.err.println("Error: " + secondError.getMessage());
+ } else {
+ final List secondPageVotes = secondPage.getVotes();
-api.setStats(shardId, shardCount, serverCount);
+ // ...
+ }
+ });
+ }
+});
```
-**#3**
-Posts the server counts for every shard in one request.
+### Posting your bot's application commands list
+
+#### JDA
+
+> **NOTE**: This requires the `jdaWrapper` capability.
+
```java
-List shardServerCounts = ...; // a list of all the shards' server counts
+final JDA jda = ...;
-api.setStats(shardServerCounts);
+client.postCommands(jda);
```
-#### Checking votes
+#### Discord4J
+
+> **NOTE**: This requires the `discord4jWrapper` capability.
```java
-String userId = ...; // ID of the user you're checking
-api.hasVoted(userId).whenComplete((hasVoted, e) -> {
- if(hasVoted)
- System.out.println("This person has voted!");
- else
- System.out.println("This person has not voted!");
-});
+final DiscordClient bot = ...;
+
+client.postCommands(bot);
```
-#### Getting voting multiplier
+#### Raw
```java
-api.getVotingMultiplier().whenComplete((multiplier, e) -> {
- if(multiplier.isWeekend())
- System.out.println("It's the weekend, so votes are worth 2x!");
- else
- System.out.println("It's not the weekend :pensive:");
-});
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+
+// Array of application commands that
+// can be serialized to Discord API's raw JSON format.
+final String commandsJson =
+ "[{" +
+ " \"id\": \"1\"," +
+ " \"type\": 1," +
+ " \"application_id\": \"1\"," +
+ " \"name\": \"test\"," +
+ " \"description\": \"command description\"," +
+ " \"default_member_permissions\": \"\"," +
+ " \"version\": \"1\"" +
+ "}]";
+
+final JsonArray commands = JsonParser.parseString(commandsJson).getAsJsonArray();
+
+client.postCommands(commands);
```
-## Download
+### Generating widget URLs
-[](https://jitpack.io/#top-gg/java-sdk)
+#### Large
-Replace `VERSION` with the latest version or commit hash. The latest version can be found under releases.
+```java
+import gg.top.api.TopggWidget;
+import gg.top.api.entity.Platform;
+import gg.top.api.entity.ProjectType;
-#### Maven
+final String widgetUrl = TopggWidget.large(Platform.DISCORD, ProjectType.BOT, "1026525568344264724");
+```
-```xml
-
-
- jitpack.io
- https://jitpack.io
-
-
+#### Votes
+
+```java
+import gg.top.api.TopggWidget;
+import gg.top.api.entity.Platform;
+import gg.top.api.entity.ProjectType;
+
+final String widgetUrl = TopggWidget.votes(Platform.DISCORD, ProjectType.BOT, "1026525568344264724");
```
-```xml
-
-
- com.github.top-gg
- java-sdk
- VERSION
-
-
-```
-
-#### Gradle
-```gradle
-repositories {
- maven { url 'https://jitpack.io' }
+
+#### Owner
+
+```java
+import gg.top.api.TopggWidget;
+import gg.top.api.entity.Platform;
+import gg.top.api.entity.ProjectType;
+
+final String widgetUrl = TopggWidget.owner(Platform.DISCORD, ProjectType.BOT, "1026525568344264724");
+```
+
+#### Social
+
+```java
+import gg.top.api.TopggWidget;
+import gg.top.api.entity.Platform;
+import gg.top.api.entity.ProjectType;
+
+final String widgetUrl = TopggWidget.social(Platform.DISCORD, ProjectType.BOT, "1026525568344264724");
+```
+
+### Webhooks
+
+#### Dropwizard
+
+> **NOTE**: This requires the `dropwizardWebhooks` capability.
+
+In your `Webhooks.java`:
+
+```java
+import gg.top.webhooks.dropwizard.TopggWebhooks;
+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 jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+
+// POST /webhook
+@Path("/webhook")
+public class Webhooks extends TopggWebhooks {
+ public Webhooks() {
+ super(System.getenv("TOPGG_WEBHOOK_SECRET"));
+ }
+
+ // Optional
+ @Override
+ public Response onIntegrationCreate(final IntegrationCreatePayload payload, final String trace) {
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+ // Optional
+ @Override
+ public Response onIntegrationDelete(final IntegrationDeletePayload payload, final String trace) {
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+ // Optional
+ @Override
+ public Response onTest(final TestPayload payload, final String trace) {
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+ // Optional
+ @Override
+ public Response onVoteCreate(final VoteCreatePayload payload, final String trace) {
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
}
```
-```gradle
-dependencies {
- compile 'com.github.top-gg:java-sdk:VERSION'
+
+Later, in your server's `run` function:
+
+```java
+env.jersey().register(new Webhooks());
+```
+
+#### Eclipse Jetty
+
+> **NOTE**: This requires the `eclipseJettyWebhooks` capability.
+
+In your `Webhooks.java`:
+
+```java
+import gg.top.webhooks.eclipsejetty.TopggWebhooks;
+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 jakarta.servlet.http.HttpServletResponse;
+
+public class Webhooks extends TopggWebhooks {
+ public Webhooks() {
+ super(System.getenv("TOPGG_WEBHOOK_SECRET"));
+ }
+
+ // Optional
+ @Override
+ public void onIntegrationCreate(
+ final HttpServletResponse response,
+ final IntegrationCreatePayload payload,
+ final String trace) {
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ }
+
+ // Optional
+ @Override
+ public void onIntegrationDelete(
+ final HttpServletResponse response,
+ final IntegrationDeletePayload payload,
+ final String trace) {
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ }
+
+ // Optional
+ @Override
+ public void onTest(
+ final HttpServletResponse response, final TestPayload payload, final String trace) {
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ }
+
+ // Optional
+ @Override
+ public void onVoteCreate(
+ final HttpServletResponse response, final VoteCreatePayload payload, final String trace) {
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ }
}
```
+Later, in your server's setup:
+
+```java
+// POST /webhook
+context.addServlet(new ServletHolder(new Webhooks()), "/webhook");
+```
+
+#### Spring Boot
+
+> **NOTE**: This requires the `springBootWebhooks` capability.
+
+In your `Webhooks.java`:
+
+```java
+import gg.top.webhooks.springboot.TopggWebhooks;
+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;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class Webhooks extends TopggWebhooks {
+ public Webhooks() {
+ super(System.getenv("TOPGG_WEBHOOK_SECRET"));
+ }
+ // POST /webhook
+ @PostMapping("/webhook")
+ public ResponseEntity main(
+ @RequestBody final String body,
+ @RequestHeader("x-topgg-signature") final String signature,
+ @RequestHeader("x-topgg-trace") final String trace) {
+ return dispatch(body, signature, trace);
+ }
+
+ // Optional
+ @Override
+ public ResponseEntity onIntegrationCreate(
+ final IntegrationCreatePayload payload, final String trace) {
+ return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
+ }
+
+ // Optional
+ @Override
+ public ResponseEntity onIntegrationDelete(
+ final IntegrationDeletePayload payload, final String trace) {
+ return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
+ }
+
+ // Optional
+ @Override
+ public ResponseEntity onTest(final TestPayload payload, final String trace) {
+ return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
+ }
+
+ // Optional
+ @Override
+ public ResponseEntity onVoteCreate(final VoteCreatePayload payload, final String trace) {
+ return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
+ }
+}
+```
diff --git a/build.gradle b/build.gradle
index 023b55d..1bcdb00 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,19 +1,223 @@
-group 'org.discordbots'
+plugins {
+ id 'java-library'
+ id 'maven-publish'
+}
+
+def ver = '1.0.0'
+
+group = 'gg.top'
+version = ver
+description = 'The community-maintained Java SDK for Top.gg.'
-apply plugin: 'java'
-apply plugin: 'maven'
+if (JavaVersion.current() < JavaVersion.VERSION_17) {
+ throw new GradleException('Top.gg\'s Java SDK requires Java 17 or later. Java ${JavaVersion.current()} is not supported.')
+}
repositories {
- mavenCentral()
+ mavenCentral()
+}
+
+sourceSets {
+ jdaWrapper
+ discord4jWrapper
+
+ webhooks
+ dropwizardWebhooks
+ eclipseJettyWebhooks
+ springBootWebhooks
+}
+
+configurations {
+ dropwizardWebhooksImplementation.extendsFrom webhooksImplementation
+ eclipseJettyWebhooksImplementation.extendsFrom webhooksImplementation
+ springBootWebhooksImplementation.extendsFrom webhooksImplementation
+
+
+ testImplementation.extendsFrom dropwizardWebhooksImplementation
+ testImplementation.extendsFrom eclipseJettyWebhooksImplementation
+ testImplementation.extendsFrom springBootWebhooksImplementation
+
+ googleJavaFormat
+}
+
+test {
+ useJUnitPlatform()
+}
+
+java {
+ withSourcesJar()
+
+ registerFeature('jdaWrapper') {
+ usingSourceSet sourceSets.jdaWrapper
+
+ capability('gg.top', 'jdaWrapper', ver)
+ }
+
+ registerFeature('discord4jWrapper') {
+ usingSourceSet sourceSets.discord4jWrapper
+
+ capability('gg.top', 'discord4jWrapper', ver)
+ }
+
+ registerFeature('webhooks') {
+ usingSourceSet sourceSets.webhooks
+
+ capability('gg.top', 'webhooks', ver)
+ }
+
+ registerFeature('dropwizardWebhooks') {
+ usingSourceSet sourceSets.webhooks
+ usingSourceSet sourceSets.dropwizardWebhooks
+
+ capability('gg.top', 'dropwizardWebhooks', ver)
+ }
+
+ registerFeature('eclipseJettyWebhooks') {
+ usingSourceSet sourceSets.webhooks
+ usingSourceSet sourceSets.eclipseJettyWebhooks
+
+ capability('gg.top', 'eclipseJettyWebhooks', ver)
+ }
+
+ registerFeature('springBootWebhooks') {
+ usingSourceSet sourceSets.webhooks
+ usingSourceSet sourceSets.springBootWebhooks
+
+ capability('gg.top', 'springBootWebhooks', ver)
+ }
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifactId = 'SDK'
+ groupId = 'gg.top'
+ version = ver
+
+ from components.java
+
+ pom {
+ name = 'SDK'
+ description = 'The community-maintained Java SDK for Top.gg.'
+ url = 'https://github.com/top-gg-community/java-sdk'
+ inceptionYear = '2020'
+
+ licenses {
+ license {
+ name = 'Apache License 2.0'
+ distribution = 'repo'
+ url = 'https://github.com/top-gg-community/java-sdk/blob/$ver/LICENSE'
+ }
+ }
+
+ developers {
+ developer {
+ id = 'top-gg-community'
+ name = 'Top.gg'
+ url = 'https://github.com/top-gg-community'
+ }
+ }
+
+ scm {
+ url = 'https://github.com/top-gg-community/java-sdk'
+ connection = 'scm:git:github.com/top-gg-community/java-sdk.git'
+ developerConnection = 'scm:git:ssh://github.com/top-gg-community/java-sdk.git'
+ }
+
+ issueManagement {
+ system = 'GitHub'
+ url = 'https://github.com/top-gg-community/java-sdk/issues'
+ }
+
+ ciManagement {
+ system = 'Github Actions'
+ url = 'https://github.com/top-gg-community/java-sdk/actions'
+ }
+ }
+ }
+ }
+
+ repositories {
+ maven {
+ url = layout.buildDirectory.dir 'staging-deploy'
+ }
+ }
}
dependencies {
+ implementation 'org.slf4j:slf4j-api:2.0.17'
+
+ implementation 'org.json:json:20251224'
+ api 'com.squareup.okhttp3:okhttp:5.3.2'
+ implementation 'com.google.code.gson:gson:2.13.2'
+ implementation 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
+
+ jdaWrapperImplementation sourceSets.main.output
+ jdaWrapperImplementation 'com.google.code.gson:gson:2.13.2'
+ jdaWrapperApi('net.dv8tion:JDA:6.3.1') {
+ exclude module: 'opus-java'
+ exclude module: 'tink'
+ }
+
+ discord4jWrapperImplementation sourceSets.main.output
+ discord4jWrapperApi 'com.discord4j:discord4j-core:3.3.1'
+
+ webhooksImplementation 'com.google.code.gson:gson:2.13.2'
+ webhooksImplementation 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
+
+ dropwizardWebhooksImplementation sourceSets.webhooks.output
+ dropwizardWebhooksImplementation 'jakarta.ws.rs:jakarta.ws.rs-api:4.0.0'
+ dropwizardWebhooksImplementation 'jakarta.servlet:jakarta.servlet-api:6.1.0'
+ dropwizardWebhooksImplementation 'com.google.guava:guava:33.5.0-jre'
+
+ eclipseJettyWebhooksImplementation sourceSets.webhooks.output
+ eclipseJettyWebhooksApi 'jakarta.servlet:jakarta.servlet-api:6.1.0'
+ eclipseJettyWebhooksImplementation 'com.google.guava:guava:33.5.0-jre'
- //Logger
- compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
+ springBootWebhooksImplementation sourceSets.webhooks.output
+ springBootWebhooksImplementation 'org.springframework.boot:spring-boot-starter-web:4.0.3'
+ springBootWebhooksImplementation 'jakarta.servlet:jakarta.servlet-api:6.1.0'
+
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.3'
+ testImplementation 'org.junit.platform:junit-platform-suite-api:6.0.3'
+ testImplementation 'com.google.code.gson:gson:2.13.2'
+ testImplementation 'io.dropwizard:dropwizard-testing:5.0.1'
+ testImplementation 'org.springframework.boot:spring-boot-starter-test-classic:4.0.3'
+ testImplementation 'org.springframework.boot:spring-boot-starter-security-test:4.0.3'
+
+ testImplementation sourceSets.dropwizardWebhooks.output
+ testImplementation sourceSets.eclipseJettyWebhooks.output
+ testImplementation sourceSets.springBootWebhooks.output
+
+ testCompileOnly 'org.junit.jupiter:junit-jupiter-params:6.0.3'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:6.0.3'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher:6.0.3'
+
+ googleJavaFormat 'com.google.googlejavaformat:google-java-format:1.34.1'
+}
+
+tasks.register('format', JavaExec) {
+ group = 'format'
+ description = 'Format code'
+ classpath = configurations.googleJavaFormat
+ mainClass = 'com.google.googlejavaformat.java.Main'
+
+ def sources = fileTree(dir: 'src', include: '**/*.java')
+ args = ['-i'] + sources
+
+ inputs.files sources
+ outputs.files sources
+
+ jvmArgs(
+ '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
+ '--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
+ '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
+ '--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
+ '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
+ '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
+ )
+}
- compile group: 'org.json', name: 'json', version: '20180130'
- compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.11.0'
- compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
- compile group: 'com.fatboyindustrial.gson-javatime-serialisers', name: 'gson-javatime-serialisers', version: '1.1.1'
+tasks.withType(JavaCompile) {
+ options.compilerArgs << '-Xlint:deprecation'
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 1948b90..61285a6 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3eba147..37f78a6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Sat Jul 14 00:30:34 EDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip
diff --git a/gradlew b/gradlew
index cccdd3d..adff685 100644
--- a/gradlew
+++ b/gradlew
@@ -1,78 +1,128 @@
-#!/usr/bin/env sh
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,92 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=$((i+1))
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
fi
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index f955316..c4bdd3a 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,4 +1,22 @@
-@if "%DEBUG%" == "" @echo off
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -9,25 +27,29 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -35,48 +57,35 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/src/discord4jWrapper/java/gg/top/api/discord4j/Discord4JPostCommandsTransformer.java b/src/discord4jWrapper/java/gg/top/api/discord4j/Discord4JPostCommandsTransformer.java
new file mode 100644
index 0000000..d520b48
--- /dev/null
+++ b/src/discord4jWrapper/java/gg/top/api/discord4j/Discord4JPostCommandsTransformer.java
@@ -0,0 +1,37 @@
+package gg.top.api.discord4j;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import discord4j.common.JacksonResources;
+import discord4j.core.DiscordClient;
+import java.util.concurrent.CompletionStage;
+import gg.top.api.io.PostCommandsTransformer;
+
+public class Discord4JPostCommandsTransformer implements PostCommandsTransformer {
+ private final DiscordClient client;
+
+ public Discord4JPostCommandsTransformer(final DiscordClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public CompletionStage toJsonString() {
+ return this.client
+ .getApplicationId()
+ .toFuture()
+ .thenCompose(
+ applicationId ->
+ this.client
+ .getApplicationService()
+ .getGlobalApplicationCommands(applicationId)
+ .collectList()
+ .toFuture())
+ .thenApply(
+ commands -> {
+ try {
+ return JacksonResources.create().getObjectMapper().writeValueAsString(commands);
+ } catch (final JsonProcessingException ignored) {
+ return "[]";
+ }
+ });
+ }
+}
diff --git a/src/jdaWrapper/java/gg/top/api/jda/JDAPostCommandsTransformer.java b/src/jdaWrapper/java/gg/top/api/jda/JDAPostCommandsTransformer.java
new file mode 100644
index 0000000..f1027b5
--- /dev/null
+++ b/src/jdaWrapper/java/gg/top/api/jda/JDAPostCommandsTransformer.java
@@ -0,0 +1,34 @@
+package gg.top.api.jda;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import java.util.concurrent.CompletionStage;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.interactions.commands.Command;
+import net.dv8tion.jda.api.interactions.commands.build.CommandData;
+import gg.top.api.io.PostCommandsTransformer;
+
+public class JDAPostCommandsTransformer implements PostCommandsTransformer {
+ private final JDA jda;
+
+ public JDAPostCommandsTransformer(final JDA jda) {
+ this.jda = jda;
+ }
+
+ @Override
+ public CompletionStage toJsonString() {
+ return jda.retrieveCommands()
+ .submit()
+ .thenApply(
+ commands -> {
+ final JsonArray object = new JsonArray();
+
+ for (final Command command : commands) {
+ object.add(
+ JsonParser.parseString(CommandData.fromCommand(command).toData().toString()));
+ }
+
+ return object.toString();
+ });
+ }
+}
diff --git a/src/main/java/gg/top/api/TopggAPI.java b/src/main/java/gg/top/api/TopggAPI.java
new file mode 100644
index 0000000..0652015
--- /dev/null
+++ b/src/main/java/gg/top/api/TopggAPI.java
@@ -0,0 +1,192 @@
+package gg.top.api;
+
+import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import gg.top.api.entity.PaginatedVotes;
+import gg.top.api.entity.PartialVote;
+import gg.top.api.entity.Project;
+import gg.top.api.entity.UserSource;
+import gg.top.api.io.DefaultResponseTransformer;
+import gg.top.api.io.EmptyResponseTransformer;
+import gg.top.api.io.PaginatedVotesConverter;
+import gg.top.api.io.PostCommandsTransformer;
+import gg.top.api.io.RawPostCommandsTransformer;
+import gg.top.api.io.ResponseTransformer;
+import gg.top.api.io.UnsuccessfulHttpException;
+
+public class TopggAPI {
+ private static final HttpUrl BASE_URL =
+ new HttpUrl.Builder()
+ .scheme("https")
+ .host("top.gg")
+ .addPathSegment("api")
+ .addPathSegment("v1")
+ .build();
+
+ private final OkHttpClient httpClient;
+ private final Gson gson;
+
+ public TopggAPI(final OkHttpClient httpClient) {
+ gson =
+ new GsonBuilder()
+ .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeConverter())
+ .registerTypeAdapter(PaginatedVotes.class, new PaginatedVotesConverter(this))
+ .create();
+
+ this.httpClient = httpClient;
+ }
+
+ public TopggAPI(final String token) {
+ this(
+ new OkHttpClient.Builder()
+ .addInterceptor(
+ (chain) ->
+ chain.proceed(
+ chain
+ .request()
+ .newBuilder()
+ .addHeader("Authorization", "Bearer " + token)
+ .build()))
+ .build());
+ }
+
+ public CompletionStage getSelf() {
+ final HttpUrl url =
+ BASE_URL.newBuilder().addPathSegment("projects").addPathSegment("@me").build();
+
+ return get(url, Project.class);
+ }
+
+ public CompletionStage postCommands(final PostCommandsTransformer commands) {
+ final HttpUrl url =
+ BASE_URL
+ .newBuilder()
+ .addPathSegment("projects")
+ .addPathSegment("@me")
+ .addPathSegment("commands")
+ .build();
+
+ return commands
+ .toJsonString()
+ .thenCompose(jsonBody -> post(url, jsonBody, new EmptyResponseTransformer()));
+ }
+
+ public CompletionStage postCommands(final JsonArray commands) {
+ return postCommands(new RawPostCommandsTransformer(commands));
+ }
+
+ public CompletionStage getVote(final UserSource userSource, final String id) {
+ final HttpUrl url =
+ BASE_URL
+ .newBuilder()
+ .addPathSegment("projects")
+ .addPathSegment("@me")
+ .addPathSegment("votes")
+ .addPathSegment(id)
+ .addQueryParameter("source", gson.toJson(userSource))
+ .build();
+
+ return get(url, PartialVote.class)
+ .exceptionally(
+ error -> {
+ if (error instanceof UnsuccessfulHttpException
+ && ((UnsuccessfulHttpException) error).getResponse().code() == 404) {
+ return null;
+ }
+
+ throw new CompletionException(error);
+ });
+ }
+
+ public CompletionStage getVotes(final TemporalAccessor since) {
+ final HttpUrl url =
+ BASE_URL
+ .newBuilder()
+ .addPathSegment("projects")
+ .addPathSegment("@me")
+ .addPathSegment("votes")
+ .addQueryParameter("startDate", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(since))
+ .build();
+
+ return get(url, PaginatedVotes.class);
+ }
+
+ public CompletionStage getVotes(final String cursor) {
+ final HttpUrl url =
+ BASE_URL
+ .newBuilder()
+ .addPathSegment("projects")
+ .addPathSegment("@me")
+ .addPathSegment("votes")
+ .addQueryParameter("cursor", cursor)
+ .build();
+
+ return get(url, PaginatedVotes.class);
+ }
+
+ private CompletionStage get(final HttpUrl url, final Class aClass) {
+ return get(url, new DefaultResponseTransformer<>(aClass, gson));
+ }
+
+ private CompletionStage get(
+ final HttpUrl url, final ResponseTransformer responseTransformer) {
+ return execute(new Request.Builder().get().url(url).build(), responseTransformer);
+ }
+
+ private CompletionStage post(
+ final HttpUrl url, final String jsonBody, final ResponseTransformer responseTransformer) {
+ final RequestBody body = RequestBody.create(jsonBody, MediaType.parse("application/json"));
+ final Request req = new Request.Builder().post(body).url(url).build();
+
+ return execute(req, responseTransformer);
+ }
+
+ private CompletionStage execute(
+ final Request request, final ResponseTransformer responseTransformer) {
+ final Call call = httpClient.newCall(request);
+ final CompletableFuture future = new CompletableFuture<>();
+
+ call.enqueue(
+ new Callback() {
+ @Override
+ public void onFailure(final Call call, final IOException error) {
+ future.completeExceptionally(error);
+ }
+
+ @Override
+ @SuppressWarnings("UseSpecificCatch")
+ public void onResponse(final Call call, final Response response) {
+ try {
+ if (response.isSuccessful()) {
+ future.complete(responseTransformer.transform(response));
+ } else {
+ future.completeExceptionally(new UnsuccessfulHttpException(response));
+ }
+ } catch (final Throwable error) {
+ future.completeExceptionally(error);
+ } finally {
+ response.body().close();
+ }
+ }
+ });
+
+ return future;
+ }
+}
diff --git a/src/main/java/gg/top/api/TopggWidget.java b/src/main/java/gg/top/api/TopggWidget.java
new file mode 100644
index 0000000..cb462ae
--- /dev/null
+++ b/src/main/java/gg/top/api/TopggWidget.java
@@ -0,0 +1,24 @@
+package gg.top.api;
+
+import gg.top.api.entity.Platform;
+import gg.top.api.entity.ProjectType;
+
+public final class TopggWidget {
+ private static final String BASE_URL = "https://top.gg/api/v1/widgets";
+
+ public static String large(final Platform platform, final ProjectType projectType, final String id) {
+ return String.format("%s/large/%s/%s/%s", BASE_URL, platform.name().toLowerCase(), projectType.name().toLowerCase(), id);
+ }
+
+ public static String votes(final Platform platform, final ProjectType projectType, final String id) {
+ return String.format("%s/small/votes/%s/%s/%s", BASE_URL, platform.name().toLowerCase(), projectType.name().toLowerCase(), id);
+ }
+
+ public static String owner(final Platform platform, final ProjectType projectType, final String id) {
+ return String.format("%s/small/owner/%s/%s/%s", BASE_URL, platform.name().toLowerCase(), projectType.name().toLowerCase(), id);
+ }
+
+ public static String social(final Platform platform, final ProjectType projectType, final String id) {
+ return String.format("%s/small/social/%s/%s/%s", BASE_URL, platform.name().toLowerCase(), projectType.name().toLowerCase(), id);
+ }
+}
diff --git a/src/main/java/gg/top/api/entity/PaginatedVotes.java b/src/main/java/gg/top/api/entity/PaginatedVotes.java
new file mode 100644
index 0000000..87191fb
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/PaginatedVotes.java
@@ -0,0 +1,25 @@
+package gg.top.api.entity;
+
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import gg.top.api.TopggAPI;
+
+public class PaginatedVotes {
+ private final List votes;
+ private final String cursor;
+ private final TopggAPI client;
+
+ public PaginatedVotes(final List votes, final String cursor, final TopggAPI client) {
+ this.votes = votes;
+ this.cursor = cursor;
+ this.client = client;
+ }
+
+ public List getVotes() {
+ return votes;
+ }
+
+ public CompletionStage next() {
+ return client.getVotes(cursor);
+ }
+}
diff --git a/src/main/java/gg/top/api/entity/PartialVote.java b/src/main/java/gg/top/api/entity/PartialVote.java
new file mode 100644
index 0000000..bff2371
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/PartialVote.java
@@ -0,0 +1,26 @@
+package gg.top.api.entity;
+
+import com.google.gson.annotations.SerializedName;
+import java.time.OffsetDateTime;
+
+public class PartialVote {
+ @SerializedName("created_at")
+ private OffsetDateTime votedAt;
+
+ @SerializedName("expires_at")
+ private OffsetDateTime expiresAt;
+
+ private int weight;
+
+ public OffsetDateTime getVotedAt() {
+ return votedAt;
+ }
+
+ public OffsetDateTime getExpiresAt() {
+ return expiresAt;
+ }
+
+ public int getWeight() {
+ return weight;
+ }
+}
diff --git a/src/main/java/gg/top/api/entity/Platform.java b/src/main/java/gg/top/api/entity/Platform.java
new file mode 100644
index 0000000..efc6a9c
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/Platform.java
@@ -0,0 +1,8 @@
+package gg.top.api.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum Platform {
+ @SerializedName("discord")
+ DISCORD
+}
diff --git a/src/main/java/gg/top/api/entity/Project.java b/src/main/java/gg/top/api/entity/Project.java
new file mode 100644
index 0000000..bf5f000
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/Project.java
@@ -0,0 +1,65 @@
+package gg.top.api.entity;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+public class Project {
+ private String id;
+ private String name;
+ private Platform platform;
+ private ProjectType type;
+ private String headline;
+ private List tags;
+
+ @SerializedName("votes")
+ private long currentVotes;
+
+ @SerializedName("votes_total")
+ private long totalVotes;
+
+ @SerializedName("review_score")
+ private float reviewScore;
+
+ @SerializedName("review_count")
+ private long reviewCount;
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Platform getPlatform() {
+ return platform;
+ }
+
+ public ProjectType getType() {
+ return type;
+ }
+
+ public String getHeadline() {
+ return headline;
+ }
+
+ public List getTags() {
+ return tags;
+ }
+
+ public long getCurrentVotes() {
+ return currentVotes;
+ }
+
+ public long getTotalVotes() {
+ return totalVotes;
+ }
+
+ public float getReviewScore() {
+ return reviewScore;
+ }
+
+ public long getReviewCount() {
+ return reviewCount;
+ }
+}
diff --git a/src/main/java/gg/top/api/entity/ProjectType.java b/src/main/java/gg/top/api/entity/ProjectType.java
new file mode 100644
index 0000000..5572a7d
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/ProjectType.java
@@ -0,0 +1,11 @@
+package gg.top.api.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum ProjectType {
+ @SerializedName("bot")
+ BOT,
+
+ @SerializedName("server")
+ SERVER
+}
diff --git a/src/main/java/gg/top/api/entity/UserSource.java b/src/main/java/gg/top/api/entity/UserSource.java
new file mode 100644
index 0000000..dee03fb
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/UserSource.java
@@ -0,0 +1,11 @@
+package gg.top.api.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum UserSource {
+ @SerializedName("discord")
+ DISCORD,
+
+ @SerializedName("topgg")
+ TOPGG
+}
diff --git a/src/main/java/gg/top/api/entity/Vote.java b/src/main/java/gg/top/api/entity/Vote.java
new file mode 100644
index 0000000..37b9d17
--- /dev/null
+++ b/src/main/java/gg/top/api/entity/Vote.java
@@ -0,0 +1,20 @@
+package gg.top.api.entity;
+
+import com.google.gson.annotations.SerializedName;
+import java.time.OffsetDateTime;
+
+public class Vote extends PartialVote {
+ @SerializedName("user_id")
+ private String voterId;
+
+ @SerializedName("platform_id")
+ private String platformId;
+
+ public String getVoterId() {
+ return voterId;
+ }
+
+ public String getPlatformId() {
+ return platformId;
+ }
+}
diff --git a/src/main/java/gg/top/api/io/DefaultResponseTransformer.java b/src/main/java/gg/top/api/io/DefaultResponseTransformer.java
new file mode 100644
index 0000000..e2fb6a5
--- /dev/null
+++ b/src/main/java/gg/top/api/io/DefaultResponseTransformer.java
@@ -0,0 +1,20 @@
+package gg.top.api.io;
+
+import com.google.gson.Gson;
+import java.io.IOException;
+import okhttp3.Response;
+
+public class DefaultResponseTransformer implements ResponseTransformer {
+ private final Class aClass;
+ private final Gson gson;
+
+ public DefaultResponseTransformer(final Class aClass, final Gson gson) {
+ this.aClass = aClass;
+ this.gson = gson;
+ }
+
+ @Override
+ public E transform(final Response response) throws IOException {
+ return gson.fromJson(response.body().string(), aClass);
+ }
+}
diff --git a/src/main/java/gg/top/api/io/EmptyResponseTransformer.java b/src/main/java/gg/top/api/io/EmptyResponseTransformer.java
new file mode 100644
index 0000000..602e225
--- /dev/null
+++ b/src/main/java/gg/top/api/io/EmptyResponseTransformer.java
@@ -0,0 +1,10 @@
+package gg.top.api.io;
+
+import okhttp3.Response;
+
+public class EmptyResponseTransformer implements ResponseTransformer {
+ @Override
+ public Void transform(final Response response) {
+ return null;
+ }
+}
diff --git a/src/main/java/gg/top/api/io/PaginatedVotesConverter.java b/src/main/java/gg/top/api/io/PaginatedVotesConverter.java
new file mode 100644
index 0000000..0d5a55e
--- /dev/null
+++ b/src/main/java/gg/top/api/io/PaginatedVotesConverter.java
@@ -0,0 +1,35 @@
+package gg.top.api.io;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import gg.top.api.TopggAPI;
+import gg.top.api.entity.PaginatedVotes;
+import gg.top.api.entity.Vote;
+
+public class PaginatedVotesConverter implements JsonDeserializer {
+ private final TopggAPI client;
+
+ public PaginatedVotesConverter(final TopggAPI client) {
+ this.client = client;
+ }
+
+ @Override
+ public PaginatedVotes deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ final JsonObject object = json.getAsJsonObject();
+
+ final List votes =
+ StreamSupport.stream(object.getAsJsonArray("data").spliterator(), false)
+ .map(vote -> (Vote) context.deserialize(vote, Vote.class))
+ .collect(Collectors.toList());
+ final String cursor = object.get("cursor").getAsString();
+
+ return new PaginatedVotes(votes, cursor, client);
+ }
+}
diff --git a/src/main/java/gg/top/api/io/PostCommandsTransformer.java b/src/main/java/gg/top/api/io/PostCommandsTransformer.java
new file mode 100644
index 0000000..9e473ec
--- /dev/null
+++ b/src/main/java/gg/top/api/io/PostCommandsTransformer.java
@@ -0,0 +1,7 @@
+package gg.top.api.io;
+
+import java.util.concurrent.CompletionStage;
+
+public interface PostCommandsTransformer {
+ CompletionStage toJsonString();
+}
diff --git a/src/main/java/gg/top/api/io/RawPostCommandsTransformer.java b/src/main/java/gg/top/api/io/RawPostCommandsTransformer.java
new file mode 100644
index 0000000..429715f
--- /dev/null
+++ b/src/main/java/gg/top/api/io/RawPostCommandsTransformer.java
@@ -0,0 +1,18 @@
+package gg.top.api.io;
+
+import com.google.gson.JsonArray;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class RawPostCommandsTransformer implements PostCommandsTransformer {
+ private final JsonArray object;
+
+ public RawPostCommandsTransformer(final JsonArray object) {
+ this.object = object;
+ }
+
+ @Override
+ public CompletionStage toJsonString() {
+ return CompletableFuture.completedFuture(object.toString());
+ }
+}
diff --git a/src/main/java/gg/top/api/io/ResponseTransformer.java b/src/main/java/gg/top/api/io/ResponseTransformer.java
new file mode 100644
index 0000000..2fcf690
--- /dev/null
+++ b/src/main/java/gg/top/api/io/ResponseTransformer.java
@@ -0,0 +1,7 @@
+package gg.top.api.io;
+
+import okhttp3.Response;
+
+public interface ResponseTransformer {
+ E transform(final Response response) throws Exception;
+}
diff --git a/src/main/java/gg/top/api/io/UnsuccessfulHttpException.java b/src/main/java/gg/top/api/io/UnsuccessfulHttpException.java
new file mode 100644
index 0000000..fe13262
--- /dev/null
+++ b/src/main/java/gg/top/api/io/UnsuccessfulHttpException.java
@@ -0,0 +1,18 @@
+package gg.top.api.io;
+
+import okhttp3.Response;
+
+public class UnsuccessfulHttpException extends Exception {
+ private final Response response;
+
+ public UnsuccessfulHttpException(final Response response) {
+ super(
+ "The server responded with code: " + response.code() + ", message: " + response.message());
+
+ this.response = response;
+ }
+
+ public Response getResponse() {
+ return response;
+ }
+}
diff --git a/src/main/java/org/discordbots/api/client/DiscordBotListAPI.java b/src/main/java/org/discordbots/api/client/DiscordBotListAPI.java
deleted file mode 100644
index 1dbd21c..0000000
--- a/src/main/java/org/discordbots/api/client/DiscordBotListAPI.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.discordbots.api.client;
-
-import org.discordbots.api.client.entity.*;
-import org.discordbots.api.client.impl.DiscordBotListAPIImpl;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletionStage;
-
-public interface DiscordBotListAPI {
-
- CompletionStage setStats(int shardId, int shardTotal, int serverCount);
- CompletionStage setStats(List shardServerCounts);
- CompletionStage setStats(int serverCount);
-
- CompletionStage getStats(String botId);
-
- @Deprecated
- CompletionStage> getVoters(String botId);
- CompletionStage hasVoted(String userId);
-
- CompletionStage getBots(Map search, int limit, int offset);
- CompletionStage getBots(Map search, int limit, int offset, String sort);
- CompletionStage getBots(Map search, int limit, int offset, String sort, List fields);
- CompletionStage getBot(String botId);
-
- CompletionStage getUser(String userId);
-
- CompletionStage getVotingMultiplier();
-
- class Builder {
-
- // Required
- private String botId = null;
- private String token = null;
-
- public Builder token(String token) {
- this.token = token;
- return this;
- }
-
- public Builder botId(String botId) {
- this.botId = botId;
- return this;
- }
-
- public DiscordBotListAPI build() {
- if(token == null)
- throw new IllegalArgumentException("The provided token cannot be null!");
-
- if(botId == null)
- throw new IllegalArgumentException("The provided bot ID cannot be null!");
-
- return new DiscordBotListAPIImpl(token, botId);
- }
-
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/Bot.java b/src/main/java/org/discordbots/api/client/entity/Bot.java
deleted file mode 100644
index 0576dd9..0000000
--- a/src/main/java/org/discordbots/api/client/entity/Bot.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.discordbots.api.client.entity;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.time.OffsetDateTime;
-import java.util.List;
-
-public class Bot {
-
- private String id;
- @SerializedName("clientid")
- private String clientId;
- private String username;
- private String discriminator;
-
- private String avatar;
- @SerializedName("defAvatar")
- private String defaultAvatar;
-
- private String prefix;
- private String invite;
- private String website;
- private String vanity;
- private String support;
- private List tags;
-
- @SerializedName("longdesc")
- private String longDescription;
- @SerializedName("shortdesc")
- private String shortDescription;
- @SerializedName("betadesc")
- private String betaDescription;
-
- @SerializedName("certifiedBot")
- private boolean certified;
-
- @SerializedName("date") // rename so that the naming actually makes sense
- private OffsetDateTime approvalTime;
-
- @SerializedName("server_count")
- private long serverCount;
-
- private List guilds;
- private List shards;
- private int monthlyPoints;
- private int points;
-
- private boolean legacy;
-
-
-
- public String getId() {
- return id;
- }
-
- public String getClientId() {
- return clientId;
- }
-
- public String getUsername() {
- return username;
- }
-
- public String getDiscriminator() {
- return discriminator;
- }
-
- public String getAvatar() {
- return avatar;
- }
-
- public String getDefaultAvatar() {
- return defaultAvatar;
- }
-
- public String getPrefix() {
- return prefix;
- }
-
- public String getInvite() {
- return invite;
- }
-
- public String getWebsite() {
- return website;
- }
-
- public String getVanity() {
- return vanity;
- }
-
- public String getSupport() {
- return support;
- }
-
- public List getTags() {
- return tags;
- }
-
- public String getLongDescription() {
- return longDescription;
- }
-
- public String getShortDescription() {
- return shortDescription;
- }
-
- public String getBetaDescription() {
- return betaDescription;
- }
-
- public boolean isCertified() {
- return certified;
- }
-
- public OffsetDateTime getApprovalTime() {
- return approvalTime;
- }
-
- public long getServerCount() {
- return serverCount;
- }
-
- public List getGuilds() {
- return guilds;
- }
-
- public List getShards() {
- return shards;
- }
-
- public int getMonthlyPoints() {
- return monthlyPoints;
- }
-
- public int getPoints() {
- return points;
- }
-
- public boolean isLegacy() {
- return legacy;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/BotResult.java b/src/main/java/org/discordbots/api/client/entity/BotResult.java
deleted file mode 100644
index 7581b60..0000000
--- a/src/main/java/org/discordbots/api/client/entity/BotResult.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.discordbots.api.client.entity;
-
-// This class is needed because of the way Java generics work. I can't reference Result as a class
-// so this is just a simple workaround for that
-public class BotResult extends Result {}
diff --git a/src/main/java/org/discordbots/api/client/entity/BotStats.java b/src/main/java/org/discordbots/api/client/entity/BotStats.java
deleted file mode 100644
index 209edfd..0000000
--- a/src/main/java/org/discordbots/api/client/entity/BotStats.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.discordbots.api.client.entity;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.Collections;
-import java.util.List;
-
-public class BotStats {
-
- @SerializedName("server_count")
- private int serverCount;
- private List shards;
-
- public int getServerCount() { return serverCount; }
- public List getShards() { return Collections.unmodifiableList(shards); }
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/Result.java b/src/main/java/org/discordbots/api/client/entity/Result.java
deleted file mode 100644
index 1dd0130..0000000
--- a/src/main/java/org/discordbots/api/client/entity/Result.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.discordbots.api.client.entity;
-
-import java.util.List;
-
-public class Result {
-
- private List results;
- private int limit, offset, count, total;
-
-
-
- public List getResults() {
- return results;
- }
-
- public int getLimit() {
- return limit;
- }
-
- public int getOffset() {
- return offset;
- }
-
- public int getCount() {
- return count;
- }
-
- public int getTotal() {
- return total;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/SimpleUser.java b/src/main/java/org/discordbots/api/client/entity/SimpleUser.java
deleted file mode 100644
index 562f0ed..0000000
--- a/src/main/java/org/discordbots/api/client/entity/SimpleUser.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.discordbots.api.client.entity;
-
-public class SimpleUser {
-
- private String id;
- private String username;
- private String discriminator;
-
- private String avatar;
-
-
-
- public String getId() {
- return id;
- }
-
- public String getUsername() {
- return username;
- }
-
- public String getDiscriminator() {
- return discriminator;
- }
-
- public String getAvatar() {
- return avatar;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/Social.java b/src/main/java/org/discordbots/api/client/entity/Social.java
deleted file mode 100644
index d7a64e0..0000000
--- a/src/main/java/org/discordbots/api/client/entity/Social.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.discordbots.api.client.entity;
-
-public class Social {
-
- String youtube, reddit, twitter, instagram, github;
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/User.java b/src/main/java/org/discordbots/api/client/entity/User.java
deleted file mode 100644
index bfed556..0000000
--- a/src/main/java/org/discordbots/api/client/entity/User.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.discordbots.api.client.entity;
-
-import com.google.gson.annotations.SerializedName;
-
-public class User extends SimpleUser {
-
- @SerializedName("defAvatar")
- private String defaultAvatar;
-
- private boolean admin, mod, webMod;
- private boolean artist, certifiedDev, supporter;
-
- private Social social;
-
-
-
- public String getDefaultAvatar() {
- return defaultAvatar;
- }
-
- public boolean isAdmin() {
- return admin;
- }
-
- public boolean isMod() {
- return mod;
- }
-
- public boolean isWebMod() {
- return webMod;
- }
-
- public boolean isArtist() {
- return artist;
- }
-
- public boolean isCertifiedDev() {
- return certifiedDev;
- }
-
- public boolean isSupporter() {
- return supporter;
- }
-
- public Social getSocial() {
- return social;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/Vote.java b/src/main/java/org/discordbots/api/client/entity/Vote.java
deleted file mode 100644
index 047f6de..0000000
--- a/src/main/java/org/discordbots/api/client/entity/Vote.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.discordbots.api.client.entity;
-
-import com.google.gson.annotations.SerializedName;
-
-public class Vote {
-
- @SerializedName("bot")
- private String botId;
- @SerializedName("user")
- private String userId;
-
- private String type;
-
- private String query;
-
- @SerializedName("isWeekend")
- private boolean weekend;
-
-
-
- public String getBotId() {
- return botId;
- }
-
- public String getUserId() {
- return userId;
- }
-
- public String getType() {
- return type;
- }
-
- public String getQuery() {
- return query;
- }
-
- public boolean isWeekend() {
- return weekend;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/entity/VotingMultiplier.java b/src/main/java/org/discordbots/api/client/entity/VotingMultiplier.java
deleted file mode 100644
index 6f927fe..0000000
--- a/src/main/java/org/discordbots/api/client/entity/VotingMultiplier.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.discordbots.api.client.entity;
-
-import com.google.gson.annotations.SerializedName;
-
-public class VotingMultiplier {
-
- @SerializedName("is_weekend")
- private boolean weekend;
-
-
-
- public boolean isWeekend() {
- return weekend;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/impl/DiscordBotListAPIImpl.java b/src/main/java/org/discordbots/api/client/impl/DiscordBotListAPIImpl.java
deleted file mode 100644
index 546df9a..0000000
--- a/src/main/java/org/discordbots/api/client/impl/DiscordBotListAPIImpl.java
+++ /dev/null
@@ -1,264 +0,0 @@
-package org.discordbots.api.client.impl;
-
-import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import okhttp3.*;
-import org.discordbots.api.client.DiscordBotListAPI;
-import org.discordbots.api.client.entity.*;
-import org.discordbots.api.client.io.DefaultResponseTransformer;
-import org.discordbots.api.client.io.ResponseTransformer;
-import org.discordbots.api.client.io.UnsuccessfulHttpException;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.time.OffsetDateTime;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.stream.Collectors;
-
-public class DiscordBotListAPIImpl implements DiscordBotListAPI {
-
- private static final HttpUrl baseUrl = new HttpUrl.Builder()
- .scheme("https")
- .host("top.gg")
- .addPathSegment("api")
- .build();
-
- private final OkHttpClient httpClient;
- private final Gson gson;
-
- private final String token, botId;
-
- public DiscordBotListAPIImpl(String token, String botId) {
- this.token = token;
- this.botId = botId;
-
- this.gson = new GsonBuilder()
- .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeConverter())
- .create();
-
- this.httpClient = new OkHttpClient.Builder()
- .addInterceptor((chain) -> {
- Request req = chain.request().newBuilder()
- .addHeader("Authorization", this.token)
- .build();
- return chain.proceed(req);
- })
- .build();
- }
-
- public CompletionStage setStats(int shardId, int shardTotal, int serverCount) {
- JSONObject json = new JSONObject()
- .put("shard_id", shardId)
- .put("shard_count", shardTotal)
- .put("server_count", serverCount);
-
- return setStats(json);
- }
-
- public CompletionStage setStats(List shardServerCounts) {
- JSONObject json = new JSONObject()
- .put("shards", shardServerCounts);
-
- return setStats(json);
- }
-
- public CompletionStage setStats(int serverCount) {
- JSONObject json = new JSONObject()
- .put("server_count", serverCount);
-
- return setStats(json);
- }
-
- private CompletionStage setStats(JSONObject jsonBody) {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("bots")
- .addPathSegment(botId)
- .addPathSegment("stats")
- .build();
-
- return post(url, jsonBody, Void.class);
- }
-
- public CompletionStage getStats(String botId) {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("bots")
- .addPathSegment(botId)
- .addPathSegment("stats")
- .build();
-
- return get(url, BotStats.class);
- }
-
- public CompletionStage> getVoters(String botId) {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("bots")
- .addPathSegment(botId)
- .addPathSegment("votes")
- .build();
-
- return get(url, resp -> {
- // This is kinda awkward but this is done so that it can return it was a list instead of
- // an array
- ResponseTransformer arrayTransformer = new DefaultResponseTransformer<>(SimpleUser[].class, gson);
- return Arrays.asList(arrayTransformer.transform(resp));
- });
- }
-
- public CompletionStage getBot(String botId) {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("bots")
- .addPathSegment(botId)
- .build();
-
- return get(url, Bot.class);
- }
-
- public CompletionStage getBots(Map search, int limit, int offset) {
- return getBots(search, limit, offset, null);
- }
-
- public CompletionStage getBots(Map search, int limit, int offset, String sort) {
- return getBots(search, limit, offset, sort, null);
- }
-
- public CompletionStage getBots(Map search, int limit, int offset, String sort, List fields) {
- // DBL search uses this format: field1: value1 field2: value2
- String searchString = search.entrySet().stream()
- .map(entry -> entry.getKey() + ": " + entry.getValue())
- .collect(Collectors.joining(" "));
-
- HttpUrl.Builder urlBuilder = baseUrl.newBuilder()
- .addPathSegment("bots")
- .addQueryParameter("search", searchString)
- .addQueryParameter("limit", String.valueOf(limit))
- .addQueryParameter("offset", String.valueOf(offset));
-
- if(sort != null) {
- urlBuilder.addQueryParameter("sort", sort);
- }
-
- if(fields != null) {
- String fieldsString = fields.stream()
- .collect(Collectors.joining(" "));
-
- urlBuilder.addQueryParameter("fields", fieldsString);
- }
-
- return get(urlBuilder.build(), BotResult.class);
- }
-
- public CompletionStage getUser(String userId) {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("users")
- .addPathSegment(userId)
- .build();
-
- return get(url, User.class);
- }
-
- public CompletionStage hasVoted(String userId) {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("bots")
- .addPathSegment(botId)
- .addPathSegment("check")
- .addQueryParameter("userId", userId)
- .build();
-
- return get(url, (resp) -> {
- JSONObject json = new JSONObject(resp.body().string());
- return json.getInt("voted") == 1;
- });
- }
-
- public CompletionStage getVotingMultiplier() {
- HttpUrl url = baseUrl.newBuilder()
- .addPathSegment("weekend")
- .build();
-
- return get(url, VotingMultiplier.class);
- }
-
- private CompletionStage get(HttpUrl url, Class aClass) {
- return get(url, new DefaultResponseTransformer<>(aClass, gson));
- }
-
- private CompletionStage get(HttpUrl url, ResponseTransformer responseTransformer) {
- Request req = new Request.Builder()
- .get()
- .url(url)
- .build();
-
- return execute(req, responseTransformer);
- }
-
- // The class provided in this is kinda unneeded because the only thing ever given to it
- // is Void, but I wanted to make it expandable (maybe some post methods will return objects
- // in the future)
- private CompletionStage post(HttpUrl url, JSONObject jsonBody, Class aClass) {
- return post(url, jsonBody, new DefaultResponseTransformer<>(aClass, gson));
- }
-
- private CompletionStage post(HttpUrl url, JSONObject jsonBody, ResponseTransformer responseTransformer) {
- MediaType mediaType = MediaType.parse("application/json");
- RequestBody body = RequestBody.create(mediaType, jsonBody.toString());
-
- Request req = new Request.Builder()
- .post(body)
- .url(url)
- .build();
-
- return execute(req, responseTransformer);
- }
-
- private CompletionStage execute(Request request, ResponseTransformer responseTransformer) {
- Call call = httpClient.newCall(request);
-
- final CompletableFuture future = new CompletableFuture<>();
-
- call.enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {
- future.completeExceptionally(e);
- }
-
- @Override
- public void onResponse(Call call, Response response) {
- try {
-
- if (response.isSuccessful()) {
- E transformed = responseTransformer.transform(response);
- future.complete(transformed);
- } else {
- String message = response.message();
-
- // DBL sends error messages as part of the body and leaves the
- // actual message blank so this will just pull that instead because
- // it's 1000x more useful than the actual message
- if (message == null || message.isEmpty()) {
- try {
- JSONObject body = new JSONObject(response.body().string());
- message = body.getString("error");
- } catch (Exception ignored) {}
- }
-
- Exception e = new UnsuccessfulHttpException(response.code(), message);
- future.completeExceptionally(e);
- }
-
- } catch (Exception e) {
- future.completeExceptionally(e);
- } finally {
- response.body().close();
- }
- }
- });
-
- return future;
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/io/DefaultResponseTransformer.java b/src/main/java/org/discordbots/api/client/io/DefaultResponseTransformer.java
deleted file mode 100644
index 8639e10..0000000
--- a/src/main/java/org/discordbots/api/client/io/DefaultResponseTransformer.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.discordbots.api.client.io;
-
-import com.google.gson.Gson;
-import okhttp3.Response;
-
-import java.io.IOException;
-
-public class DefaultResponseTransformer implements ResponseTransformer {
-
- private final Class aClass;
- private final Gson gson;
-
- public DefaultResponseTransformer(Class aClass, Gson gson) {
- this.aClass = aClass;
- this.gson = gson;
- }
-
- @Override
- public E transform(Response response) throws IOException {
- String body = response.body().string();
- return gson.fromJson(body, aClass);
- }
-
-}
diff --git a/src/main/java/org/discordbots/api/client/io/ResponseTransformer.java b/src/main/java/org/discordbots/api/client/io/ResponseTransformer.java
deleted file mode 100644
index 730b783..0000000
--- a/src/main/java/org/discordbots/api/client/io/ResponseTransformer.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.discordbots.api.client.io;
-
-import okhttp3.Response;
-
-public interface ResponseTransformer {
-
- E transform(Response response) throws Exception;
-
-}
diff --git a/src/main/java/org/discordbots/api/client/io/UnsuccessfulHttpException.java b/src/main/java/org/discordbots/api/client/io/UnsuccessfulHttpException.java
deleted file mode 100644
index ae8a294..0000000
--- a/src/main/java/org/discordbots/api/client/io/UnsuccessfulHttpException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.discordbots.api.client.io;
-
-public class UnsuccessfulHttpException extends Exception {
-
- public UnsuccessfulHttpException(int code, String message) {
- super("The server responded with code: " + code + ", message: " + message);
- }
-
-}