Skip to content

cottoncammy/disquark

Repository files navigation

DisQuark

Version License Main branch build

DisQuark is a reactive library that enables developers to write fast and performant JVM applications that leverage Discord's REST API. Includes support for REST interactions, OAuth2, and responding to interactions and executing webhooks without a bot token.

Powered by Vert.x, SmallRye Mutiny, and Immutables.

Installation

Get DisQuark by importing the io.disquark:disquark-rest artifact (or the io.disquark:disquark-rest-kotlin artifact if you prefer Kotlin) dependency using your preferred build tool.

Getting Started

The entrypoint to your DisQuark application depends on your use case.

  • DiscordBotClient - a client tied to a bot account capable of doing anything a bot token allows you to do; can also receive and respond to interactions and execute webhooks using interaction and webhook tokens

  • DiscordOAuth2Client - a client tied to a user account's OAuth2 token capable of doing anything a bearer token allows you to do; can also receive and respond to interactions and execute webhooks using interaction and webhook tokens

  • DiscordInteractionsClient - a client capable of receiving and responding to incoming interactions using interaction tokens, not tied to a bot or user account

  • DiscordWebhookClient - a client capable of executing webhooks using webhook tokens, not tied to a bot or user account

Each client is created using create or builder static methods.

Kotlin API

DisQuark provides a (WIP) idiomatic Kotlin API for users who prefer Kotlin. The API provides Kotlin DSLs as alternatives to Java builders and offers extension methods to translate SmallRye Mutiny constructs to Kotlin Coroutine Flows and suspended values. The Kotlin API is strongly recommended; the Kotlin DSL syntax is much easier to use to build requests than the Java builder syntax.

Get the Kotlin API by importing the io.disquark:disquark-rest-kotlin dependency using your preferred build tool.

API Status

DisQuark's API should not be considered stable for now.

DisQuark currently does not support Discord's Gateway or Voice APIs. Interest in the project will determine whether time is invested to develop corresponding modules. For now, the project's development efforts are focused on improving and maintaining the REST module.

Examples

Creating a message

Using io.disquark:disquark-rest:

class MyApp {
    public static void main(String[] args) {
        var botClient = DiscordBotClient.create(Vertx.vertx(), "TOKEN");
        
        botClient.createMessage(new Snowflake(0L))
                .withContent("Hello World!")
                .subscribe().with(x -> {});
    }
}

Using io.disquark:disquark-rest-kotlin:

suspend fun main() {
    val botClient = DiscordBotClient.create(Vertx.vertx(), "TOKEN") 
    
    botClient.createMessage(channelId = Snowflake(0L)) {
        content = "Hello World!"
    }.awaitSuspending()
}

Creating application commands

Using io.disquark:disquark-rest:

class MyApp {
    public static void main(String[] args) {
        var botClient = DiscordBotClient.create(Vertx.vertx(), "TOKEN");
       
        botClient.bulkOverwriteGlobalApplicationCommands(new Snowflake(0L))
                .withOverwrites(new GlobalApplicationCommandOverwrite("foo")
                    .withType(ApplicationCommand.Type.CHAT_INPUT)
                    .withDescription("Foo commands")
                    .withOptions(List.of(
                        new ApplicationCommand.Option(ApplicationCommand.Option.Type.SUB_COMMAND, "bar", "Foo bar command")
                            .withOptions(List.of(
                                new ApplicationCommand.Option(ApplicationCommand.Option.Type.STRING, "baz", "Baz option")
                                    .withRequired(true)))))
                 ).subscribe().with(x -> {});
    }
}

Using io.disquark:disquark-rest-kotlin:

suspend fun main() {
    val botClient = DiscordBotClient.create(Vertx.vertx(), "TOKEN") 
    
    botClient.bulkOverwriteGlobalApplicationCommands(applicationId = Snowflake(0L)) {
        chatInputCommand(name = "foo") {
            description = "Foo commands"
            
            subcommand(name = "bar", description = "Foo bar command") {
                stringOption(name = "baz", description = "Baz option") {
                    required = true
                }
            }
        }
    }.onItem().ignoreAsUni().awaitSuspending()
}

Listening and responding to incoming interactions via HTTP

By default, the web server launched by DisQuark will listen for incoming interactions on localhost:80.

Verification of incoming interactions requires the org.bouncycastle:bcprov-jdk18on dependency to be installed and for the BouncyCastleProvider to be configured as Java's java.security.Provider in your application. Incoming interactions won't have their signatures verified if you don't setup a BouncyCastleProvider or change the default InteractionsValidator.

class MyApp {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    public static void main(String[] args) {
        var botClient = DiscordBotClient.create(Vertx.vertx(), "TOKEN");
        
        botClient.on(applicationCommand().name("foo").with(option().type(ApplicationCommand.Option.Type.SUB_COMMAND).name("bar").with(option().name("baz"))))
            .onItem().transformToUniAndMerge(interaction -> interaction.respond().withContent("Hello World!"))
            .onItem().ignoreAsUni().await().indefinitely();
    }
}

It's strongly recommended to use DisQuark with our Quarkiverse extension which minimizes the boilerplate needed to listen and respond to interactions.

Client Configuration

DisQuark clients expose several configuration options via Builders accessible from corresponding static builder methods.

Token source

You can configure your client's AccessTokenSource to receive your access token asynchronously from an alternative source.

Rate limit configuration

You can change the RateLimitStrategy and the GlobalRateLimiter implementation used by DisQuark to avoid being rate limited by Discord's API. There are pre-configured strategies to disable rate limiting logic or to only apply bucket or global rate limiting logic. This can be useful when you are creating a distributed application and want to synchronize rate limiting logic across your DisQuark application instances.

Requester factory

You can change the RequesterFactory used to construct a Requester instance that performs the actual request logic to Discord. This is useful if you need to configure the underlying HttpClient used by the default Requester or if you want to provide a different implementation altogether.

Logging

Logging in DisQuark is provided by the SLF4J API. Install an implementation using your preferred build tool and configure it appropriately to receive trace- and debug-level logs from your application.

Interactions client configuration

The following configuration options are available for applications leveraging HTTP interactions:

  • router - the Vert.x Router instance used to setup the interactions handler
  • verifyKey - your Discord application's public key used to verify incoming interaction signatures, lazily fetched if not provided
  • handleCors - whether to install a CORS handler to the Router to accept incoming POST requests from discord.com, true by default
  • interactionsUrl - the relative URL to receive interactions from, / by default
  • startHttpServer - whether to construct and start an HttpServer when listening for incoming interactions, true by default
  • httpServerSupplier - the factory used to construct the HttpServer if startHttpServer is enabled
  • validatorFactory - the factory used to construct an InteractionsValidator implementation from the verifyKey

Using Snapshots

DisQuark snapshots are automatically published with the version 999-SNAPSHOT when commits are pushed to the main branch.

To use the latest snapshot in Maven, add the following repository to your pom.xml:

<repositories>
    <repository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>