JDA (Java Discord API)

repository·master·Indexed 26 days ago

https://github.com/discord-jda/jda

An open-source library for implementing Discord bots using the real-time gateway and REST API. JDA provides an event-driven system, scalable REST actions, and customizable caching. It includes features for implementing message logging, slash commands, and low-latency audio via NativeAudioSendFactory, as well as jda-ktx for idiomatic Kotlin support with coroutines.

Tokens
14.5K
Snippets
20
Records
79
Agent score
88%

What's inside JDA

  1. Understand Entity Mixins in JDA

    master

    Entity Mixins are a design pattern used within JDA to provide core functionalities through composition rather than pure inheritance. This system allows the library to:

    • Reuse code across different entity types.
    • Expose internal state setters and getters to the library's internal implementation without exposing them to the end-user API.

    Mixins typically consist of a public interface, an internal mixin interface that extends the public one, and a concrete implementation class.

  2. Install JDA via Gradle

    master

    Add the JDA dependency to your build.gradle file. Ensure you use the latest version from Maven Central. You can optionally exclude opus-java and tink to reduce the JAR size if you do not require audio encoding or encryption.

    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation("net.dv8tion:JDA:$version") { // replace $version with the latest version
          // Optionally disable audio natives to reduce jar size by excluding `opus-java` and `tink`
          // Gradle DSL:
          // exclude module: 'opus-java' // required for encoding audio into opus, not needed if audio is already provided in opus encoding
          // exclude module: 'tink' // required for encrypting and decrypting audio
          // Kotlin DSL:
          // exclude(module="opus-java") // required for encoding audio into opus, not needed if audio is already provided in opus encoding
          // exclude(module="tink") // required for encrypting and decrypting audio
        }
    }
  3. Configure NativeAudioSendFactory for low-latency audio

    master

    To avoid GC pauses that can cause stuttering during continuous audio playback, use the udpqueue extension by configuring a NativeAudioSendFactory in your JDABuilder.

    Note: This implementation creates an extra UDP-Client, which means audio receiving will no longer function properly because Discord identifies the sending UDP-Client as the receiver.

    JDABuilder builder = JDABuilder.createDefault(BOT_TOKEN)
        .setAudioSendFactory(new NativeAudioSendFactory());
  4. Initialize a bot with JDABuilder presets

    master

    JDA provides several builder presets to manage cache usage and gateway intents easily:

    • createDefault: Enables cache for users active in voice channels and all cache flags.
    • createLight: Disables all user cache and cache flags.
    • create: Enables member chunking, caches all users, and enables all cache flags.

    Note: For audio functionality, you must also add a dependency that implements the DAVE Protocol.

  5. Install JDA via Maven

    master

    Add the JDA dependency to your pom.xml. You can optionally exclude opus-java and tink to reduce the JAR size if you do not require audio encoding or encryption.

    <dependency>
        <groupId>net.dv8tion</groupId>
        <artifactId>JDA</artifactId>
        <version>$version</version> <!-- replace $version with the latest version -->
        <!-- Optionally disable audio natives to reduce jar size by excluding `opus-java` and `tink` -->
        <exclusions>
            <!-- required for encoding audio into opus, not needed if audio is already provided in opus encoding
            <exclusion>
                <groupId>club.minnced</groupId>
                <artifactId>opus-java</artifactId>
            </exclusion> -->
            <!-- required for encrypting and decrypting audio
            <exclusion>
                <groupId>com.google.crypto.tink</groupId>
                <artifactId>tink</artifactId>
            </exclusion> -->
        </exclusions>
    </dependency>
  6. Use jda-ktx for idiomatic Kotlin

    master

    The jda-ktx extension library provides Kotlin-idiomatic wrappers for RestAction and events, including support for coroutines (suspending functions).

    fun main() {
        val jda = light(BOT_TOKEN)
        
        jda.onCommand("ping") { event ->
            val time = measureTime {
                event.reply("Pong!").await() // suspending
            }.inWholeMilliseconds
    
            event.hook.editOriginal("Pong: $time ms").queue()
        }
    }
  7. Implement an Entity Mixin

    master

    To implement the Mixin pattern, follow this structure:

    1. Public Interface: Define the API exposed to the user (e.g., getName(), updateName()).
    2. Internal Mixin: An interface that extends the public interface. It uses generics (e.g., SomeEntityMixin<T extends SomeEntityMixin<T>>) to allow fluent state accessors. It provides default implementations for the public interface methods by utilizing State Accessors and Mixin Hooks.
    3. Concrete Implementation: The actual class that implements the Mixin. It holds the private state and provides the actual logic for the State Accessors and Mixin Hooks.

    Example Implementation

    // Publicly exposed entity api interface
    public interface SomeEntity {
        String getName();
        
        default String getNameTwice() {
          return getName() + "-" + getName();
        }
    
        RestAction<Void> updateName(String name);
    }
    
    // Internal mixin for that entity for code reuse and state exposing
    public interface SomeEntityMixin<T extends SomeEntityMixin<T>> extends SomeEntity {
        //---- Default implementations of interface ----
        @Override
        default RestAction<Void> updateName(String name) {
            checkCanModifyEntity();
            
            Route.CompiledRoute route = Route.custom(Method.POST, "/someEntity/name/");
            return new RestActionImpl<>(route);
        }
        
        //---- State Accessors ----
        T setName(String name);
        
        //---- Mixin Hooks -----
        void checkCanModifyEntity();
    }
    
    // Internal concrete implementation of the entity
    public class SomeEntityImpl implements SomeEntityMixin<SomeEntityImpl> {
        private String name;
        
        public SomeEntityImpl() {}
        
        @Override
        public String getName() {
            return name;
        }
        
        @Override
        public SomeEntityImpl setName(String name) {
            this.name = name;
        }
        
        @Override
        public void checkCanModifyEntity() {
            // Do some check here, throw if check is bad!
        }
    }
    //Publicly exposed entity api interface
    public interface SomeEntity {
        String getName();
        
        default String getNameTwice() {
          return getName() + "-" + getName();
        }
    
        RestAction<Void> updateName(String name);
    }
    
    //Internal mixin for that entity for code reuse and state exposing
    public interface SomeEntityMixin<T extends SomeEntityMixin<T>> extends SomeEntity {
        //---- Default implementations of interface ----
        @Override
        default RestAction<Void> updateName(String name) {
            checkCanModifyEntity();
            
            Route.CompiledRoute route = Route.custom(Method.POST, "/someEntity/name/");
            return new RestActionImpl<>(route);
        }
        
        //---- State Accessors ----
        T setName(String name);
        
        //---- Mixin Hooks -----
        void checkCanModifyEntity();
    }
    
    //Internal concrete implementation of the entity
    public class SomeEntityImpl implements SomeEntityMixin<SomeEntityImpl> {
        private String name;
        
        public SomeEntityImpl() {}
        
        @Override
        public String getName() {
            return name;
        }
        
        @Override
        public SomeEntityImpl setName(String name) {
            this.name = name;
        }
        
        @Override
        public void checkCanModifyEntity() {
            //Do some check here, throw if check is bad!
        }
    }
  8. Chain complex RestAction sequences

    master

    You can use flatMap and delay to create complex, multi-step asynchronous workflows. For example, you can send a message, wait for a duration, edit it, and then delete it.

    public RestAction<Void> selfDestruct(MessageChannel channel, String content) {
        return channel.sendMessage("The following message will destroy itself in 1 minute!")
            .addComponents(ActionRow.of(Button.danger("delete", "Delete now")))
            .delay(10, SECONDS, scheduler)
            .flatMap((it) -> it.editMessage(content))
            .delay(1, MINUTES, scheduler)
            .flatMap(Message::delete);
    }
    
    // Usage:
    selfDestruct(channel, "Hello friend, this is my secret message").queue();
  9. Implement a Slash Command bot

    master

    Slash commands use interactions and do not require specific gateway intents. You can register commands globally using jda.updateCommands(). To handle commands, extend ListenerAdapter and override onSlashCommandInteraction. Use .queue() to execute RestActions.

    public static void main(String[] args) {
      JDA jda = JDABuilder.createLight(token, Collections.emptyList())
          .addEventListeners(new SlashCommandListener())
          .build();
    
      // Register your commands to make them visible globally on Discord:
    
      CommandListUpdateAction commands = jda.updateCommands();
    
      // Add all your commands on this action instance
      commands.addCommands(
        Commands.slash("say", "Makes the bot say what you tell it to")
          .addOption(STRING, "content", "What the bot should say", true), // Accepting a user input
        Commands.slash("leave", "Makes the bot leave the server")
          .setContexts(InteractionContextType.GUILD) // this doesn't make sense in DMs
          .setDefaultPermissions(DefaultMemberPermissions.DISABLED) // only admins should be able to use this command.
      );
    
      // Then finally send your commands to discord using the API
      commands.queue();
    }
    
    public class SlashCommandListener extends ListenerAdapter {
      @Override
      public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
        switch (event.getName()) {
          case "say" -> {
            String content = event.getOption("content", OptionMapping::getAsString);
            event.reply(content).queue();
          }
          case "leave" -> {
            event.reply("I'm leaving the server now!")
              .setEphemeral(true) // this message is only visible to the command user
              .flatMap(m -> event.getGuild().leave()) // append a follow-up action using flatMap
              .queue(); // enqueue both actions to run in sequence (send message -> leave guild)
          }
        }
      }
    }
  10. Implement a Message Logging bot

    master

    To log messages, use JDABuilder.createLight with the necessary GatewayIntents. Note that GatewayIntent.MESSAGE_CONTENT is a privileged intent and must be enabled in the Discord Developer Portal. You must extend ListenerAdapter and override onMessageReceived to handle events.

    public static void main(String[] args) {
      JDABuilder.createLight(token, EnumSet.of(GatewayIntent.GUILD_MESSAGES, GatewayIntent.MESSAGE_CONTENT))
          .addEventListeners(new MessageReceiveListener())
          .build();
    }
    
    public class MessageReceiveListener extends ListenerAdapter {
      @Override
      public void onMessageReceived(MessageReceivedEvent event) {
        System.out.printf("[%s] %#s: %s\n",
          event.getChannel(),
          event.getAuthor(),
          event.getMessage().getContentDisplay());
      }
    }
  11. Use State Accessors and Mixin Hooks in Mixins

    master

    When defining an internal Mixin, use the following components to provide default logic for the public interface:

    • State Accessors: Methods defined in the Mixin that provide access to the underlying state in the concrete implementation. They are used by default methods to perform logic or to expose internal setters/getters (like setName or getConnectedMembersMap) to the library without exposing them to the end-user.
    • Mixin Hooks: Methods that must be implemented by the concrete class to provide necessary information or validation for the Mixin's default logic, but which do not necessarily map directly to a single piece of state (e.g., checkCanModifyEntity()).
    • Default Implementations of Interface: The default methods in the Mixin that use State Accessors and Mixin Hooks to implement the public API. Note that basic getters that map directly to a field (e.g., int getBitrate()) cannot be implemented in the Mixin and must be implemented in the concrete class.
  12. Use RestAction for API requests

    master

    The RestAction interface is a lazy request builder used for all API endpoints. You chain builder methods to configure the request, but the request is not sent until you call a terminal method like .queue().

    Common Operators

    To avoid callback hell, RestAction supports several functional operators:

    • map: Convert the result of the RestAction to a different value.
    • flatMap: Chain another RestAction based on the result.
    • delay: Delay the execution of the next step.

    Combinators

    • and: Runs another RestAction in parallel, requiring it to complete successfully.
    • allOf: Accumulates a collection of many actions into one.
    • zip: Similar to and, but combines results into a list.

    Configurators

    • timeout(long, TimeUnit) / deadline(long): Sets how long the action can stay in the queue before being cancelled.
    • setCheck(BooleanSupplier): Runs a check immediately before the request is sent.
    • reason(String): Sets the audit log reason for the action.
    channel.sendMessage("Hello Friend!")
      .addFiles(FileUpload.fromData(greetImage))
      .queue(); // Sends the request asynchronously