Skip to content

Starting from scratch

Start by creating a project either using Maven or Gradle, it must run on Java 17+, I recommend using Java 21.

Creating a new Maven project

When creating a Maven project in IntelliJ, do not choose Maven Archetype in Generators, you must use New Project.

Adding the dependencies

The only strictly necessary dependencies are the framework and JDA:

Omit the v prefix from the version, e.g. 5.0.0-beta.18.

<dependencies>
    ...

    <dependency>
        <groupId>net.dv8tion</groupId>
        <artifactId>JDA</artifactId>
        <version>JDA_VERSION</version>
    </dependency>
    <dependency>
        <groupId>io.github.freya022</groupId>
        <artifactId>BotCommands</artifactId>
        <version>BC_VERSION</version>
    </dependency>
</dependencies>
repositories {
    ...
    mavenCentral()
}

dependencies {
    ...

    implementation("net.dv8tion:JDA:JDA_VERSION")
    implementation("io.github.freya022:BotCommands:BC_VERSION")
}

Adding logging

Any SLF4J compatible logger should work; I recommend logback, which you can learn more here.

Creating a config service

Create a small Config service, it can be a simple object with the properties you need, this will be useful when running your bot.

Example
class Config(val token: String, val ownerIds: List<Long>) {
    companion object {
        // Makes a service factory out of this property getter
        @get:BService
        val instance by lazy {
            // Load your config
        }
    }
}
public class Config {
    private static Config INSTANCE = null;

    private String token;
    private List<Long> ownerIds;

    public String getToken() { return token; }
    public List<Long> getOwnerIds() { return ownerIds; }

    @BService // Makes this method a service factory that outputs Config objects
    public static Config getInstance() {
        if (INSTANCE == null) {
            INSTANCE = // Load your config
        }

        return INSTANCE;
    }
}

Info

You can refer to the Dependency Injection page for more details

Creating the main class

As we've used a singleton pattern for your Config class, we can get the same instance anywhere, and still be able to get it as a service.

All you need to do to start the framework is BotCommands#create:

Main.kt - Main function
val config = Config.instance

BotCommands.create {
    // Optionally set the owner IDs if they differ from the owners in the Discord dashboard
    // addPredefinedOwners(config.ownerIds)

    // Add the base package of the application
    // All services and commands inside will be loaded
    addSearchPath("io.github.name.bot")

    textCommands {
        usePingAsPrefix = true // The bot will respond to his mention/ping
    }
}    
Main.java - Main method
final var config = Config.getInstance();

BotCommands.create(builder -> {
    // Optionally set the owner IDs if they differ from the owners in the Discord dashboard
    // builder.addPredefinedOwners(config.getOwnerIds());

    // Add the base package of the application
    // All services and commands inside will be loaded
    builder.addSearchPath("io.github.name.bot");

    builder.textCommands(textCommands -> {
        textCommands.usePingAsPrefix(true);
    });
});

The framework also supports Spring IoC, add the library, add the package of your application with the scanBasePackages value of your @SpringBootApplication, and voilà.

Note

You can always disable it by adding BotCommandsAutoConfiguration to the exclude value of your @SpringBootApplication.

Configuration of the framework is then done either by using application properties (with the prefix being either botcommands or jda), or by implementing configurers, see the BConfigurer inheritors.

Tip

You can also use the Spring developer tools to speed up your development cycle, a few options can be configured with jda.devtools.* properties.

Kotlin - Using a custom CoroutineEventManager

I recommend creating a custom CoroutineEventManager, that way you can configure the amount of threads or their names, which may be convenient in logs.

You can do so by implementing a ICoroutineEventManagerSupplier service, with the help of namedDefaultScope:

@BService
class CoroutineEventManagerSupplier : ICoroutineEventManagerSupplier {
    override fun get(): CoroutineEventManager {
        val scope = namedDefaultScope("WikiBot Coroutine", corePoolSize = 4)
        return CoroutineEventManager(scope)
    }
}

Warning

JDA must be created after the framework is built, as the framework listens to JDA events and must not skip any of these, you will need to make a service extending JDAService.

Creating a JDAService

Now you've been able to start the framework, all your services (such as Config for the moment) should be loaded, but you must now have a way to start JDA, implementing JDAService will let you start the bot in a convenient place.

Implementing JDAService guarantees that your bot will connect at the right time, and provides a way for the framework to check missing intents and missing cache flags before your bot even starts.

Spring properties

If you use Spring, you will need to put gateway intents and cache flags in your environment. You will then be able to set your gateway intents and cache flags using the values provided by JDAConfiguration.

