Introduction
Lately I got more and more in touch with discord. I stopped hosting my own teamspeak 3 server and migrated everything to a discord server. Discord has lots of features, which I was missing using teamspeak. A few days ago I realized, that discord provides support for bots (teamspeak had bots, too).
Since I’m very familiar with Java, I wanted to implement the bot using this programming language. There are two APIs provided for Java, JDA and Discord4J. I’ve chosen JDA, because it has in my opinion a more convenient interface than Discord4J. I’ve also picked maven as the build management tool for the same reason I selected Java.
Configuration
First we need some configuration effort. We need the dependency reference itself and the repository, where we can download it. Additionally I added other dependencies to be able to test our code properly, before publishing it on our server.
<repositories>
<repository>
<id>jcenter</id>
<name>jcenter-bintray</name>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>4.1.1_144</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Junit is used as the test framework, Hamcrest helps with the readability of assertions and Jmockit adds support to create stubs and mocks while unit testing.
The last thing we need to configure is the maven assembly plugin to drop an executable jar artifact in the target folder. Therefore we need to add a plugin. A second plugin we use is required for Jmockit. This agent is needed to be referenced to enable dependency injection for stubs and mocks.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>de.klemensmorbe.discord.roll.RollEntryPoint</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>roll</finalName>
</build>
Project Structure
Now we finished the basic configuration for our simple discord bot project. The next thing we need is to add some structure. I’m using IntelliJ for the creation of the default structure. It supports the user creating the basic structure once the the project was identified as a maven project. To do so, simply right click the pom.xml and select add as maven project.
Bot Registration
For the bot implementation I’ve chosen to build an application which accepts one command !roll x and returns a random number between 0 and x. Before we can start building the function, we first have to ensure that our bot is running. Therefore we need to register a new application in the discord developer portal. Click on applications and create a new application, then select the application and navigate to the bot section. Create a new bot and copy the token to the clipboard, we will need this token to authenticate in our application that we are the newly created bot.
Java Implementation
Finally, we can start with the implementation. First we want to start the empty application without doing anything to test that the authentication works. Replace the token and start the application. You will get some warnings, but it’s ok. JDA uses SLF4J as the logging API and we didn’t configure it, but the application will be executed anyway.
public static void main(final String[] args) throws Exception {
final var token = "place-token-here";
JDABuilder.createDefault(token)
.setActivity(Activity.playing("Type !roll"))
.setStatus(OnlineStatus.ONLINE)
.build();
}
Every user in discord can have one or multiple activities, like playing a game or simply listening to music. I gave the bot an activity, so people read it and know how to interact with this bot. We set the status to online, so everyone is aware of the bot.
Next we add some logic to our bot. So lets create a listener, which reacts on chat messages starting with !roll. Create a class RollListener and add it to our JDA instance. The class itself should extend from the ListenerAdapter of the JDA API hooks.
The entire implementation contains a check if the author of the message is a real user to avoid endless loops with bots triggering each other. Then we check if the incoming text message starts with !roll, if it does so, we can check if there was passed some custom value, since we are supporting a roll range from 0 to x. If no value is present for x, we proceed with a default value of 100. If x is present, we check if it is a number and if it’s not too big. If the user submits an illegal command, we return 0, otherwise we will trigger the roll() function.
package de.klemensmorbe.discord.roll;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import java.util.Random;
import java.util.regex.Pattern;
public class RollListener extends ListenerAdapter {
private static final Pattern NUMBER = Pattern.compile("\\d*");
private static final String ROLL_COMMAND = "!roll";
private static final String ROLLED_MESSAGE = "rolled %s";
private static final String DEFAULT_ROLL = "100";
private static final String DELIMITER = " ";
private static final int ZERO = 0;
@Override
public void onMessageReceived(MessageReceivedEvent event) {
if (!event.getAuthor().isBot()) {
String content = event.getMessage().getContentRaw();
if (content.startsWith(ROLL_COMMAND)) {
final var commandLine = content.split(DELIMITER);
final var maxRoll = commandLine.length == 2 ? commandLine[1] : DEFAULT_ROLL;
if (isIntegerInLegalRange(maxRoll)) {
event.getChannel().sendMessage(String.format(ROLLED_MESSAGE, roll(maxRoll))).queue();
} else {
event.getChannel().sendMessage(String.format(ROLLED_MESSAGE, roll(ZERO))).queue();
}
}
}
}
boolean isIntegerInLegalRange(final String input) {
return NUMBER.matcher(input).matches() && !isInputGraterThanInt(input);
}
boolean isInputGraterThanInt(final String input) {
try {
Integer.parseInt(input);
return false;
} catch (final NumberFormatException e) {
return true;
}
}
int roll(final String maxRoll) {
return roll(Integer.parseInt(maxRoll));
}
int roll(final int maxRoll) {
return maxRoll > ZERO ? new Random().nextInt(maxRoll) : ZERO;
}
}
We are almost done. There is only one thing missing. We created a new bot application, we started it, but we don’t see it in our discord server. The bot needs to be invited to your server you are administrator of. To do that, we need to go back to the discord developer portal and click on OAuth2. Add the scope bot and add the permission to send messages. Now copy the generated link and paste it into a blank browser tab. You will be prompted to select a server, where the bot should be added. You can recheck the permissions and click on submit.
Now connect on your discord server. You should see its appearance. Now type the command in a public text channel and see the magic happens. The bot should accept your command and reply with a generated number.
Comments