@BService
class Bot(private val config: Config) : JDAService() {
    // If you use Spring, you can set the properties below to the values provided by JDAConfiguration
    override val intents: Set<GatewayIntent> = defaultIntents(/* _Additional_ intents */ GatewayIntent.GUILD_VOICE_STATES)

    override val cacheFlags: Set<CacheFlag> = setOf(/* _Additional_ cache flags */ CacheFlag.VOICE_STATE)

    override fun createJDA(event: BReadyEvent, eventManager: IEventManager) {
        // This uses JDABuilder#createLight, with the intents and the additional cache flags set above
        // It also sets the EventManager and a special rate limiter
        light(
            config.token,
            activity = Activity.customStatus("In Kotlin with ❤️")
        ) {
            // Other configs
        }
    }
}
@BService
public class Bot extends JDAService {
    private final Config config;

    public Bot(Config config) {
        this.config = config;
    }

    // If you use Spring, you can return values provided by JDAConfiguration in the getters below
    @NotNull
    @Override
    public Set<CacheFlag> getCacheFlags() {
        return Set.of(/* _Additional_ cache flags */);
    }

    @NotNull
    @Override
    public Set<GatewayIntent> getIntents() {
        return defaultIntents(/* _Additional_ intents */);
    }

    @Override
    public void createJDA(@NotNull BReadyEvent event, @NotNull IEventManager eventManager) {
        // This uses JDABuilder#createLight, with the intents and the additional cache flags set above
        // It also sets the EventManager and a special rate limiter
        createLight(config.getToken())
                .setActivity(Activity.customStatus("In Java with ❤️"))
                .build();
    }
}

You can now run your bot! Assuming you have done your config class and provided at least the token and owner IDs, you should be able to run the help command, by mentioning your bot @YourBot help.

Tip

If necessary, you can retrieve a JDA instance once createJDA has been called, I recommend listening to InjectedJDAEvent, but you can also get one later, using BContext#jda.

Optional - Add stacktrace-decoroutinator

I recommend adding stacktrace-decoroutinator, which will help you get clearer stacktrace when using Kotlin coroutines.

Note

Java users also benefit from it as it may help debug framework issues.

<dependencies>
    ...

    <dependency>
        <groupId>dev.reformator.stacktracedecoroutinator</groupId>
        <artifactId>stacktrace-decoroutinator-jvm</artifactId>
        <version>SD_VERSION</version>
    </dependency>
</dependencies>
repositories {
    ...
    mavenCentral()
}

dependencies {
    ...

    implementation("dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm:SD_VERSION")
}

Finally, load it on the first lines of your main program:

// stacktrace-decoroutinator has issues when reloading with hotswap agent
if ("-XX:+AllowEnhancedClassRedefinition" in ManagementFactory.getRuntimeMXBean().inputArguments) {
    logger.info { "Skipping stacktrace-decoroutinator as enhanced hotswap is active" }
} else if ("--no-decoroutinator" in args) {
    logger.info { "Skipping stacktrace-decoroutinator as --no-decoroutinator is specified" }
} else {
    DecoroutinatorRuntime.load()
}

Warning

stacktrace-decoroutinator must be loaded before any coroutine code is loaded, including suspending main functions suspend fun main() { ... }.

// stacktrace-decoroutinator has issues when reloading with hotswap agent
if (ManagementFactory.getRuntimeMXBean().getInputArguments().contains("-XX:+AllowEnhancedClassRedefinition")) {
    logger.info("Skipping stacktrace-decoroutinator as enhanced hotswap is active");
} else if (Arrays.asList(args).contains("--no-decoroutinator")) {
    logger.info("Skipping stacktrace-decoroutinator as --no-decoroutinator is specified");
} else {
    DecoroutinatorRuntime.INSTANCE.load();
}

Creating a runnable JAR

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.5.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>io.github.name.bot.Main</mainClass> <!-- TODO change here -->
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                </transformers>

                <createDependencyReducedPom>false</createDependencyReducedPom>
                <finalName>${artifactId}</finalName>
            </configuration>
        </execution>
    </executions>
</plugin>
plugins {
    ...
    id("com.github.johnrengelman.shadow") version "7.1.2"
}

application.mainClass.set("io.github.name.bot.Main")    //TODO change here

tasks.withType<ShadowJar> {
    mergeServiceFiles() // Fixes Java's service loading, which is used by Flyway
    archiveFileName.set("your-project-name.jar")        //TODO change here
}

While you can run the main class in your IDE during development, you can create a JAR with all the dependencies by pressing Ctrl twice in IntelliJ, then running:

mvn package
gradle shadowJar

Other resources

Take a look at other wiki pages, such as Dependency injection, Creating slash command and Using components.

Examples

You can find examples covering parts of the framework here.

Getting help

Don't hesitate to join the support server if you have any question!