diff --git a/404.html b/404.html new file mode 100644 index 0000000..60d1129 --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + +
+ + + + + +It's important to know the life-cycle of a discord bot to properly handle disconnects. The following state diagram shows the 4 states a bot can have:
The bot is connected to the websocket and receives all events.
The bot is not connected to the websocket and receives no events. It's not uncommon for a bot to occasionally lose connection. This can have various reasons, for example:
The bot will periodically try to resume/reconnect to the websocket. It will start with a small frequency and increase it with every failed reconnect attempt. You can modify the reconnect delay with the DiscordApi#setReconnectDelay(...)
method. The following example code would increase the delay linearly. The 1st attempt would be delayed for 2
seconds, the 2nd attempt for 4
seconds, the 3rd attempts for 6
seconds, ...
api.setReconnectDelay(attempt -> attempt * 2);
+
Important: Bots can only reconnect 1000 times in a 24-hour period (every ~90 seconds). This limit is global and across all shards. Upon hitting this limit, all active sessions for the bot will be terminated, the bot's token will be reset, and you will receive an email notification. This is the reason Javacord increases the reconnect delay with every attempt.
By default, the $default_delay$ formula below is used to calculate the reconnect delay
$$ default_delay(a) = \\lfloor a^{1.5} - \\frac{a^{1.5}}{\\frac{1}{(0.1 \\cdot a)} + 1} \\rceil $$
with $a$ being the attempt.
The formula will generate the following reconnect delay:
Attempt | Delay |
---|---|
1 | 1 |
2 | 2 |
3 | 4 |
4 | 6 |
5 | 7 |
... | ... |
10 | 16 |
15 | 23 |
20 | 30 |
... | ... |
50 | 59 |
100 | 91 |
150 | 115 |
... | ... |
Resuming is only possible for a short time after being disconnected. If the bot can successfully resume the connection, you will not miss any events. Your bot will receive all events you missed while being disconnected. The cache gets updated accordingly.
If your bot reconnects (not resumes!), the whole cache gets wiped, and you will not receive any missed events.
What does this mean?
`,22),h=t("References to entities (e.g. a "),m=e("code",null,"Server",-1),f=t(", "),b=e("code",null,"User",-1),v=t(", "),g=e("code",null,"Channel",-1),y=t(", ...) will be outdated. This is why you should never store entities, but the id instead. See "),k=t("Entity Cache"),w=t("."),_=e("li",null,"You will miss events. There's no way to receive the missed events.",-1),x=e("li",null,[t("Listeners attached to entities will "),e("strong",null,"not"),t(" be affected, because they are bound to the entity's id, not the object itself.")],-1),T=n(`For most bots, there's nothing you have to do. All registered listeners are reconnect-resistant, which means if your bot is only reacting to events, it will work fine after a restart. For example, the following code will not be affected by a reconnect (besides maybe some missed !ping
messages):
api.addMessageCreateListener(event -> {
+ if (event.getMessage().getContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+});
+
In case you want to handle reconnects (e.g. fetch the message history to detect missed messages), there are special connection-related listeners which can be used to track the state of the bot:
LostConnectionListener
ReconnectListener
ResumeListener
INFO
There are a lot of convenient methods which aim to make your life easier with i.e., not being able to have an invalid configuration of your builder. Therefore, the following examples will only show the usage with the convenient methods.
INFO
There are 2 different types of Commands:
createGlobal(DiscordApi)
.createForServer(Server)
.Let's get started with the most basic command, a ping command.
SlashCommand command = SlashCommand.with("ping", "Checks the functionality of this command")
+ .createGlobal(api)
+ .join();
+
That's all you have to do!
Let's have a look at a more complex command which involves nearly all possibilities:
SlashCommand command =
+ SlashCommand.with("channel", "A command dedicated to channels",
+ Arrays.asList(
+ SlashCommandOption.createWithOptions(SlashCommandOptionType.SUB_COMMAND_GROUP, "edit", "Edits a channel",
+ Arrays.asList(
+ SlashCommandOption.createWithOptions(SlashCommandOptionType.SUB_COMMAND, "allow", "Allows a permission to a user for a channel",
+ Arrays.asList(
+ SlashCommandOption.create(SlashCommandOptionType.CHANNEL, "channel", "The channel to modify", true),
+ SlashCommandOption.create(SlashCommandOptionType.USER, "user", "The user which permissions should be changed", true),
+ SlashCommandOption.createWithChoices(SlashCommandOptionType.DECIMAL, "permission", "The permission to allow", true,
+ Arrays.asList(
+ SlashCommandOptionChoice.create("manage", 0),
+ SlashCommandOptionChoice.create("show", 1)))
+ ))))))
+ .createGlobal(api)
+ .join();
+
Let that sink in first!
What are we doing here?
channel
.edit
which basically is just a folder where you can put your commands in.allow
which is our actual command. Therefore, our complete argument looks like channel edit allow
.REQUIRED
attributeYou can only mark the last argument as being not required. This means it can be optionally set by the command executor. In the above example you could i.e. set the PERMISSIONS
argument to false
.
Your command has to follow these structures in order to be successfully created:
VALID
+
+command
+|
+|__ subcommand
+|
+|__ subcommand
+
+----
+
+command
+|
+|__ subcommand-group
+ |
+ |__ subcommand
+|
+|__ subcommand-group
+ |
+ |__ subcommand
+
+----
+
+VALID
+
+command
+|
+|__ subcommand-group
+ |
+ |__ subcommand
+|
+|__ subcommand
+
+-------
+
+INVALID
+
+
+command
+|
+|__ subcommand-group
+ |
+ |__ subcommand-group
+|
+|__ subcommand-group
+ |
+ |__ subcommand-group
+
+----
+
+INVALID
+
+command
+|
+|__ subcommand
+ |
+ |__ subcommand-group
+|
+|__ subcommand
+ |
+ |__ subcommand-group
+
All global commands:
Set<SlashCommand> commands = api.getGlobalSlashCommands().join();
+
All commands only available on a single server:
Server server = ...;
+Set<SlashCommand> commands = api.getServerSlashCommands(server).join();
+
WARNING
Getting all commands from a server only contains the commands you have created on this specific server. Therefore, the returned list does not include any global command!
When updating your commands you only have to include what you actually want to change. The following updater will change the previous created command and change its base name from channel
to channels
.
SlashCommand updatedCommand =
+ new SlashCommandUpdater(commandId)
+ .setName("channels")
+ .updateGlobal(api)
+ .join();
+
If you have to update / create multiple commands at once it advised to use the batch updater to only have to do 1 request.
DiscordApi api = ...;
+
+Set<SlashCommandBuilder> builders = new HashSet<>();
+builders.add(new SlashCommandBuilder().setName("server").setDescription("A command for the server"));
+builders.add(new SlashCommandBuilder().setName("permission").setDescription("A command for permissions"));
+
+api.bulkOverwriteGlobalApplicationCommands(builders).join();
+
Permissions exist to enable / disable the usage of your commands for certain things. These things may be:
When you create a command you can specify which permissions are required to use it. In addition to the required permissions, you can also specify whether the command should be available in DMs.
SlashCommand.with("ping","Ping!")
+ .setDefaultEnabledForPermissions(PermissionType.ADMINISTRATOR, PermissionType.BAN_MEMBERS)
+ //.setDefaultDisabled() Effectively the same as setDefaultEnabledForPermissions(PermissionType.ADMINISTRATOR) but this will lead to the default type by Discord.
+ .setEnabledInDms(false)
+ .createGlobal(api)
+ .join();
+
INFO
Once your bot has been invited to a server, you can not change the permissions afterwards on this server. Then it's up to the server administrators / owner to correctly set up the commands for users / roles / channels.
A future is basically a wrapper, that will contain a value in the future but might not contain it right now. This is useful, if a method call requires some time and should not block the execution of your current code. You can easily see the difference with a primitive speed comparison:
long currentTime = System.currentTimeMillis();
+channel.sendMessage("Test 1");
+channel.sendMessage("Test 2");
+channel.sendMessage("Test 3");
+channel.sendMessage("Test 4");
+channel.sendMessage("Test 5");
+// Prints "4 ms"
+System.out.println((System.currentTimeMillis() - currentTime) + " ms");
+
long currentTime = System.currentTimeMillis();
+channel.sendMessage("Test 1").join();
+channel.sendMessage("Test 2").join();
+channel.sendMessage("Test 3").join();
+channel.sendMessage("Test 4").join();
+channel.sendMessage("Test 5").join();
+// Prints "894 ms"
+System.out.println((System.currentTimeMillis() - currentTime) + " ms");
+
TIP
join()
blocks the current thread until the method finished. This will be explained later.
The join
method blocks the current thread until the method finished. It returns the method's result or throws a CompletionException
if anything failed.
The following example would create a new text channel in a given server
and sends a message directly afterwards.
// Create the channel
+ServerTextChannel channel = new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .join();
+// Send a message in the new channel
+Message message = channel.sendMessage("First!").join();
+// Adds an reaction to the message. Even though this method doesn't return anything,
+// join() ensures, that an exception is thrown in case something went wrong
+message.addReaction("\u{1F44D}").join();
+
DANGER
You should avoid join()
for methods which will be called frequently.
TIP
While join()
can become a performance issue when you call it very frequently, it is very convenient to use and easy to understand. If you are new to programming and just want to get your first bot working, this is a good method to start with.
Once you gathered more experience, we highly advise against using join
as it negatively impacts your bot's performance!
The thenAccept
method accepts a Consumer
, that consumes the result of the method and is executed asynchronously. It is the method you usually want to use most of the time.
The following example would create a new text channel in a given server
and send a message directly afterwards.
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenAccept(channel -> {
+ channel.sendMessage("First!").thenAccept(message -> {
+ message.addReaction("\u{1F44D}");
+ });
+ });
+
DANGER
The example code above has a major problem: Any exception that might occur will be completely ignored. This makes it very hard to find bugs.
For example, if your bot doesn't have the permissions to create a new channel, it will just fail silently.
The exceptionally
method accepts a Function
as parameter, which consumes possible exceptions and returns a fallback value.
The following example would create a new text channel in a given server
and send a message directly afterwards. If something fails (e.g., if the bot isn't allowed to create a text channel in the server), it will log an exception.
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenAccept(channel -> {
+ channel.sendMessage("First!").thenAccept(message -> {
+ message.addReaction("\u{1F44D}").exceptionally(e -> {
+ e.printStackTrace(); // Adding the reaction failed
+ return null;
+ });
+ }).exceptionally(e -> {
+ e.printStackTrace(); // Message sending failed
+ return null;
+ });
+ }).exceptionally(e -> {
+ e.printStackTrace(); // Channel creation failed
+ return null;
+ });
+
Wow! This looks ugly \u{1F92E}. But worry not! There are many options to improve this code!
To make things simpler for you, Javacord has the ExceptionLogger
class, which can be used here. It logs every exception you didn't catch manually.
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenAccept(channel -> {
+ channel.sendMessage("First!").thenAccept(message -> {
+ message.addReaction("\u{1F44D}").exceptionally(ExceptionLogger.get());
+ }).exceptionally(ExceptionLogger.get());
+ }).exceptionally(ExceptionLogger.get());
+
Okay! This is at least a little better, but still not really perfect \u{1F914}.
The example to create a text channel can now be written like this:
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenCompose(channel -> channel.sendMessage("First!"))
+ .thenCompose(message -> message.addReaction("\u{1F44D}"))
+ .exceptionally(ExceptionLogger.get());
+
Finally \u{1F389}! Now we only need a single exceptionally(...)
call at the end. We also got rid of the nested callbacks (usually referred to as "callback hell").
For better understanding, here's the example with comments that tell you the type at each line:
new ServerTextChannelBuilder(server) // ServerTextChannelBuilder
+ .setName("new-channel") // ServerTextChannelBuilder
+ .create() // CompletableFuture<ServerTextChannel>
+ .thenCompose(channel -> channel.sendMessage("First!")) // CompletableFuture<Message>
+ .thenCompose(message -> message.addReaction("\u{1F44D}")) // CompletableFuture<Void>
+ .exceptionally(ExceptionLogger.get()); // CompletableFuture<Void>
+
Components are interactive elements like buttons or hidden elements like the ActionRow which use is for displaying the visible components. You can add them to a message and interact with users in a very convenient way. Currently, the only interactive components available at the moment are buttons. They differ in style and behaviour(link redirect) seen in the picture below:
Sending a component with your message is a simple as that:
TextChannel channel = ...;
+
+new MessageBuilder()
+ .setContent("Click on one of these Buttons!")
+ .addComponents(
+ ActionRow.of(Button.success("success", "Send a message"),
+ Button.danger("danger", "Delete this message"),
+ Button.secondary("secondary", "Remind me after 5 minutes")))
+ .send(channel);
+
You simply add a High Level component like an ActionRow which is a container for displaying your components. In turn the ActionRow consist of the components you can interact with like Buttons.
This works for Select Menus as well:
TextChannel channel = ...;
+
+new MessageBuilder()
+ .setContent("Select an option of this list!")
+ .addComponents(
+ ActionRow.of(SelectMenu.create("options", "Click here to show the options", 1, 1,
+ Arrays.asList(SelectMenuOption.create("Option One", "You selected Option One!", "Click here to select Option One"),
+ SelectMenuOption.create("Option Two", "You selected Option Two!", "Click here to select Option Two"),
+ SelectMenuOption.create("Option Three", "You selected Option Three!", "Click here to select Option Three")))))
+ .send(channel);
+
Bot
TIP
If you want to, you can rename your application first
Add bot
and confirm the popupNDc[...]pCs
. You can just click on Copy
.DANGER
This token is used to login your bot. Keep it secret!
Bots cannot join a server on their own like normal Discord users can. Instead, the owner of a server has to invite the bot using a so called Invite Link
. There are multiple ways to create the invite link:
The easiest way to obtain an invite link for your bot is by letting Javacord do it for you. Simply execute the following code, and it will print the invite link to your console:
DiscordApi api = new DiscordApiBuilder().setToken("your token").login().join();
+System.out.println(api.createBotInvite());
+
If you don't have Javacord setup yet, you can also create the invite link manually.
In order to add a bot to your server you need its client id.
`,19),D=t("You can get your client id from the "),N={href:"https://discord.com/developers/applications/me",target:"_blank",rel:"noopener noreferrer"},J=t("same page"),E=t(" where you created it."),S=o('With this id you can create an invite link for your bot.
If you are the owner or admin of the server, you can use this link to add your bot to your server. Otherwise, you have to give the link to the server owner/admins and ask them to add your bot.
TIP
Unlike the token, you don't have to keep your client id secret
Just use the following link and replace 123456789
with your own client id.
https://discord.com/api/oauth2/authorize?client_id=123456789&scope=applications.commands%20bot&permissions=0
You can calculate the permissions (in the link above it's the 0
) on the page where you created the bot:
You can now open the link and add the bot to your server:
TIP
Only the owner and admins of a server can invite bots. If you do not own a server yet, it is recommended to create one for testing.
Javacord provides XyzBuilder
classes to create new Discord entities like channels, webhooks, servers, and many more.
You can get the channel builders for a specific server using the Server#createXyzChannelBuilder
or by directly calling the constructor. Creating a ServerVoiceChannel
would look like this:
Server server = ...;
+ServerVoiceChannel channel = new ServerVoiceChannelBuilder(server)
+ .setName("example-channel")
+ .setUserlimit(10)
+ .create().join();
+
You can get the WebhookBuilder
for a specific text channel:
ServerTextChannel channel = ...;
+Webhook webhook = new WebhookBuilder(channel)
+ .setName("Captain Hook")
+ .setAvatar(new File("C:/Users/Bastian/Pictures/puppy.jpg"))
+ .create().join();
+
You can get the InviteBuilder
for a specific server channel:
ServerTextChannel channel = ...;
+Invite invite = new InviteBuilder(channel)
+ .setMaxAgeInSeconds(60*60*24)
+ .setMaxUses(42)
+ .create().join();
+
You can get the ServerBuilder
from the current api instance:
DiscordApi api = ...;
+long serverId = new ServerBuilder(api)
+ .setName("My Awesome Server")
+ .setIcon(api.getYourself().getAvatar())
+ .setVerificationLevel(VerificationLevel.HIGH)
+ .setDefaultMessageNotificationLevel(DefaultMessageNotificationLevel.ONLY_MENTIONS)
+ .setRegion(Region.EU_CENTRAL)
+ .create().join();
+
WARNING
By default, bots can only create servers if they are in less than 10 servers. You can contact the Discord support to request a higher limit.
File
-> New
-> Project
)Maven Project
Next
Create a simple project
Next
com.github.yourname
)myfirstbot
)Finish
pom.xml
filepom.xml
<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>your.package.name</groupId>
+ <artifactId>myfirstbot</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.javacord</groupId>
+ <artifactId>javacord</artifactId>
+ <version>$latest-version</version>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+
+</project>
+
src/main/java
folderpackage com.github.yourname.myfirstbot;
+
+import org.javacord.api.DiscordApi;
+import org.javacord.api.DiscordApiBuilder;
+
+public class Main {
+
+ public static void main(String[] args) {
+ // Insert your bot's token here
+ String token = "your token";
+
+ DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+
+ // Print the invite url of your bot
+ System.out.println("You can invite the bot by using the following url: " + api.createBotInvite());
+ }
+
+}
+
You can run your code by clicking on the small green arrow
`,30);function y(x,w){const o=s("LatestVersion"),p=s("ClientOnly"),c=s("RouterLink");return l(),u("div",null,[k,t(p,null,{default:e(()=>[t(o)]),_:1}),g,n("div",h,[m,n("p",null,[v,t(c,{to:"/wiki/getting-started/setup/intellij-gradle.html"},{default:e(()=>[b]),_:1}),f])]),_])}var q=i(d,[["render",y],["__file","eclipse-maven.html.vue"]]);export{q as default}; diff --git a/assets/embeds.html.68dc7509.js b/assets/embeds.html.68dc7509.js new file mode 100644 index 0000000..f74b031 --- /dev/null +++ b/assets/embeds.html.68dc7509.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-37293ae0","path":"/wiki/basic-tutorials/embeds.html","title":"Embeds","lang":"en-US","frontmatter":{"keywords":["EmbedBuilder","inline field","setTitle","setDescription","setAuthor","addField","addInlineField","setColor","setFooter","setImage","setThumbnail"]},"excerpt":"","headers":[{"level":2,"title":"\u{1F528} Creating an Embed","slug":"creating-an-embed","children":[]},{"level":2,"title":"\u{1F4F7} Supported Image Sources","slug":"supported-image-sources","children":[]},{"level":2,"title":"\u{1F512} Embed Limits","slug":"embed-limits","children":[]},{"level":2,"title":"\u2753 FAQ","slug":"faq","children":[{"level":3,"title":"What is the second parameter of setAuthor(...)?","slug":"what-is-the-second-parameter-of-setauthor","children":[]},{"level":3,"title":"What's the difference between an inline field and a normal one?","slug":"what-s-the-difference-between-an-inline-field-and-a-normal-one","children":[]},{"level":3,"title":"Can I change the placement of inline fields?","slug":"can-i-change-the-placement-of-inline-fields","children":[]},{"level":3,"title":"How can I format text in an embed?","slug":"how-can-i-format-text-in-an-embed","children":[]}]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/basic-tutorials/embeds.md"}`);export{e as data}; diff --git a/assets/embeds.html.f7a6f54c.js b/assets/embeds.html.f7a6f54c.js new file mode 100644 index 0000000..bc532ef --- /dev/null +++ b/assets/embeds.html.f7a6f54c.js @@ -0,0 +1,16 @@ +import{_ as e,r as t,o,c as i,a,b as c,e as p,d as n}from"./app.151ccb98.js";const l={},d=p(`Embeds are attached to messages and have a special design. The usually look like this:
Javacord provides an EmbedBuilder
which can be used to create embeds:
// Create the embed
+EmbedBuilder embed = new EmbedBuilder()
+ .setTitle("Title")
+ .setDescription("Description")
+ .setAuthor("Author Name", "http://google.com/", "https://cdn.discordapp.com/embed/avatars/0.png")
+ .addField("A field", "Some text inside the field")
+ .addInlineField("An inline field", "More text")
+ .addInlineField("Another inline field", "Even more text")
+ .setColor(Color.BLUE)
+ .setFooter("Footer", "https://cdn.discordapp.com/embed/avatars/1.png")
+ .setImage(new File("C:/Users/Bastian/Pictures/puppy.jpg"))
+ .setThumbnail(new File("C:/Users/Bastian/Pictures/kitten2.png"));
+// Send the embed
+channel.sendMessage(embed);
+
By default, Discord expects embed images to be a link (e.g., the image link used in setFooter(...)
), but you can also use attachments for images. If you provide a non-url image source (e.g. the puppy.jpg
file used in setImage(...)
), Javacord automatically uploads them as an attachment to the message and uses this attachment for the embed.
Type | Limit |
---|---|
Title | 256 characters |
Description | 4096 characters |
Field Amount | Up to 25 fields |
Field Name | 256 characters |
Field Value | 1024 characters |
Footer Text | 2048 characters |
Author Name | 256 characters |
In addition to the limits above, the sum of all characters in an embed structure must not exceed 6000 characters.
setAuthor(...)
?.setAuthor("Author Name", "http://google.com/", "https://cdn.discordapp.com/embed/avatars/0.png")
+
null
.Normal fields always start in a new line, whereas several inline fields can be in the same line.
No, Discord does not allow different embed layouts.
There are two different kinds of emojis in Discord: Unicode emojis and custom emojis.
You can either directly add them in your code, e.g.
channel.sendMessage("Hi! \u{1F603}");
+
or use the normal "tag" like you would in the Client:
channel.sendMessage("Hi! :smiley:");
+
Adding unicode reactions is only possible by using the "real" reaction. It doesn't support tags like :smiley:
.
message.addReaction("\u{1F603}"); // works
+message.addReaction(":smiley:"); // doesn't work
+
Custom emojis are emojis that are created in a server. You can get all custom emojis the bot knows by using DiscordApi#getCustomEmojis()
.
To use custom emojis, you have to know its "tag", which has the format <:name:id>
. You can get it by calling CustomEmoji#getMentionTag()
:
channel.sendMessage("Hi! <:javacord:415465982715494402>");
+
CustomEmoji emoji = ...;
+channel.sendMessage("Hi! " + emoji.getMentionTag());
+
You can either directly use the custom emoji object or use the tag without the <:
>
if you don't have access a custom emoji object (e.g., because it's from a different shard):
CustomEmoji emoji = ...;
+message.addReaction(emoji);
+
message.addReaction("javacord:415465982715494402");
+
Just add a \\
in front of the emoji and press Enter
In Javacord, all Emojis are a child of the Emoji
interface:
Known custom emojis are emojis that the bot knows because it's a member of the server with this emoji. A custom emoji can be unknown if someone adds a reaction with an unknown emoji for example. A KnownCustomEmoji
has additional methods like getServer()
or updateName(String)
.
message.addReaction(EmojiManager.getByAlias(":thumbsup:"));
+
Javacord keeps an internal cache for entities (e.g. Servers, Channels, Users, ...). It is important to know how the cache behaves to properly use it.
Nearly every entity known by the bot is guaranteed to be in the cache. There are a few exceptions though:
Webhooks and Invites are not kept in the cache at all and won't receive any updates.
Embeds from message.getEmbed()
won't receive updates. If a message's embed gets edited, getEmbed()
will return a completely new embed object.
Javacord's cache exclusively uses websocket events to keep the cache up to date. This means that the content of your objects might be outdated, even though you modified it yourself:
Messages message = ...;
+System.out.println(message.getContent()); // Prints the old content, e.g. "old content"
+message.edit("new content").join(); // Edits the message and waits for success
+System.out.println(message.getContent()); // Still prints "old content"
+Thread.sleep(1000);
+System.out.println(message.getContent()); // Most likely prints "new content" now
+
Even though entities are usually kept in the cache for a very long time, you should not keep references to these objects for a longer period of time, but store the id / use event methods:
// Bad
+Message message = ...;
+message.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("\u{1F44E}")) {
+ message.delete(); // Prevents "message" from being garbage collected
+ }
+});
+
+// Good
+Message message = ...;
+message.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("\u{1F44E}")) {
+ event.deleteMessage(); // Does not use the message object
+ }
+});
+
// Bad
+Set<User> usersWithBadMood = new HashSet<>();
+api.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("\u{1F626}")) {
+ usersWithBadMood.add(event.getUser());
+ }
+});
+
+// Good
+Set<Long> usersWithBadMood = new HashSet<>();
+api.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("\u{1F626}")) {
+ usersWithBadMood.add(event.getUser().getId());
+ }
+});
+
Some examples of when cached entities are invalidated:
Channel
, because you left and rejoined a server...
in the code examples?You have to replace the ...
with an instance that can be assigned to the datatype seen left.
For example, if you see TextChannel channel = ...
, you have to replace ...
with an instance that is a TextChannel which you can get from the API api.getTextChannelById(CHANNEL_ID)
(note this returns an Optional<TextChannel>) or from an event like messageCreateEvent.getChannel()
.
There are multiple reasons why your code might not work. The most common ones are:
',5),x=e("li",null,"Your code is not being reached. So make sure your code actually gets executed with a print statement or a debugger.",-1),v=t("Add at least "),q=e("code",null,".exceptionally(ExceptionLogger.get())",-1),b=t(" to every "),C=e("a",{href:"../essential-knowledge/completable-futures"},"CompletableFuture",-1),E=t(" (like when sending a message) to show any exceptions that might come from Discord."),A=a('User#getRoles(Server)
do not return the roles of the user. To fix this make sure to add the GUILD_MEMBERS
intent.NoSuchElementException
. Congratulations, you have killed a kitten! You are most likely getting this Exception because you handle Optionals wrong. Read the article on Optionals to learn how to use them correctly.Don't ask:
Why is my code not working?
+//Code
+
Why am I getting Exception X?
+
To ensure all information is provided that is needed to solve your issue, you should ask your question in a format like:
I have an issue with: YOUR_ISSUE
+I want to do: WHAT_YOU_WANT_TO_DO
+Currently this happens: WHAT_HAPPENS_NOW
+
+//Code
+
+//Exception
+The exception is thrown in the following line(not the number): CODE_LINE
+
While all 3 libraries are Wrappers for the programming language Java, they use different techniques and concepts for their API.
null
. channel.sendMessage("Javacord")
message.getMessageAuthor().asUser().isPresent()
null
if values are not present. channel.sendMessage("JDA").queue()
message.getMember() != null
reactive
approach. channel.createMessage("Pong!").block();
Discord allows you to "subscribe" to specific groups of events. These "subscriptions" are called intent. Disabling intents that are not required for your bot can significantly increase your bot's performance.
Below you can find a table with all intents supported by Discord.
Intent | Safe to Disable | Privileged |
---|---|---|
GUILDS | \u274C | \u274C |
GUILD_MEMBERS | \u2714\uFE0F | \u2714\uFE0F |
GUILD_BANS | \u26A0\uFE0F* | \u274C |
GUILD_EMOJIS | \u26A0\uFE0F* | \u274C |
GUILD_INTEGRATIONS | \u2714\uFE0F | \u274C |
GUILD_WEBHOOKS | \u2714\uFE0F | \u274C |
GUILD_INVITES | \u2714\uFE0F | \u274C |
GUILD_VOICE_STATES | \u26A0\uFE0F* | \u274C |
GUILD_PRESENCES | \u2714\uFE0F | \u2714\uFE0F |
GUILD_MESSAGES | \u2714\uFE0F | \u274C |
GUILD_MESSAGE_REACTIONS | \u2714\uFE0F | \u274C |
GUILD_MESSAGE_TYPING | \u2714\uFE0F | \u274C |
DIRECT_MESSAGES | \u2714\uFE0F | \u274C |
DIRECT_MESSAGE_REACTIONS | \u2714\uFE0F | \u274C |
DIRECT_MESSAGE_TYPING | \u2714\uFE0F | \u274C |
MESSAGE_CONTENT | \u2714\uFE0F | \u2714\uFE0F |
AUTO_MODERATION_CONFIGURATION | \u2714\uFE0F | \u274C |
AUTO_MODERATION_EXECUTION | \u2714\uFE0F | \u274C |
* Will most likely work, but needs further testing
',6),k={class:"custom-container tip"},v=n("p",{class:"custom-container-title"},"Good to know!",-1),m=n("em",null,"Guild",-1),b=s(" is a synonym for servers, commonly used in Discord's API. See "),g=s("Glossary"),_=s("."),f=n("h2",{id:"what-happens-when-i-disable-some-intents",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#what-happens-when-i-disable-some-intents","aria-hidden":"true"},"#"),s(" \u{1F4A1} What Happens When I Disable Some Intents?")],-1),E=n("p",null,"When you disable some of the listed intents, Javacord will not fire events that belong to the intents and will not update these specific parts of the cache.",-1),I=n("p",null,[s("At the moment, we don't have a list which events are affected by which intents (but it will come soon\u2122\uFE0F). However, most intents should be self-explanatory. E.g. when you disable the "),n("code",null,"DIRECT_MESSAGES"),s(" intent, your bot will not receive any private messages.")],-1),S=n("h2",{id:"privileged-intents",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#privileged-intents","aria-hidden":"true"},"#"),s(" \u{1F451} Privileged Intents")],-1),y=s('Some intents are defined as "privileged" due to the sensitive nature of the data. To use these intents, you have to go to your bot in the '),w={href:"https://discord.com/developers/applications",target:"_blank",rel:"noopener noreferrer"},T=s("Developer Portal"),D=s(" (where you created bot) and manually enable the intents:"),A=n("p",null,[n("img",{src:r,alt:""})],-1),x=n("p",null,"There are some additionally restrictions for bots that are in over 100 servers:",-1),G=n("ul",null,[n("li",null,"Your bot must be verified"),n("li",null,"Your bot must be whitelisted to use this intents")],-1),N=s("Take a look at the official article from Discord about this topic and how to verify your bot: "),U={href:"https://support.discord.com/hc/en-us/articles/360040720412",target:"_blank",rel:"noopener noreferrer"},j=s("Bot Verification and Data Whitelisting"),O=s("."),L=o(`The following two intents are especially noteworthy: GUILD_MEMBERS
and GUILD_PRESENCES
. Besides being privileged, they have some special implications for Javacord:
GUILD_PRESENCES
This intent is required to get updates about a user's status (i.e., if they are online, what game they are playing, ...). Additionally, without this intent it might take considerably longer to cache all users because of ratelimits (up to 10 minutes for shards with 1000 servers). It is advised against setting DiscordApiBuilder#setWaitForAllUsersOnStartup(true)
without this intent, unless absolutely necessary.
GUILD_MEMBERS
This intent is required to keep all users in Javacord's cache. Without this intent, methods like Server#getMembers()
or DiscordApi#getCachedUsers()
will return empty collections. However, you will still be able to access users from objects like messages, e.g. Message#getUserAuthor()
will still work.
MESSAGE_CONTENT
This intent is a bit different to the other as it does not act as a toggle to receive any events. It's sole purpose is to receive the message content, attachments, components, and embeds. Otherwise, these fields will be empty when you receive a Message
object.
Javacord allows you to specify intents in the DiscordApiBuilder
prior to login. There are many options to set intents. The following example code shows the most common ones:
This method enables all non-privileged intents. This is the default setting in Javacord.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllNonPrivilegedIntents()
+ .login()
+ .join();
+
This method enabled all non-privileged intents, except the given ones.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllNonPrivilegedIntentsExcept(Intent.GUILD_WEBHOOKS)
+ .login()
+ .join();
+
This method enabled all intents.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllIntents()
+ .login()
+ .join();
+
This method enabled all intents, except the given ones.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllIntentsExcept(Intent.GUILD_PRESENCES, Intent.GUILD_WEBHOOKS)
+ .login()
+ .join();
+
This method only enables the given intents.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setIntents(Intent.GUILDS, Intent.DIRECT_MESSAGES)
+ .login()
+ .join();
+
This method adds the intents to the currently enabled ones(by default all non-privileged). This is useful i.e. if you only want to enable 1 privileged intent like the MESSAGE_CONTENT
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .addIntents(Intent.MESSAGE_CONTENT)
+ .login()
+ .join();
+
Welcome to the Javacord wiki! \u{1F44B}
This wiki will help you to get started with your first Discord bot as fast as possible.
The wiki is divided into four groups:
While the wiki is great and covers many aspects of Javacord, we highly recommended you to join our Discord server if you have any questions:
',8),h=t("Join the "),u={href:"https://discord.gg/javacord",target:"_blank",rel:"noopener noreferrer"},p=t("Javacord server");function f(g,v){const o=r("ExternalLinkIcon");return s(),i("div",null,[l,e("ul",null,[e("li",null,[h,e("strong",null,[e("a",u,[p,n(o)])])])])])}var m=a(d,[["render",f],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/intellij-gradle.html.4d5164dd.js b/assets/intellij-gradle.html.4d5164dd.js new file mode 100644 index 0000000..49f275b --- /dev/null +++ b/assets/intellij-gradle.html.4d5164dd.js @@ -0,0 +1,42 @@ +import{_ as r,r as a,o as p,c as l,b as s,w as t,a as e,d as n,e as d}from"./app.151ccb98.js";var u="/assets/create-project.f0c107a4.png",k="/assets/select-gradle.959beb91.png",g="/assets/new-project.7658c402.png",h="/assets/new-project-2.3dadb90c.png",v="/assets/new-project-3.bd7a7df1.png",A="/assets/after-finished.b35ece7e.png",b="/assets/new-package.fc4668da.png",m="/assets/new-package-2.9c890d4d.png",f="/assets/new-class.388b2e2e.png",y="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVsAAACXCAYAAAC7kviWAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA8zSURBVHhe7Z3LkxRFHseJMDbYgxf34p68eNAwXIONxV1tmHF4uQwg8hBQdGcwgIlAcGVZBBGXGXksAkoMzsDIuu7jwMxghKFeOPRc/AO8e+BmePLgaQ/ecuuX1VmVXZ1VU/3Kru76fCM+MV2ZWdVTSfRnfpU1Q6349YXvFAAAdBctW0IIId0LsiWEEA9BtoQQ4iHIlhBCPATZEkKIhyBbQgjxkNyy/dWZ/7UFIYSUOU3JdmhoqCWQLSGk7GlKtmvWrGkJZEsIKXuaku0zzzyj+fnnnzXffPNN1Ca89957ut1uE5AtIaTfsmLFCvXtt9/WthojfTImb5qS7erVqzUzMzNaqjt27IjahDNnzuh2u01YXrb31XRlhf7GNZXpoKVXuacm5HuYuFfbNpH2ipruyjeWdv7dfE9CSFZEpr9Y+UuncLP60tKUbFetWqU5ffq0lqrZXq49U7b3p1UlEEyd24K26aTrWkorsgr3qQTyq/dtl8SXef7IlpBexiXVVkQraUq2Tz75pObkyZNaqmbbcOrUKWd7umzDiq6hiOxYWpft9L1AgnUVdjfEt9z5I1tCeh1brq2KVtKUbB977DHNzZs3tVS3b9+uHn/8cfXxxx+rw4cPq6+//lr98MMP0ThDqmx1VTcRKCUtoWwmJirB5XVtXK0SDC+5433vTViX4dpesm+yLUjK/nFiwckxK5HpEuJzHEfGx+J0jE9aNef5m2M0nqOkfgkibHa1EUJajZFsq6KVNCXbRx99VPPjjz9qvvzyS/XEE0+oxcVF9dNPP6nvv/9eHTlyJBpnyJRt5vpsKMxU4d2bsPpM7DGJ8W3tn3Fcc5zgayRBaatUouPfn45fR8l1/tb7RLHa7fc0cbURQlqOd9k+8sgjLdFuZRvJRo+PKzaNJbe4PUWKWftHaRRpOMZqTzuOJc97QTUeL0VIpWkd06TZ83edY+17qRO5q40Q0lKMaOWr/brZNCXbhx9+uCVSZasl1MSaZZqc6tptseXcvy6JfaLvMSlb13HMewdjI8kG42S8s4Jt4vxTzzHM/aByFgnbx3K1EULyxyVXV1ueNCXbhx56qCXSZRukVq3VCSEQi/tuvGw7KjY5hpGZlpLZJ+f+dUnuE0Qf06omM44jgksuH0xMuJYrasl7/qnnGEe/d6LR1UYIWT4i0zSpZvWlpSnZPvjggxq5OebC9CfJlK0kklmNqArMEl8NbamwQgz3nQiqynif6KaSsZlzfzuO9wwi0oplqxvcx0mKMLntSvJYzvNPOce6pYVa5etqI4Q0Ffn8ZMlU+mRM3jQl25UrV7bEsrIlhJABT1OyfeCBB1oC2RJCyp6mZNsOhBBS5uSWLSGEkNaDbAkhxEOQLSGEeAiyJYQQD0G2hBDiIciWEEI8BNkSQoiHIFtCCPGQSLZrn1sHAABdAtkCAHgA2QIAeADZAgB4ANkCAHgA2QIAeADZAgB4ANkCAHgA2QIAeKB0sh164U9qaPx9NfT6JQAYFOQzHXy2XZ/5olA+2SJagMEk+Gy7PvNFoXyyrf3DPP/mLAAMCOZz7frMF4VSy3bT6BYA6HOQbUFBtgCDBbItKMgWYLBAtgUF2QIMFsi2oCBbgMEC2RYUZAswWLQr2/Wb/qiuXL2mNm3e4uw3fPHFF872vCBbAOhr2pHt0Mh69dH161qk09M31HPrNzjHCT2TbWX4mJqpVlV19pijfV6d2zNS114UsmS7cfNxdUvOae54Q5+w/+KiqlYX1fmxUWe/sHHsolpYZgwAdI52ZPu3c5NaoobJqfQ/jOixbOfVnfmqmjkai7X/ZbuoFhYaZRmJGJECFIp2ZGtYWFhwttv0XLbnjk4Fwp1Su4dDuQ6CbG/NBcK9OFbfd3wuqHjndD+yBSgO5ZFtINUjs1V1Z3JfQ7tsS9/S0pLGLDnUiTqoFqVPquPdk/PRWHM8PX5PPK5anVVHArFXjs6qqiX5vOSR7fkx+Tqn3tocSnXj5jF1Xle7pj9sf2vOOrfa0kN8jNH49cW5aFxS4gDQHqWSbdrr7PHBJXlNliJOEVEkbBGpkWrieNIn47or21Et0lvHa7KVddiFi2q/1Z+2X+PreA1YV8eWxAGgfUolW70t8gsqV1d7VP3VydYak7FtV7XRcRI35Zohr2xjwcbitfv1+ECe8bmlydYan9gGgPYpn2yH96lz+mZZUpSmQpX+WnvTsg2PYd67HXLL1iwdHI+lW9evf+sgrFLjZYbEGGQL0HVKJ1vdVqtCowrWutQP+2rtzchWv47XhKP36vIygt6uVa5mnbVOpLIkYCRs/boXsgXwSzdlK793K79/K6I1XPvwo8zfx02jo7IV5CZXvFwQVrv6Mnt+Vs20UNnq7cRSgl6u8CFbXbHaN8pskUpf7dyCMbeobAF6QrcrW/kLs9u3b2vR3rx1S63buMk5bjlalm2/kiVbAOg/ui1bYfPWF9SHH11f9k96s2hZtqbKzMK1X69BtgCDRSdk6wMqWwDoa5BtQUG2AIMFsi0oyBZgsEC2BQXZAgwWyLag2LIFgMEA2RaQofH3o38YABgggs+26zNfFMon2xf+hHABBg35TAefbddnviiUTrYAAL0A2QIAeADZAgB4ANkCAHgA2QIAeADZAgB4ANkCAHgA2QIAeADZAgB4oLSylUdb7H/1VXXw4CF16BAAgBtxhLii1cfhGEor21f271cvv7JfrdvQ3gQCwKCzXm3bvkM7w92fj9LK9uDBg2oE0QJATsQZrva8lFa2cnngagcAcNGuM5AtAEAOkG2L5J64jaNq+NAJNXzmunruwj818lrapM+5DwAMHMi2RfJM3NDeA2r4/Kdq+O//cRP0yRjXvgAwWCDbFllu4rRoLzkEmyQYg3ABBp+eybYyfEzNVOfVuT0jte196tx8VVVnj6nKnil1x+rLS/KY3SRz4jaNZle0SYKxso/zWDXM/CwtLWmq81Nq9/CI13MGgNYpjGyPzFbVncl9DeOaoSiy1Wu0dUL9t1r9+gn11Ja9atW2/WrVzvFEf4Cs4TqOJYQ/fKpq5mh8XtJ2LthGtgD9QSFku3tyXle0rnHNUBjZyg0wS6SrX/+r+u2ucTV0IaPaDfZxHctUtLZo6/uRLYAvjp94W/3js/+mIv2u/YTey3ZyNrokbugLBGKPM5fQdgVsqr6wfSre72jjcTtJpmwTSwi/2bRTPXvqal1bA7KU4DhWeH6z6kjKeSRlK1cI0VJD7QdYcglCxO1qs48LAI0MjaxX75yddIpW2qXftZ/QY9mGH/bkB71RtuFaru4TidbkY/rM/rpCLqRsd6hn375S19ZAlmwzziMpW1e7novElYOrDQCWZ3jdBjV14XKdaGVb2l3jDb2vbI823gyrE0VCJnV9CRElx3aTTNkmlhF+d+AttWrnWGvLCE1WtiJRU61GP3hq1b/riqDddXKAMjKy4Xl1+dp1LVr5KtuucTa9l63IQKosSyh1fUmZ2H1FlW3DDbJ/qdUH/qKeGt2tntq6V63afSDRH5Byg6yZNVtbzOF+9XMhlX/ySsLVBgDLs3HzqK5o5aurP0khZCvbegmgJs46gSTGNfYVbxmh07/6ZapVW4gi1uRvI9jnHIo3njeDzFGymnW1AUBnKYxsBbm5I7LYNXQ0IdR4XMO2ddlclBtkQqf/qMFc9ptzdf9gim96Vedn1UytsrXnKFrvdrS53hcAOkPPZNvv5Jk4LdysCjfo46/HAMpBz2RrqioXrvFFI/fE8R/RAEAAlW2LtDtxAFAukG2LIFsAaAZk2yI8FgcAmoHH4rQID3wEgHzwwMe24FHmAJAHHmUOANBHIFsAAA8gWwAADyBbAAAPIFsAAA8gWwAADyBbAAAPIFsAAA8gWwAADyBbAAAPIFsAAA8gWwAADyBbAAAPIFsAAA8gW+hrhtZtUBvGz6gtb15V245/CAEyFzInMjeuOctieGSd2rVnr3ptfFyNHTgAATIXMicyN645ywuyhb5mYyCV0TevqbVjF9QfXplSv3+53MgcyFzInMjcuOYsCy3asXG146W9atuOXRAgcyFzInPjmrO8IFvoa7b8+apaO37BKZ4yI3Oi58YxZ1m8NjamdgZScUmnzMicyNy45iwvyBb6GrlspqJtROZE5sY1Z1nIZbNLNrBLz41rzvKCbKGvEaG4ZAPIttMgWyg1yDYdZNtZkC2UGmSbDrLtLMgWSg2yTQfZdhZkC6WmV7LdfvUrtbS0FHHn6pWo7+l9M+pGtaqq1QV1eN+kbjv8aVWPq37+mdpea+s2RZDt1hcPq0t3w3PX53/3A3XgxZ3OsZ1m64un1Fz1rrp0sDPvh2yh1PRCtkacN94Npfn0uwuhSD6dCbcTso36PYpW6LVstx78QC0G8zB3MpadtF2ytrsJsgXoIL5l+/Qbn6k7ItKaWA0i4Gr1K3X2jUCudbI1r8M+e59u00vZmorWFq1vkC1AB/EtW7N8YKraZLssJ9iyvVGrgu1lBl/0VLa6qr2tTmQsGZz4xFpe+OSUbosEefl21Ld4+XC0T3JZwsjcVNH6WLX3RbYAHaQosjVLBbZsjRDstVuf9Fy2OddnbSmGr4MfVEa+J29b8gxFa8s3ub/ZR8YgW4AOUrTKVtrtyvZsrd33eq1Q9MpWpBj/QLJla4nTFnHKMe2qNjpeIGtkC9BBfMs2eTPMkL5me0Wd/TwUge+lhJ7Kdpk1W1uc4dh2ZetoR7YAncO3bAVbrLJtLyHo7TrZBvI1N9U83yTrpWwFU7m6fhtBLw/UlhlCWeaQbeYywvLLC+2CbKHU9EK2glk2MNhVa1K20hb9nq3H9dtey1YIRWqtXxvBWje6qndvq7kclW28HR/PdYNMH5NlBIDO0ivZ9gNFkO0ggWyh1CDbdJBtZ0G2UGqQbTrItrMgWyg1IhT+8/BG+M/DOw+yhVLDY3Hc8FiczsJjcaD0bBh/hwc+WsgcmAc+yty45iyLXS/t0Q835IGPMdEDH4O5cc1ZXpAt9DWV4RG1fuw0jzK3kLmQOZG5cc1ZFmuGhtWu3S/xKHML/SjzYE5kblxzlhdkC31NZe2werayBhzI3LjmLAvmM51W5tMG2ULfowWxZq3zA1JKgrloRwzMZ4I259OAbAEAPIBsAQA8gGwBADyAbAEAPIBsAQA8gGwBADyAbAEAPIBsAQA8gGwBADwQyRYAALrJd+r/zHVjD33sbMEAAAAASUVORK5CYII=",w="/assets/run-the-bot.f048bed2.png";const j={},V=e("h1",{id:"intellij-gradle",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#intellij-gradle","aria-hidden":"true"},"#"),n(" IntelliJ + Gradle")],-1),q=n("This tutorial provides a beginner-friendly click by click guide to set up Javacord with Intellij and Gradle. If you are already familiar with IntelliJ and Gradle, you can just see the artifact locations at "),Z=n("Download / Installation"),B=n("."),C=d('File
-> New
-> Project
)Gradle
Next
com.github.yourname
)You can choose whatever you want
myfirstbot
)You can choose whatever you want
Next
Use auto-import
Next
Finish
build.gradle
file and open itbuild.gradle
file should now look like thisplugins {
+ id 'java'
+}
+
+group 'com.github.yourname'
+version '1.0-SNAPSHOT'
+
+sourceCompatibility = 1.8
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.javacord:javacord:$latest-version'
+}
+
src/main/java
folderExample code:
package com.github.yourname;
+
+import org.javacord.api.DiscordApi;
+import org.javacord.api.DiscordApiBuilder;
+
+public class Main {
+
+ public static void main(String[] args) {
+ // Insert your bot's token here
+ String token = "your token";
+
+ DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+
+ // Print the invite url of your bot
+ System.out.println("You can invite the bot by using the following url: " + api.createBotInvite());
+ }
+
+}
+
You can run your code by clicking on the small green arrow
',32);function I(x,W){const o=a("LatestVersion"),i=a("ClientOnly"),c=a("RouterLink");return p(),l("div",null,[V,s(i,null,{default:t(()=>[s(o)]),_:1}),e("p",null,[q,s(c,{to:"/wiki/getting-started/download-installation.html"},{default:t(()=>[Z]),_:1}),B]),C])}var N=r(j,[["render",I],["__file","intellij-gradle.html.vue"]]);export{N as default}; diff --git a/assets/intellij-gradle.html.9ec9aa86.js b/assets/intellij-gradle.html.9ec9aa86.js new file mode 100644 index 0000000..cdd7a73 --- /dev/null +++ b/assets/intellij-gradle.html.9ec9aa86.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6d1d378b","path":"/wiki/getting-started/setup/intellij-gradle.html","title":"IntelliJ + Gradle","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"\u{1F527} Setup","slug":"setup","children":[]},{"level":2,"title":"\u{1F3C3}\u200D\u2640\uFE0F Run the code","slug":"run-the-code","children":[]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/getting-started/setup/intellij-gradle.md"}');export{e as data}; diff --git a/assets/intellij-maven.html.74a49f6d.js b/assets/intellij-maven.html.74a49f6d.js new file mode 100644 index 0000000..1579679 --- /dev/null +++ b/assets/intellij-maven.html.74a49f6d.js @@ -0,0 +1,46 @@ +import{_ as i,r as t,o as l,c as u,b as s,w as e,a,d as n,e as r}from"./app.151ccb98.js";const d={},k=a("h1",{id:"intellij-maven",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#intellij-maven","aria-hidden":"true"},"#"),n(" IntelliJ + Maven")],-1),g=n("This tutorial provides a beginner-friendly click by click guide to set up Javacord with Intellij and Maven. If you are already familiar with IntelliJ and Maven, you can just see the artifact locations at "),h=n("Download / Installation"),m=n("."),v={class:"custom-container tip"},b=a("p",{class:"custom-container-title"},"Info",-1),f=n("We recommend to use "),_=n("Intellij + Gradle"),w=n(" unless you already have experience with one of the other IDEs or build managers."),y=r(`File
-> New
-> Project
)Maven
Next
com.github.yourname
)myfirstbot
)Next
Finish
Enable Auto-Import
<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>your.package.name</groupId>
+ <artifactId>myfirstbot</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.javacord</groupId>
+ <artifactId>javacord</artifactId>
+ <version>$latest-version</version>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+
+</project>
+
package com.github.yourname;
+
+import org.javacord.api.DiscordApi;
+import org.javacord.api.DiscordApiBuilder;
+
+public class Main {
+
+ public static void main(String[] args) {
+ // Insert your bot's token here
+ String token = "your token";
+
+ DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+
+ // Print the invite url of your bot
+ System.out.println("You can invite the bot by using the following url: " + api.createBotInvite());
+ }
+
+}
+
You can run your code by clicking on the small green arrow
Note: If you get the following error:
you have to change your language level to 1.8
@FunctionalInterface
+public interface MessageCreateListener {
+ void onMessageCreate(MessageCreateEvent event);
+}
+
api.addMessageCreateListener(new MessageCreateListener() {
+ @Override
+ public void onMessageCreate(MessageCreateEvent event) {
+ // Do stuff
+ event.pinMessage();
+ }
+});
+
In Java 8, this can be replaced with a lambda expression, which does exactly the same thing, but in a more readable fashion. The method parameter (in this case event
) is written in front of the ->
arrow, and the method body is written after it.
api.addMessageCreateListener(event -> {
+ // Do stuff
+ event.pinMessage();
+});
+
TIP
If the method has more than one parameter, it would look like this:
(param1, param2) -> { ... }
+
There's even a shorter version: If you are only executing one statement, you can get rid of the { }
brackets as well:
api.addMessageCreateListener(event -> event.pinMessage());
+
api.addMessageCreateListener(MessageEvent::pinMessage);
+
Creating listeners is extremely easy in Javacord. You can either use Java 8's lambda expressions to register listeners inline or just create a new class for them, if an inline listener would get too messy.
api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+});
+
api.addListener(new MyListener());
+
and
public class MyListener implements MessageCreateListener {
+
+ @Override
+ public void onMessageCreate(MessageCreateEvent event) {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ }
+
+}
+
Sometimes it might be useful to add listeners before calling the DiscordApiBuilder#login()
method.
DiscordApi api = new DiscordApiBuilder()
+ // An inline listener
+ .addMessageCreateListener(event -> {
+ Message message = event.getMessage();
+ if (message.getContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ })
+ .addServerBecomesAvailableListener(event -> {
+ System.out.println("Loaded " + event.getServer().getName());
+ })
+ // A listener in their own class
+ .addListener(new MyListener())
+ // Alternative syntax that can be used for classes that require a DiscordApi parameter in their constructor
+ .addListener(MyListener::new)
+ .setToken("top secret")
+ .setWaitForServersOnStartup(false)
+ .login()
+ .join();
+
Note: In most cases, it's enough to add listeners after logging in
Another cool feature is the ability to attach listeners directly to objects. An example where this can be useful is, for example, reacting to reactions. The following code would delete the message if someone adds a \u{1F44E} reaction.
message.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("\u{1F44E}")) {
+ event.deleteMessage();
+ }
+}).removeAfter(30, TimeUnit.MINUTES);
+
Seems like the bot is very sensitive to criticism.
There are two ways to remove a listener:
Every time you register a listener, a ListenerManager
is returned which can be used to unregister the listener:
ListenerManager<MessageCreateListener> listenerManager = api.addMessageCreateListener(event -> {
+ // Do stuff
+});
+
+listenerManager.remove();
+
This manager also has some utility methods. You can, for example, remove a listener after a given time, which can be useful for object listeners:
message.addReactionAddListener(event -> {
+ // Do stuff
+}).removeAfter(30, TimeUnit.MINUTES);
+
removeListener(...)
methodYou can remove any listener using the removeListener(...)
method:
MyListener listener = new MyListener();
+api.addListener(listener);
+// ...
+api.removeListener(listener);
+
Javacord's fallback logger is a simple Log4j logger which always logs INFO
level and higher. It allows you to enable DEBUG
and TRACE
logging manually. As log levels are hierarchical, enabling TRACE
will also implicitly enable DEBUG
, and disabling DEBUG
will also implicitly disable TRACE
.
// Enable debug logging
+FallbackLoggerConfiguration.setDebug(true);
+
+// Enable trace logging
+FallbackLoggerConfiguration.setTrace(true);
+
Changing the log level of the fallback logger only affects newly created loggers. Pre-existing loggers will not have their log level changed. So if you want to configure the fallback logger, you should do this as one of the first actions in your bot code. If you want to change log levels during runtime, you should use a proper logging framework like Log4j 2 Core or another library that supports this.
All fallback logger messages are printed to the standard output stream (System.out
) and thus usually to your console. If you want to log to a file, database, or anything else, you should consider using a proper logging framework which allows you to configure this behavior.
This is how a log line from the fallback logger will look like:
<time with date ><level><logger name, usually the logging class > <message > <the thread context, here the shard number>
+2018-08-03 20:00:06.080+0200 DEBUG org.javacord.core.util.gateway.DiscordWebSocketAdapter Received HELLO packet {shard=0}
+
Adding a logging framework of your choice is very straightforward. You can just add it as a dependency, and it will be detected by Log4j automatically. The following example adds Log4j 2 using Gradle:
dependencies { runtimeOnly 'org.apache.logging.log4j:log4j-core:2.17.0' }
+
You can also use an SLF4J compatible logging framework using log4j-to-slf4j
. The following example adds Logback Classic using Gradle:
dependencies {
+ runtimeOnly 'org.apache.logging.log4j:log4j-to-slf4j:2.17.0'
+ runtimeOnly 'ch.qos.logback:logback-classic:1.2.3'
+}
+
The MessageBuilder
class is a more powerful alternative to the TextChannel#sendMessage(...)
method.
It can be used to construct more complex messages and supports some additional features that are not possible with a simple TextChannel#sendMessage(...)
call.
The following code
new MessageBuilder()
+ .append("Look at these ")
+ .append("awesome", MessageDecoration.BOLD, MessageDecoration.UNDERLINE)
+ .append(" animal pictures! \u{1F603}")
+ .appendCode("java", "System.out.println(\\"Sweet!\\");")
+ .addAttachment(new File("C:/Users/Bastian/Pictures/kitten.jpg"))
+ .addAttachment(new File("C:/Users/Bastian/Pictures/puppy.jpg"))
+ .setEmbed(new EmbedBuilder()
+ .setTitle("WOW")
+ .setDescription("Really cool pictures!")
+ .setColor(Color.ORANGE))
+ .send(channel);
+
will be displayed like this:
The allowed mentions object lets you control what should be mentioned (pinged) in a message if it contains mentions.
The following code will ping:
And will not ping:
AllowedMentions allowedMentions = new AllowedMentionsBuilder()
+ .addUser(user0.getId())
+ .setMentionRoles(true)
+ .setMentionEveryoneAndHere(false)
+ .build();
+
+ new MessageBuilder()
+ .setAllowedMentions(allowedMentions)
+ .append(user0.getMentionTag())
+ .append(user1.getMentionTag())
+ .append(role.getMentionTag())
+ .append(role2.getMentionTag())
+ .append("@everyone")
+ .send(channel);
+
If you add a user to the mentions object and set setMentionUsers(true)
it will ping every mentioned user. The same applies for setMentionRoles(true)
The Optional class is widely used in Javacord. Basically, every method that might return a null
value will return an Optional in Javacord instead. Optionals help you to avoid NullPointerExceptions
and make it very clear if a method may not have a result. Here's a small example:
User user = api.getCachedUserById(123L);
+if (user != null) {
+ user.sendMessage("Hi!");
+}
+
api.getCachedUserById(123L).ifPresent(user ->
+ user.sendMessage("Hi!")
+);
+
You can imagine an Optional
like a box \u{1F4E6} that may or may not contain a value. Before accessing this value, you have to "unpack" this box first.
The get
method returns the value of the Optional or throws a NoSuchElementException
if it does not contain a value.
TextChannel channel = api.getTextChannelById(123L).get();
+channel.sendMessage("Hi");
+
DANGER
You should never use this method blindly but only if you are 100% sure the optional contains a value.
Every time you use this method carelessly, a kitten dies \u{1F640}! True story.
The isPresent
methods checks, if the Optional contains a value.
Optional<TextChannel> channel = api.getTextChannelById(123L);
+if (channel.isPresent()) {
+ // A text channel with the id 123 exists. It's safe to call #get() now
+ channel.get().sendMessage("Hi");
+}
+
The orElse
methods returns the value of the Optional if it is present. Otherwise, it returns the given default value.
// The user may not have a nickname on the given server.
+// In this case, we use the user's "regular" name.
+String displayName = user.getNickname(server).orElse(user.getName());
+
The example above is (mostly) equivalent to the example below but much more concise.
String displayName = "";
+Optional<String> nickname = user.getNickname(server);
+if (nickname.isPresent()) {
+ displayName = nickname.get();
+} else {
+ displayName = user.getName();
+}
+
TIP
In this case you can just use user.getDisplayName(server)
instead.
api.getTextChannelById(123L).ifPresent(channel -> {
+ channel.sendMessage("Hi!");
+});
+
The example above is (mostly) equivalent to the example below but more concise.
Optional<TextChannel> channel = api.getTextChannelById(123L);
+if (channel.isPresent()) {
+ channel.get().sendMessage("Hi!");
+}
+
The filter
method filters the Optional for a given criteria.
Optional<User> botUser = api.getCachedUserById(123L).filter(User::isBot);
+
The example above is equivalent to the example below but more concise.
Optional<User> user = api.getCachedUserById(123L);
+Optional<User> botUser;
+if (user.isPresent() && user.get().isBot()) {
+ botUser = user;
+} else {
+ botUser = Optional.empty();
+}
+
The map
method "converts" the type of an Optional. This is useful, if the type of an Optional does not contain the final value you need.
The following example gets the name of the bots current activity (the "Playing xyz" status) or "None" if the bot has no current activity.
String activityName = api.getYourself().getActivity().map(Activity::getName).orElse("None");
+
For better understanding, here's the exact same code but with the types as comments:
String activityName = api.getYourself() // User
+ .getActivity() // Optional<Activity>
+ .map(Activity::getName) // Optional<String>
+ .orElse("None"); // String
+
The flatMap
method if very similar to the map
methods. It is used to map values that itself are Optionals to prevent Optional nesting (a "box in a box").
String activityName = api.getCachedUserById(123L) // Optional<User>
+ .flatMap(User::getActivity) // Optional<Activity>
+ .map(Activity::getName) // Optional<String>
+ .orElse("None"); // String
+
Without flatMap
, the code would look like this:
String activityName = api.getCachedUserById(123L) // Optional<User>
+ .map(User::getActivity) // Optional<Optional<Activity>>
+ .filter(Optional::isPresent) // Optional<Optional<Activity>>
+ .map(Optional::get) // Optional<Activity>
+ .map(Activity::getName) // Optional<String>
+ .orElse("None"); // String
+
Interactions are a means of accepting user input through Discord. They have been introduced to provide a more standardized, controlled way for commands than parsing messages. They can even be used with applications that do not provide a bot user.
The "old" way of doing commands was done through parsed text messages, like !ping
, !userinfo James
or !mute James 100s
. While such commands are easy in theory, they come with several problems, such as:
INFO
Creation of interactions is detailed on the pages linked in the previous section.
Unlike chat message commands, interactions and interaction commands need to be registered with Discord. In order for a bot's interactions to be available in a server, the bot must be added to the server with the applications.commands
OAUTH scope. The scope is included in links created by DiscordApi#createInviteLink
. If your bot is older, it may need to be invited with the new link to add the scope. It is not necessary to remove the bot from the server to do this.
While being more complicated to utilize, interactions have many benefits over pure text commands.
',6),k=e("li",null,"Better Validation: Commands can not be sent with parameters of the wrong type or missing required parameters",-1),C=e("li",null,"No conflicts: Interactions are separated by bot and only sent to the proper bot",-1),N=e("li",null,'"Privacy": If no public response is sent by the bot, the exchange is invisible to other chat participants',-1),D=e("li",null,"Integration: Interactions are integrated into the client's user interface",-1),T=t("Conversations: "),L=t("Message components"),A=t(" can be used in replies to interactions, allowing for nested dialogues."),B=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"WARNING"),e("p",null,"If a bot replies to a slash command with a public message, the command used, including all parameters, is visible to other users.")],-1),M=e("h2",{id:"applications-vs-bots",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#applications-vs-bots","aria-hidden":"true"},"#"),t(" \u{1F916} Applications vs. Bots")],-1),V=t("Interactions can used by any application, not only bots. While interactions can also be handled through webhooks, Javacord only offers support for dealing with them through the gateway. See the "),W={href:"https://discord.com/developers/docs/interactions/receiving-and-responding",target:"_blank",rel:"noopener noreferrer"},j=t("Discord Documentation"),R=t(" for more information."),q=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"WARNING"),e("p",null,"The methods of handling interactions can not be mixed. If you register a webhook for your interaction commands, the bot will no longer receive any interaction events.")],-1),E=e("h2",{id:"see-also",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#see-also","aria-hidden":"true"},"#"),t(" \u{1F50D} See also")],-1),J={href:"https://discord.com/developers/docs/interactions/application-commands",target:"_blank",rel:"noopener noreferrer"},S=t("Application Commands (Discord Documentation)"),z={href:"https://discord.com/developers/docs/interactions/message-components",target:"_blank",rel:"noopener noreferrer"},G=t("Message Components (Discord Documentation)");function O(U,F){const a=i("RouterLink"),n=i("ExternalLinkIcon");return d(),l("div",null,[u,e("p",null,[_,o(a,{to:"/wiki/basic-tutorials/interactions/commands.html"},{default:s(()=>[f]),_:1}),b]),e("p",null,[e("a",g,[v,o(n)]),y]),e("p",null,[o(a,{to:"/wiki/basic-tutorials/interactions/components.html"},{default:s(()=>[w]),_:1}),x]),I,e("ul",null,[k,C,N,D,e("li",null,[T,o(a,{to:"/wiki/basic-tutorials/interactions/components.html"},{default:s(()=>[L]),_:1}),A])]),B,M,e("p",null,[V,e("a",W,[j,o(n)]),R]),q,E,e("ul",null,[e("li",null,[e("a",J,[S,o(n)])]),e("li",null,[e("a",z,[G,o(n)])])])])}var P=c(p,[["render",O],["__file","overview.html.vue"]]);export{P as default}; diff --git a/assets/overview.html.d664223d.js b/assets/overview.html.d664223d.js new file mode 100644 index 0000000..9e7fc05 --- /dev/null +++ b/assets/overview.html.d664223d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-16fe8d71","path":"/wiki/basic-tutorials/interactions/overview.html","title":"Interactions","lang":"en-US","frontmatter":{"keywords":["interaction","slash command","command","context menu","autocomplete"]},"excerpt":"","headers":[{"level":2,"title":"\u{1F4AC} Message Commands","slug":"message-commands","children":[]},{"level":2,"title":"\u2709\uFE0F Interaction Types","slug":"interaction-types","children":[]},{"level":2,"title":"\u267B\uFE0F Lifecycle","slug":"lifecycle","children":[]},{"level":2,"title":"\u{1F4C8} Advantages","slug":"advantages","children":[]},{"level":2,"title":"\u{1F916} Applications vs. Bots","slug":"applications-vs-bots","children":[]},{"level":2,"title":"\u{1F50D} See also","slug":"see-also","children":[]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/basic-tutorials/interactions/overview.md"}');export{e as data}; diff --git a/assets/performance-tweaks.html.1ed90e55.js b/assets/performance-tweaks.html.1ed90e55.js new file mode 100644 index 0000000..1ffb8ea --- /dev/null +++ b/assets/performance-tweaks.html.1ed90e55.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-2037d84f","path":"/wiki/advanced-topics/performance-tweaks.html","title":"Performance Tweaks","lang":"en-US","frontmatter":{"keywords":["performance","tweaks","startup wait","message cache","tuning"]},"excerpt":"","headers":[{"level":2,"title":"\u2702\uFE0F Disabling Startup Wait","slug":"disabling-startup-wait","children":[]},{"level":2,"title":"\u2699\uFE0F Fine Tuning the Message Cache","slug":"fine-tuning-the-message-cache","children":[]},{"level":2,"title":"\u{1F48E} Using the Updater classes","slug":"using-the-updater-classes","children":[{"level":3,"title":"Example","slug":"example","children":[]}]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/advanced-topics/performance-tweaks.md"}');export{e as data}; diff --git a/assets/performance-tweaks.html.909e9a4b.js b/assets/performance-tweaks.html.909e9a4b.js new file mode 100644 index 0000000..254dd35 --- /dev/null +++ b/assets/performance-tweaks.html.909e9a4b.js @@ -0,0 +1,25 @@ +import{_ as n,o as a,c as s,e}from"./app.151ccb98.js";const t={},p=e(`By default, Javacord waits for all servers and members to be loaded on startup. You can disable this behavior in the DiscordApiBuilder
before logging in:
new DiscordApiBuilder()
+ .setToken("abc")
+ .setWaitForServersOnStartup(false)
+ .login()
+ .thenAccept(api -> {
+ // Do something
+ }).exceptionally(ExceptionLogger.get());
+
Depending on the size of your bot, this can significantly speed up the login process. This comes with one downside however: The api.getServers()
collection is empty directly after logging in. You will receive ServerBecomesAvailableEvent
s for every server which finished loading.
In order to reduce memory usage, you can completely disable the message cache or reduce the number of cached messages. By default, Javacord caches up to 50 messages per channel and removes messages from the cache which are older than 12 hours. You can lower this limit by using DiscordApi#setMessageCacheSize(Capacity, StorageTimeInSeconds)
.
// Cache a maximum of 10 messages per channel for and remove messages older than 1 hour
+api.setMessageCacheSize(10, 60*60);
+
You can even set this limit on a per-channel basis:
TextChannel channel = ...;
+channel.getMessageCache().setCapacity(10);
+channel.getMessageCache().setStorageTimeInSeconds(60*60);
+
If you update several settings of an entity (server, channel, ...) at once, you should use the updater for this entity instead of the updateXyz(...)
methods.
// Sends 1 request to Discord
+ServerTextChannel channel = ...;
+new ServerTextChannelUpdater(channel)
+ .setName("example-channel")
+ .setTopic("This is an example channel")
+ .setNsfwFlag(true)
+ .update();
+
instead of
// Sends 3 requests to Discord
+ServerTextChannel channel = ...;
+channel.updateName("example-channel");
+channel.updateTopic("This is an example channel");
+channel.updateNsfwFlag(true);
+
The following example will connect the bot to the voice channel of the user that typed !music
in the chat:
ServerVoiceChannel channel = ...;
+channel.connect().thenAccept(audioConnection -> {
+ // Do stuff
+}).exceptionally(e -> {
+ // Failed to connect to voice channel (no permissions?)
+ e.printStackTrace();
+ return null;
+});
+
To use it with Javacord, you have to add it as a dependency to your project (e.g., with Gradle or Maven) and create a Javacord audio source like this:
public class LavaplayerAudioSource extends AudioSourceBase {
+
+ private final AudioPlayer audioPlayer;
+ private AudioFrame lastFrame;
+
+ /**
+ * Creates a new lavaplayer audio source.
+ *
+ * @param api A discord api instance.
+ * @param audioPlayer An audio player from Lavaplayer.
+ */
+ public LavaplayerAudioSource(DiscordApi api, AudioPlayer audioPlayer) {
+ super(api);
+ this.audioPlayer = audioPlayer;
+ }
+
+ @Override
+ public byte[] getNextFrame() {
+ if (lastFrame == null) {
+ return null;
+ }
+ return applyTransformers(lastFrame.getData());
+ }
+
+ @Override
+ public boolean hasFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean hasNextFrame() {
+ lastFrame = audioPlayer.provide();
+ return lastFrame != null;
+ }
+
+ @Override
+ public AudioSource copy() {
+ return new LavaplayerAudioSource(getApi(), audioPlayer);
+ }
+}
+
With this audio source, you can now start using Lavaplayer, e.g. to play a YouTube video:
// Create a player manager
+AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
+playerManager.registerSourceManager(new YoutubeAudioSourceManager());
+AudioPlayer player = playerManager.createPlayer();
+
+// Create an audio source and add it to the audio connection's queue
+AudioSource source = new LavaplayerAudioSource(api, player);
+audioConnection.setAudioSource(source);
+
+// You can now use the AudioPlayer like you would normally do with Lavaplayer, e.g.,
+playerManager.loadItem("https://www.youtube.com/watch?v=NvS351QKFV4", new AudioLoadResultHandler() {
+ @Override
+ public void trackLoaded(AudioTrack track) {
+ player.playTrack(track);
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist playlist) {
+ for (AudioTrack track : playlist.getTracks()) {
+ player.playTrack(track);
+ }
+ }
+
+ @Override
+ public void noMatches() {
+ // Notify the user that we've got nothing
+ }
+
+ @Override
+ public void loadFailed(FriendlyException throwable) {
+ // Notify the user that everything exploded
+ }
+});
+
There are basically two kinds of proxies: HTTP proxies and SOCKS proxies. Both may or may not support or require authentication depending on version, capabilities, and configuration. Due to the underlying libraries used, currently, Javacord fully supports HTTP proxies and partially supports SOCKS proxies.
Javacord uses HTTPS connections to communicate with the Discord REST API and a WSS connection to communicate with the Discord WebSocket endpoint. Both these protocols are secure protocols and thus do not honor settings for HTTP connections, only settings for HTTPS connections.
If you did not explicitly set a proxy in the DiscordApiBuilder
and did not set a system default ProxySelector
, the default proxy selector of the JRE is used. This proxy selector honors, amongst others, the relevant standard system properties https.proxyHost
, https.proxyPort
, socksProxyHost
, socksProxyPort
, and socksProxyVersion
. Use the former two to configure an HTTP proxy, or the latter three to configure a SOCKS proxy, although you will not need socksProxyVersion
, as SOCKS4 is currently not supported.
You can use java.net.ProxySelector.setDefault(ProxySelector)
to set a system default proxy selector that replaces the default one. In its implementation, you can dynamically determine which proxy to use for each connection.
Using the method DiscordApiBuilder.setProxy(Proxy)
you can set a proxy instance directly in the DiscordApiBuilder
that is solely used for Javacord connections and does not affect the unrelated code running in the JVM.
Using the method DiscordApiBuilder.setProxySelector(ProxySelector)
you can set a proxy selector instance directly in the DiscordApiBuilder
that is solely used for Javacord connections and does not affect the remaining code running in the JVM. In its implementation, you can dynamically determine which proxy to use for each connection.
You can use java.net.Authenticator.setDefault(Authenticator)
to set a system default authenticator that is used to provide username and password pairs for connections. This authenticator is only used if the proxy supports the Basic
authentication scheme. If you need to support any other authentication scheme, use an explicitly configured authenticator. The java.net.Authenticator
interface is too inflexible to support this.
Using the method DiscordApiBuilder.setProxyAuthenticator(Authenticator)
, you can set a custom authenticator that is much more powerful than the java.net.Authenticator
. You get much more information about the connection to be established, and you can return any HTTP header that is necessary for a successful authentication. This should cover all sorts of available authentication mechanisms.
HTTP proxies are fully supported.
SOCKS 4 is currently not supported.
The WebSocket library we use does not support SOCKS proxies at all, and the HTTP library we use has a bug that prevents SOCKS 4 to be used. Additionally, you would need to use at least Java 9 or a separate socket factory supporting SOCKS 4, as the JRE implementation is not working in Java 8 and got fixed only in Java 9+.
SOCKS 4a is currently only partially supported.
The WebSocket library we use does not support SOCKS proxies at all, so it could be used for the REST connections only. Additionally, you would need to use a separate socket factory supporting SOCKS 4a, as the JRE implementation is not capable of doing SOCKS 4a, only SOCKS 4 and SOCKS 5 are supported at the time of creation of this wiki article.
SOCKS 5 is currently only partially supported.
The WebSocket library we use does not support SOCKS proxies at all, so it could be used for the REST connections only.
',31),s=[r];function n(c,d){return t(),o("div",null,s)}var l=e(i,[["render",n],["__file","proxies.html.vue"]]);export{l as default}; diff --git a/assets/ratelimits.html.4f60547f.js b/assets/ratelimits.html.4f60547f.js new file mode 100644 index 0000000..1438762 --- /dev/null +++ b/assets/ratelimits.html.4f60547f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-fc3b23ca","path":"/wiki/advanced-topics/ratelimits.html","title":"Ratelimits","lang":"en-US","frontmatter":{"keywords":["ratelimits"]},"excerpt":"","headers":[{"level":2,"title":"\u2757 The Most Important Ratelimits","slug":"the-most-important-ratelimits","children":[]},{"level":2,"title":"\u{1F4AA} Dealing with Ratelimits","slug":"dealing-with-ratelimits","children":[{"level":3,"title":"Example","slug":"example","children":[]}]},{"level":2,"title":"\u274C Can I disable ratelimits?","slug":"can-i-disable-ratelimits","children":[]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/advanced-topics/ratelimits.md"}');export{e as data}; diff --git a/assets/ratelimits.html.a990bfd1.js b/assets/ratelimits.html.a990bfd1.js new file mode 100644 index 0000000..ba83d7c --- /dev/null +++ b/assets/ratelimits.html.a990bfd1.js @@ -0,0 +1,14 @@ +import{_ as n,o as a,c as s,e as t}from"./app.151ccb98.js";const e={},i=t(`Ratelimits is a Discord restriction which prevents you from performing actions in a very fast rate. Most ratelimits are on a per-channel or a per-server basis.
Action | Ratelimit | Type |
---|---|---|
Send Messages | 5 / 5s | per channel |
Delete Messages | 5 / 1s | per channel |
Add/Remove Reactions | 1 / 0.25s | per channel |
Edit Server Members | 10 / 10s | per server |
Edit Member Nickname | 1 / 1s | per server |
Edit Bot Username | 2 / 1h | per account |
Update Channels | 2 / 10m | per account |
All Actions Combined | 50 / 1s | per account |
Usually Javacord takes care about these limitations for you. As a user, there's nothing you have to do, but you should at least know that ratelimits exist.
The following code
// Who even needs loops?
+channel.sendMessage("Ratelimit Example #1");
+channel.sendMessage("Ratelimit Example #2");
+channel.sendMessage("Ratelimit Example #3");
+channel.sendMessage("Ratelimit Example #4");
+channel.sendMessage("Ratelimit Example #5");
+channel.sendMessage("Ratelimit Example #6");
+channel.sendMessage("Ratelimit Example #7");
+channel.sendMessage("Ratelimit Example #8");
+channel.sendMessage("Ratelimit Example #9");
+channel.sendMessage("Ratelimit Example #10");
+channel.sendMessage("Ratelimit Example #11");
+channel.sendMessage("Ratelimit Example #12");
+
would look like this in the client:
You can clearly see the delay between every 5 sent messages.
No. Ratelimits are a limitation from Discord itself, which you cannot circumvent.
`,14),p=[i];function o(c,l){return a(),s("div",null,p)}var d=n(e,[["render",o],["__file","ratelimits.html.vue"]]);export{d as default}; diff --git a/assets/respond_with_modal.9601f2a7.png b/assets/respond_with_modal.9601f2a7.png new file mode 100644 index 0000000..e1ce916 Binary files /dev/null and b/assets/respond_with_modal.9601f2a7.png differ diff --git a/assets/responding.html.3c5c7e21.js b/assets/responding.html.3c5c7e21.js new file mode 100644 index 0000000..c7aa19f --- /dev/null +++ b/assets/responding.html.3c5c7e21.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-00728006","path":"/wiki/basic-tutorials/interactions/responding.html","title":"Responding to interactions","lang":"en-US","frontmatter":{"keywords":["interaction responding","modal","autocomplete"]},"excerpt":"","headers":[{"level":2,"title":"\u{1F4AC} Responding immediately after receiving an interaction.","slug":"responding-immediately-after-receiving-an-interaction","children":[]},{"level":2,"title":"\u{1F4AC} Responding after some time when receiving an interaction.","slug":"responding-after-some-time-when-receiving-an-interaction","children":[{"level":3,"title":"Sending followup messages","slug":"sending-followup-messages","children":[]}]},{"level":2,"title":"Responding with a Modal","slug":"responding-with-a-modal","children":[]},{"level":2,"title":"\u{1F4AC} SlashCommand interaction only response methods","slug":"slashcommand-interaction-only-response-methods","children":[{"level":3,"title":"How to know what slash command was invoked?","slug":"how-to-know-what-slash-command-was-invoked","children":[]},{"level":3,"title":"Respond to an AutoComplete interaction triggered from a SlashCommand","slug":"respond-to-an-autocomplete-interaction-triggered-from-a-slashcommand","children":[]}]},{"level":2,"title":"\u{1F4AC} Message Component interaction only response methods","slug":"message-component-interaction-only-response-methods","children":[{"level":3,"title":"A more complete example of how to respond to Component interactions","slug":"a-more-complete-example-of-how-to-respond-to-component-interactions","children":[]}]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/basic-tutorials/interactions/responding.md"}');export{e as data}; diff --git a/assets/responding.html.a1a19125.js b/assets/responding.html.a1a19125.js new file mode 100644 index 0000000..482f52d --- /dev/null +++ b/assets/responding.html.a1a19125.js @@ -0,0 +1,77 @@ +import{_ as t,r as e,o,c as p,a as c,b as i,w as u,e as s,d as n}from"./app.151ccb98.js";var l="/assets/respond_with_modal.9601f2a7.png";const r={},d=s(`There are many ways to respond to interactions and some are only available for certain interactions. The following will be usable for every interaction.
event.getInteraction()
+ .createImmediateResponder()
+ .setContent("YOUR_RESPONSE")
+ .respond();
+
INFO
Note that you have to respond withing 3 seconds, or the command will fail. If you need longer than 3 seconds you have to respond with respondLater()
which allows you to respond within 15 minutes.
Because of this time limitation, sending any files when creating an immediate response is not possible. If you want a file to be embedded either use respondLater
or include a web link in the message content. Depending on the media type of the link and the server configuration, Discord will then display an appropriate embed for the file.
When you want to respond ephemerally, you can use the setFlags
method. Your new responder would look like the following:
event.getInteraction()
+ .createImmediateResponder()
+ .setContent("YOUR_RESPONSE")
+ .setFlags(MessageFlag.EPHEMERAL)
+ .respond();
+
If your computations takes longer than the 3 seconds limit, you can respond later and the Discord Client will show that your bot is thinking until you respond.
event.getInteraction()
+ .respondLater()
+ .thenAccept(interactionOriginalResponseUpdater -> {
+ interactionOriginalResponseUpdater.setContent("Update message after some time").update();
+ });
+
You can respond ephemerally when responding later too. For that you have pass a true
boolean to the respondLater
method.
event.getInteraction()
+ .respondLater(true)
+ .thenAccept(interactionOriginalResponseUpdater -> {
+ interactionOriginalResponseUpdater.setContent("Update message after some time").update();
+ });
+
Followup messages can be sent within 15 minutes after the command has been invoked. You can send as many followup messages as you want.
api.addSlashCommandCreateListener(event -> {
+ SlashCommandInteraction slashCommandInteraction = event.getSlashCommandInteraction();
+ slashCommandInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> {
+ interactionOriginalResponseUpdater.setContent("You will receive the answer in a few minutes!").update();
+
+ // time < 15 minutes
+
+ slashCommandInteraction.createFollowupMessageBuilder()
+ .setContent("Thank you for your patience, it took a while but the answer to the universe is 42")
+ .send();
+ });
+});
+
A modal is a popup dialog which can be shown when responding to an interaction. It focuses the users to explicitly fill out this form to continue with the workflow. Currently, only the TextInput
(SelectMenu
has been seen working too, but is not yet officially supported) is supported.
api.addMessageComponentCreateListener(event -> {
+ event.getInteraction().respondWithModal("modalId","Modal Title",
+ ActionRow.of(TextInput.create(TextInputStyle.SHORT, "text_input_id", "This is a Text Input Field")));
+});
+
Which results in
For example, you have created a slash command with the name "settings" and a subcommand "color". If you want to check if exactly this command has been used, you can check it as follows:
api.addSlashCommandCreateListener(event -> {
+ SlashCommandInteraction interaction = event.getSlashCommandInteraction();
+ if (interaction.getFullCommandName().equals("settings color")) {
+ //Code if command matches the full name
+ }
+});
+
api.addAutocompleteCreateListener(event -> {
+ event.getAutocompleteInteraction()
+ .respondWithChoices(Arrays.asList(
+ SlashCommandOptionChoice.create("one", 1),
+ SlashCommandOptionChoice.create("two", 2))
+ );
+});
+
When dealing with message components, you don't necessarily have to respond or update a message. You can simply acknowledge the interaction and let the user know that the task is done.
api.addMessageComponentCreateListener(event -> {
+ event.getMessageComponentInteraction().acknowledge();
+});
+
api.addMessageComponentCreateListener(event -> {
+ MessageComponentInteraction messageComponentInteraction = event.getMessageComponentInteraction();
+ String customId = messageComponentInteraction.getCustomId();
+
+ switch (customId) {
+ case "success":
+ messageComponentInteraction.createImmediateResponder()
+ .setContent("You clicked a button!")
+ .respond();
+ break;
+ case "danger":
+ messageComponentInteraction.getMessage().ifPresent(Message::delete);
+ break;
+ case "secondary":
+ messageComponentInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> {
+ //Code to respond after 5 minutes
+ });
+ break;
+ case "options":
+ messageComponentInteraction.createImmediateResponder()
+ .setContent("You selected an option in a select menu!")
+ .respond();
+ break;
+ }
+});
+
If you took the time to write a bot, at some point you'll also want to run it, either for use in production or for debugging from the IDE.
While developing your bot, you will want to run your bot directly from the IDE in order to quickly test changes and new features. For this, create a Run/Debug Configuration in your IDE of choice with your bot's main class. Remember to also add any necessary parameters and environment variables.
A working Run/Debug configuration will also enable you to run your bot with a debugger. A debugger is often considered a developer's most important tool, so make sure to familiarize yourself with the debugging integration for your IDE of choice.
This assumes your project is set up correctly, preferably with Gradle, can be built without errors, and does not yet have any run/debug configurations.
1. Locate and click the Add Configuration...
button in the top bar next to the start button.
2. In the newly opened window, click the +
button in the top left and select Application
3. Give a name for your configuration and select the module to use the classpath of (usually yourproject.main
).
4. Select your Main class. Use the ...
button to search for it or provide the fully qualified name. If it can not be found, you most likely selected the wrong module in step 3.
5. Optional: Set command line arguments and environment variables. For the environment variables, use the button to the right of the input field for a more convenient input window.
6. Click Apply
to finalize the configuration, then OK
to close the window.
7. Select your configuration in the drop-down menu and run or debug it with the buttons to the right.
This assumes your project is set up correctly, can be built without errors, and does not yet have any run/debug configurations.
1. In the menu bar, click "Run" then "Run Configurations...".
2. In the newly opened window, select "Java Application" on the left side, then click the leftmost button in the row above the tree view. A new configuration will appear.
3. Give a name to your configuration.
4. Set the project and the main class. To easily select it, use the "Browse..." and "Search..." buttons.
5. Optional: Set command line (and VM) arguments as well as environment variables in their respective tabs.
6. Click Apply
to save your configuration, then Close
to close the window.
7. Run or debug your bot via the Buttons in the top row, the Run
menu, or the shortcuts Ctrl+F11 for running and F11 for debugging.
Running from the IDE is only recommended during development and strongly discouraged for production use. Generally, you'll want your build tool to create a convenient distribution format for you to use.
Now you can execute the distZip
or distTar
task with Gradle. The task will create a distribution and package it in an archive file that will be placed in the build/distributions
directory. Extract the content of those files on your server or whichever machine you want to run your bot on.
The distribution usually only contains the directories bin
and lib
. From the distribution directory, run either bin/yourbot
or bin/yourbot.bat
, depending on whether you're running the bot on Linux / macOS or windows.
<project>
+ ...
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>appassembler-maven-plugin</artifactId>
+ <version>1.10</version>
+ <configuration>
+ <programs>
+ <program>
+ <mainClass>org.javacord.examplebot.Main</mainClass>
+ <id>examplebot</id>
+ </program>
+ </programs>
+ </configuration>
+ <executions>
+ <execution>
+ <id>create-distribution</id>
+ <phase>package</phase>
+ <goals>
+ <goal>assemble</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>3.3.0</version>
+ <configuration>
+ <descriptors>
+ <!-- This must match the location of the descriptor -->
+ <descriptor>src/assembly/distribution.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>create-archive</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
Sadly, none of the built-in assembly descriptors match our use case, so we'll put our custom one into src/assembly/distribution.xml
:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <id>distribution</id>
+ <formats>
+ <!-- See https://maven.apache.org/plugins/maven-assembly-plugin/assembly.html for supported formats -->
+ <format>tar.gz</format>
+ <format>tar.bz2</format>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <!-- This will also include your project readme, license and similar files-->
+ <directory>\${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <includes>
+ <include>README*</include>
+ <include>LICENSE*</include>
+ <include>NOTICE*</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <!-- Change this if you reconfigured the appassembler output directory -->
+ <directory>\${project.build.directory}/appassembler</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
+
Now when you execute mvn package
, a distribution with start scripts for Windows and Linux/macOS will be generated which is then packaged into archive files for every format you specified in the assembly descriptor. You can find the raw distribution (without readme and license files) in target/appassembler
and the archive files directly in target
.
After creating your distribution via Gradle or Maven and extracting/copying it to the machine you want to run it from, you should have a directory containing both a bin
and a lib
(or repo
) directory. Depending on your platform, you can now run the bin/yourbot
or bin/yourbot.bat
script.
These automatically generated scripts will then invoke java with your dependencies on the classpath and run your main class. Your working directory will be the directory you ran the script from.
Although it is an abuse of the way java works, sometimes you will be forced to create a fat jar, or an uber jar. This is a jar file that contains your application and all its dependencies. This is sometimes used as a lazy way of building a convenient distribution, but should be foregone in favor of the above mentioned distributions.
However, in some cases (more often than not Bukkit/Spigot addons) it is necessary to provide a fat jar, since the host application's loading mechanism can only handle singular jar files. If you are subject to such a case of bad design, please complain to the maintainer of whatever host application you are using, then use the following instructions to forsake all that is good and just and create a fat jar. Remember to grit your teeth the whole time.
plugins {
+ id 'java'
+ # ...
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+}
+
With gradlew shadowJar
you can now create a shaded (fat) jar. It will be named build/libs/yourbot-1.0.0-all.jar
or similar, according to your project settings.
Some of your dependencies might be signed .jar files. Unfortunately, this will likely break your fat jar. Remove the signatures by defining an exclusion filter as demonstrated below. Let the thought that you had to disable a security feature just to make this work serve as a reminder that creating a fat jar is not how jars are meant to be used.
<project>
+ ...
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.3</version>
+ <configuration>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>fat</shadedClassifierName>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Main-Class>com.github.yourname.BotMain</Main-Class>
+ </manifestEntries>
+ </transformer>
+ </transformers>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
Running mvn package
will now additionally create the yourbot-1.0.0-fat.jar
.
Discord allows (and forces) you to "split" larger bots into several independent parts. This behavior is called "sharding", and the independent parts are called "shards". You can think of shards as completely independent bots. Every shard is responsible for a disjoint set of servers.
Logging in with a single shard is pretty much the same as logging in without sharding:
DiscordApi api = new DiscordApiBuilder()
+ .setToken("top secret")
+ .setCurrentShard(0)
+ .setTotalShards(2)
+ .login().join();
+System.out.println("Shard " + api.getCurrentShard() + " logged in!");
+
Note:
current shard
starts counting at0
! This means in the example above you would have current shard0
and shard1
with atotal amount
of2
shards.
Important: There must be a > 5-second delay between each shard-login
You can manually set a fixed amount of total shards and log in all of them:
public class Main {
+
+ public static void main(String[] args) {
+ new DiscordApiBuilder()
+ .setToken("top secret")
+ .setTotalShards(10)
+ .loginAllShards()
+ .forEach(shardFuture -> shardFuture
+ .thenAcceptAsync(Main::onShardLogin)
+ .exceptionally(ExceptionLogger.get())
+ );
+ }
+
+ private static void onShardLogin(DiscordApi api) {
+ System.out.println("Shard " + api.getCurrentShard() + " logged in!");
+ // You can treat the shard like a normal bot account, e.g. registering listeners
+ api.addMessageCreateListener(event -> {
+ // ...
+ });
+ }
+
+}
+
loginAllShards()
returns a collection with completable futures (Collection<CompletableFuture<DiscordApi>>
). This method automatically obeys the > 5-second delay rule.
You can "ask" Discord to recommend you a total amount of shards. This is done by using the DiscordApiBuilder#setRecommendedTotalShards()
method, which returns a CompletableFuture<DiscordApiBuilder>
after getting the required information.
public class Main {
+
+ public static void main(String[] args) {
+ new DiscordApiBuilder()
+ .setToken("top secret")
+ .setRecommendedTotalShards().join()
+ .loginAllShards()
+ .forEach(shardFuture -> shardFuture
+ .thenAccept(Main::onShardLogin)
+ .exceptionally(ExceptionLogger.get())
+ );
+ }
+
+ private static void onShardLogin(DiscordApi api) {
+ // ...
+ }
+
+}
+
You can calculate for which servers a shard is responsible using the server id:
boolean isResponsible = (serverId >> 22) % totalShards == currentShard;
+
Private messages are always sent to the first shard (currentShard == 0
).
Sharding is forced for bots which are in more than 2500 servers.
Everything starts with the DiscordApiBuilder
class. It is used to create a DiscordApi
object which is the most important class of your bot.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("<your super secret token>")
+ .addIntents(Intent.MESSAGE_CONTENT)
+ .login().join();
+
After executing this code, you should already see your bot online in Discord. Of course, just being online is not enough, so let's add some more code!
After you got your api
instance, let's continue by adding a listener that answers every !ping
message with a simple Pong!
.
api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+});
+
A good place for your code is the main(...)
method that every executable Java program must have. Your complete class may look like this:
public class MyFirstBot {
+
+ public static void main(String[] args) {
+ // Log the bot in
+ DiscordApi api = new DiscordApiBuilder()
+ .setToken("<your super secret token>")
+ .addIntents(Intent.MESSAGE_CONTENT)
+ .login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+ }
+
+}
+
Congratulations, that's already everything you have to know for the beginning. Now, you can play around a little bit by exploring other listeners and methods. Or you just continue reading articles in the Basic Tutorials category.
`,12);function A(j,E){const a=i("RouterLink");return p(),c("div",null,[d,k,v,n("p",null,[m,g,h,t(a,{to:"/wiki/basic-tutorials/gateway-intents.html#privileged-intents"},{default:e(()=>[b]),_:1}),f]),n("div",y,[_,n("p",null,[w,t(a,{to:"/wiki/basic-tutorials/interactions/commands.html"},{default:e(()=>[q]),_:1}),x])]),C])}var N=o(r,[["render",A],["__file","writing-your-first-bot.html.vue"]]);export{N as default}; diff --git a/assets/writing-your-first-bot.html.ab95f1ba.js b/assets/writing-your-first-bot.html.ab95f1ba.js new file mode 100644 index 0000000..42eb611 --- /dev/null +++ b/assets/writing-your-first-bot.html.ab95f1ba.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-7bf86adb","path":"/wiki/getting-started/writing-your-first-bot.html","title":"Writing your first bot","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"\u2757 Enabling required intents","slug":"enabling-required-intents","children":[]},{"level":2,"title":"\u{1F511} Log the bot in","slug":"log-the-bot-in","children":[]},{"level":2,"title":"\u{1F442} Adding a listener","slug":"adding-a-listener","children":[]},{"level":2,"title":"\u{1F469}\u200D\u{1F527} Putting it all together","slug":"putting-it-all-together","children":[]}],"git":{"updatedTime":1692105785000,"contributors":[{"name":"Dominic Fellbaum","email":"d.fellbaum@hotmail.de","commits":1}]},"filePathRelative":"wiki/getting-started/writing-your-first-bot.md"}');export{t as data}; diff --git a/bot-search-index.json b/bot-search-index.json new file mode 100644 index 0000000..e456634 --- /dev/null +++ b/bot-search-index.json @@ -0,0 +1 @@ +[{"title":"Imprint","headers":[],"content":"It's important to know the life-cycle of a discord bot to properly handle disconnects.\nThe following state diagram shows the 4 states a bot can have:
\n\nThe bot is connected to the websocket and receives all events.
\nThe bot is not connected to the websocket and receives no events. It's not uncommon for a bot to occasionally lose connection.\nThis can have various reasons, for example:
\nThe bot will periodically try to resume/reconnect to the websocket. It will start with a small frequency and increase it\nwith every failed reconnect attempt. You can modify the reconnect delay with the DiscordApi#setReconnectDelay(...)
method.\nThe following example code would increase the delay linearly.\nThe 1st attempt would be delayed for 2
seconds, the 2nd attempt for 4
seconds, the 3rd attempts for 6
seconds, ...
api.setReconnectDelay(attempt -> attempt * 2);\n
\n\nImportant: Bots can only reconnect 1000 times in a 24-hour period (every ~90 seconds). This limit is global and across all shards.\nUpon hitting this limit, all active sessions for the bot will be terminated, the bot's token will be reset, and\nyou will receive an email notification. This is the reason Javacord increases the reconnect delay with every attempt.
\n
By default, the $default_delay$ formula below is used to calculate the reconnect delay
\n$$\ndefault_delay(a) = \\lfloor a^{1.5} - \\frac{a^{1.5}}{\\frac{1}{(0.1 \\cdot a)} + 1} \\rceil\n$$
\nwith $a$ being the attempt.
\nThe formula will generate the following reconnect delay:
\nAttempt | \nDelay | \n
---|---|
1 | \n1 | \n
2 | \n2 | \n
3 | \n4 | \n
4 | \n6 | \n
5 | \n7 | \n
... | \n... | \n
10 | \n16 | \n
15 | \n23 | \n
20 | \n30 | \n
... | \n... | \n
50 | \n59 | \n
100 | \n91 | \n
150 | \n115 | \n
... | \n... | \n
Resuming is only possible for a short time after being disconnected. If the bot can successfully resume the connection,\nyou will not miss any events. Your bot will receive all events you missed while being disconnected. The cache gets updated\naccordingly.
\nIf your bot reconnects (not resumes!), the whole cache gets wiped, and you will not receive any missed events.
\nWhat does this mean?
\nServer
, User
, Channel
, ...) will be outdated. This is why you should never store\nentities, but the id instead. See For most bots, there's nothing you have to do. All registered listeners are reconnect-resistant, which means if your bot\nis only reacting to events, it will work fine after a restart. For example, the following code will not be affected by a\nreconnect (besides maybe some missed !ping
messages):
api.addMessageCreateListener(event -> {\n if (event.getMessage().getContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n});\n
In case you want to handle reconnects (e.g. fetch the message history to detect missed messages), there are\nspecial connection-related listeners which can be used to track the state of the bot:
\nLostConnectionListener
ReconnectListener
ResumeListener
Javacord keeps an internal cache for entities (e.g. Servers, Channels, Users, ...). It is important to know how the cache behaves to properly use it.
\nNearly every entity known by the bot is guaranteed to be in the cache. There are a few exceptions though:
\nUsers are only cached when you have the GUILD_MEMBERS
intent enabled.\nSee
Not every single message is in the cache, which means you can encounter messages which exist but are not in the cache. This can happen for most message events, e.g. the ReactionAddEvent
event.deleteMessage()
, event.editMessage("New Content")
. If you need the message (e.g. to get its content), you can request it using event.requestMessage()
.
Additionally, you can use the static methods in the Message
Message.edit(api, channelId, messageId, "New content");
. This is very useful if you want to store them in a database.
Webhooks and Invites are not kept in the cache at all and won't receive any updates.
\nEmbeds from message.getEmbed()
won't receive updates. If a message's embed gets edited, getEmbed()
will return a completely new embed object.
Javacord's cache exclusively uses websocket events to keep the cache up to date. This means that the content of your objects might be outdated, even though you modified it yourself:
\nMessages message = ...;\nSystem.out.println(message.getContent()); // Prints the old content, e.g. \"old content\"\nmessage.edit(\"new content\").join(); // Edits the message and waits for success\nSystem.out.println(message.getContent()); // Still prints \"old content\"\nThread.sleep(1000);\nSystem.out.println(message.getContent()); // Most likely prints \"new content\" now\n
Even though entities are usually kept in the cache for a very long time, you should not keep references to these objects for a longer period of time, but store the id / use event methods:
\n// Bad\nMessage message = ...;\nmessage.addReactionAddListener(event -> {\n if (event.getEmoji().equalsEmoji(\"👎\")) {\n message.delete(); // Prevents \"message\" from being garbage collected\n }\n});\n\n// Good\nMessage message = ...;\nmessage.addReactionAddListener(event -> {\n if (event.getEmoji().equalsEmoji(\"👎\")) {\n event.deleteMessage(); // Does not use the message object\n }\n});\n
// Bad\nSet<User> usersWithBadMood = new HashSet<>();\napi.addReactionAddListener(event -> {\n if (event.getEmoji().equalsEmoji(\"😦\")) {\n usersWithBadMood.add(event.getUser());\n }\n});\n\n// Good\nSet<Long> usersWithBadMood = new HashSet<>();\napi.addReactionAddListener(event -> {\n if (event.getEmoji().equalsEmoji(\"😦\")) {\n usersWithBadMood.add(event.getUser().getId());\n }\n});\n
Some examples of when cached entities are invalidated:
\nChannel
, because you left and rejoined a serverBy default, Javacord waits for all servers and members to be loaded on startup. You can disable this behavior in the DiscordApiBuilder
before logging in:
new DiscordApiBuilder()\n .setToken(\"abc\")\n .setWaitForServersOnStartup(false)\n .login()\n .thenAccept(api -> {\n // Do something\n }).exceptionally(ExceptionLogger.get());\n
Depending on the size of your bot, this can significantly speed up the login process. This comes with one downside however: The api.getServers()
collection is empty directly after logging in. You will receive ServerBecomesAvailableEvent
s for every server which finished loading.
In order to reduce memory usage, you can completely disable the message cache or reduce the number of cached messages. By default, Javacord caches up to 50 messages per channel and removes messages from the cache which are older than 12 hours. You can lower this limit by using DiscordApi#setMessageCacheSize(Capacity, StorageTimeInSeconds)
.
// Cache a maximum of 10 messages per channel for and remove messages older than 1 hour\napi.setMessageCacheSize(10, 60*60);\n
You can even set this limit on a per-channel basis:
\nTextChannel channel = ...;\nchannel.getMessageCache().setCapacity(10);\nchannel.getMessageCache().setStorageTimeInSeconds(60*60);\n
If you update several settings of an entity (server, channel, ...) at once, you should use the updater for this entity instead of the updateXyz(...)
methods.
// Sends 1 request to Discord\nServerTextChannel channel = ...;\nnew ServerTextChannelUpdater(channel)\n .setName(\"example-channel\")\n .setTopic(\"This is an example channel\")\n .setNsfwFlag(true)\n .update();\n
instead of
\n// Sends 3 requests to Discord\nServerTextChannel channel = ...;\nchannel.updateName(\"example-channel\");\nchannel.updateTopic(\"This is an example channel\");\nchannel.updateNsfwFlag(true);\n
WARNING
\nSupport for audio was added to Javacord very recently.\nIf you encounter any bugs, please create an issue on GitHub
Javacord allows your bot to connect to voice channels and play audio (e.g., music).\nThis short tutorial gives you an introduction on how to connect to a voice channel and play your\nfavorite music
Connecting to a voice channel is very straight forward:\nCalling #connect()
on an instance of ServerVoiceChannel
will connect your bot to this voice channel and\nreturn a AudioConnection
object.
The following example will connect the bot to the voice channel of the user that typed !music
in the chat:
ServerVoiceChannel channel = ...;\nchannel.connect().thenAccept(audioConnection -> {\n // Do stuff\n}).exceptionally(e -> {\n // Failed to connect to voice channel (no permissions?)\n e.printStackTrace();\n return null;\n});\n
There are plenty of sources for audio (e.g., YouTube, local files, etc.).\nThe current de facto standard library for extracting audio from these sources with Java is the\nLavaPlayer
To use it with Javacord, you have to add it as a dependency to your project (e.g., with Gradle or Maven) and\ncreate a Javacord audio source like this:
\npublic class LavaplayerAudioSource extends AudioSourceBase {\n\n private final AudioPlayer audioPlayer;\n private AudioFrame lastFrame;\n\n /**\n * Creates a new lavaplayer audio source.\n *\n * @param api A discord api instance.\n * @param audioPlayer An audio player from Lavaplayer.\n */\n public LavaplayerAudioSource(DiscordApi api, AudioPlayer audioPlayer) {\n super(api);\n this.audioPlayer = audioPlayer;\n }\n\n @Override\n public byte[] getNextFrame() {\n if (lastFrame == null) {\n return null;\n }\n return applyTransformers(lastFrame.getData());\n }\n\n @Override\n public boolean hasFinished() {\n return false;\n }\n\n @Override\n public boolean hasNextFrame() {\n lastFrame = audioPlayer.provide();\n return lastFrame != null;\n }\n\n @Override\n public AudioSource copy() {\n return new LavaplayerAudioSource(getApi(), audioPlayer);\n }\n}\n
With this audio source, you can now start using Lavaplayer, e.g. to play a YouTube video:
\n// Create a player manager\nAudioPlayerManager playerManager = new DefaultAudioPlayerManager();\nplayerManager.registerSourceManager(new YoutubeAudioSourceManager());\nAudioPlayer player = playerManager.createPlayer();\n\n// Create an audio source and add it to the audio connection's queue\nAudioSource source = new LavaplayerAudioSource(api, player);\naudioConnection.setAudioSource(source);\n\n// You can now use the AudioPlayer like you would normally do with Lavaplayer, e.g.,\nplayerManager.loadItem(\"https://www.youtube.com/watch?v=NvS351QKFV4\", new AudioLoadResultHandler() {\n @Override\n public void trackLoaded(AudioTrack track) {\n player.playTrack(track);\n }\n\n @Override\n public void playlistLoaded(AudioPlaylist playlist) {\n for (AudioTrack track : playlist.getTracks()) {\n player.playTrack(track);\n }\n }\n\n @Override\n public void noMatches() {\n // Notify the user that we've got nothing\n }\n\n @Override\n public void loadFailed(FriendlyException throwable) {\n // Notify the user that everything exploded\n }\n});\n
There are basically two kinds of proxies: HTTP proxies and SOCKS proxies. Both may or may not support or require authentication depending on version, capabilities, and configuration. Due to the underlying libraries used, currently, Javacord fully supports HTTP proxies and partially supports SOCKS proxies.
\nJavacord uses HTTPS connections to communicate with the Discord REST API and a WSS connection to communicate with the Discord WebSocket endpoint. Both these protocols are secure protocols and thus do not honor settings for HTTP connections, only settings for HTTPS connections.
\nIf you did not explicitly set a proxy in the DiscordApiBuilder
and did not set a system default ProxySelector
, the default proxy selector of the JRE is used. This proxy selector honors, amongst others, the relevant standard system properties https.proxyHost
, https.proxyPort
, socksProxyHost
, socksProxyPort
, and socksProxyVersion
. Use the former two to configure an HTTP proxy, or the latter three to configure a SOCKS proxy, although you will not need socksProxyVersion
, as SOCKS4 is currently not supported.
You can use java.net.ProxySelector.setDefault(ProxySelector)
to set a system default proxy selector that replaces the default one. In its implementation, you can dynamically determine which proxy to use for each connection.
Using the method DiscordApiBuilder.setProxy(Proxy)
you can set a proxy instance directly in the DiscordApiBuilder
that is solely used for Javacord connections and does not affect the unrelated code running in the JVM.
Using the method DiscordApiBuilder.setProxySelector(ProxySelector)
you can set a proxy selector instance directly in the DiscordApiBuilder
that is solely used for Javacord connections and does not affect the remaining code running in the JVM. In its implementation, you can dynamically determine which proxy to use for each connection.
You can use java.net.Authenticator.setDefault(Authenticator)
to set a system default authenticator that is used to provide username and password pairs for connections. This authenticator is only used if the proxy supports the Basic
authentication scheme. If you need to support any other authentication scheme, use an explicitly configured authenticator. The java.net.Authenticator
interface is too inflexible to support this.
Using the method DiscordApiBuilder.setProxyAuthenticator(Authenticator)
, you can set a custom authenticator that is much more powerful than the java.net.Authenticator
. You get much more information about the connection to be established, and you can return any HTTP header that is necessary for a successful authentication. This should cover all sorts of available authentication mechanisms.
HTTP proxies are fully supported.
\nSOCKS 4 is currently not supported.
\nThe WebSocket library we use does not support SOCKS proxies at all, and the HTTP library we use has a bug that prevents SOCKS 4 to be used. Additionally, you would need to use at least Java 9 or a separate socket factory supporting SOCKS 4, as the JRE implementation is not working in Java 8 and got fixed only in Java 9+.
\nSOCKS 4a is currently only partially supported.
\nThe WebSocket library we use does not support SOCKS proxies at all, so it could be used for the REST connections only. Additionally, you would need to use a separate socket factory supporting SOCKS 4a, as the JRE implementation is not capable of doing SOCKS 4a, only SOCKS 4 and SOCKS 5 are supported at the time of creation of this wiki article.
\nSOCKS 5 is currently only partially supported.
\nThe WebSocket library we use does not support SOCKS proxies at all, so it could be used for the REST connections only.
\n","path":"/wiki/advanced-topics/proxies.html","keywords":["proxy","connection","socks","socks4","socks5"]},{"title":"Ratelimits","headers":[{"level":2,"title":"❗ The Most Important Ratelimits","slug":"the-most-important-ratelimits","children":[]},{"level":2,"title":"💪 Dealing with Ratelimits","slug":"dealing-with-ratelimits","children":[{"level":3,"title":"Example","slug":"example","children":[]}]},{"level":2,"title":"❌ Can I disable ratelimits?","slug":"can-i-disable-ratelimits","children":[]}],"content":"Ratelimits is a Discord restriction which prevents you from performing actions in a very fast rate.\nMost ratelimits are on a per-channel or a per-server basis.
\nAction | \nRatelimit | \nType | \n
---|---|---|
Send Messages | \n5 / 5s | \nper channel | \n
Delete Messages | \n5 / 1s | \nper channel | \n
Add/Remove Reactions | \n1 / 0.25s | \nper channel | \n
Edit Server Members | \n10 / 10s | \nper server | \n
Edit Member Nickname | \n1 / 1s | \nper server | \n
Edit Bot Username | \n2 / 1h | \nper account | \n
Update Channels | \n2 / 10m | \nper account | \n
All Actions Combined | \n50 / 1s | \nper account | \n
Usually Javacord takes care about these limitations for you.\nAs a user, there's nothing you have to do, but you should at least know that ratelimits exist.
\nThe following code
\n// Who even needs loops?\nchannel.sendMessage(\"Ratelimit Example #1\");\nchannel.sendMessage(\"Ratelimit Example #2\");\nchannel.sendMessage(\"Ratelimit Example #3\");\nchannel.sendMessage(\"Ratelimit Example #4\");\nchannel.sendMessage(\"Ratelimit Example #5\");\nchannel.sendMessage(\"Ratelimit Example #6\");\nchannel.sendMessage(\"Ratelimit Example #7\");\nchannel.sendMessage(\"Ratelimit Example #8\");\nchannel.sendMessage(\"Ratelimit Example #9\");\nchannel.sendMessage(\"Ratelimit Example #10\");\nchannel.sendMessage(\"Ratelimit Example #11\");\nchannel.sendMessage(\"Ratelimit Example #12\");\n
would look like this in the client:
\n\n\n\n
You can clearly see the delay between every 5 sent messages.
\nNo. Ratelimits are a limitation from Discord itself, which you cannot circumvent.
\n","path":"/wiki/advanced-topics/ratelimits.html","keywords":["ratelimits"]},{"title":"Sharding","headers":[{"level":2,"title":"👩🏭 Sharding in Javacord","slug":"sharding-in-javacord","children":[{"level":3,"title":"Logging in with a single shard","slug":"logging-in-with-a-single-shard","children":[]},{"level":3,"title":"Logging in with a fixed amount of shards","slug":"logging-in-with-a-fixed-amount-of-shards","children":[]},{"level":3,"title":"Using the recommended shard amount","slug":"using-the-recommended-shard-amount","children":[]}]},{"level":2,"title":"💡 Behavior of Shards","slug":"behavior-of-shards","children":[{"level":3,"title":"Managed servers","slug":"managed-servers","children":[]},{"level":3,"title":"Private messages","slug":"private-messages","children":[]},{"level":3,"title":"When do I need sharding?","slug":"when-do-i-need-sharding","children":[]}]},{"level":2,"title":"🌄 Sharding for Very Large Bots","slug":"sharding-for-very-large-bots","children":[]}],"content":"Discord allows (and forces) you to "split" larger bots into several independent parts. This behavior is called "sharding", and the independent parts are called "shards". You can think of shards as completely independent bots. Every shard is responsible for a disjoint set of servers.
\nLogging in with a single shard is pretty much the same as logging in without sharding:
\nDiscordApi api = new DiscordApiBuilder()\n .setToken(\"top secret\")\n .setCurrentShard(0)\n .setTotalShards(2)\n .login().join();\nSystem.out.println(\"Shard \" + api.getCurrentShard() + \" logged in!\");\n
\n\nNote:
\ncurrent shard
starts counting at0
! This means in the example above you would have current shard0
and shard1
with atotal amount
of2
shards.
\n\nImportant: There must be a > 5-second delay between each shard-login
\n
You can manually set a fixed amount of total shards and log in all of them:
\npublic class Main {\n\n public static void main(String[] args) {\n new DiscordApiBuilder()\n .setToken(\"top secret\")\n .setTotalShards(10)\n .loginAllShards()\n .forEach(shardFuture -> shardFuture\n .thenAcceptAsync(Main::onShardLogin)\n .exceptionally(ExceptionLogger.get())\n );\n }\n\n private static void onShardLogin(DiscordApi api) {\n System.out.println(\"Shard \" + api.getCurrentShard() + \" logged in!\");\n // You can treat the shard like a normal bot account, e.g. registering listeners\n api.addMessageCreateListener(event -> {\n // ...\n });\n }\n\n}\n
loginAllShards()
returns a collection with completable futures (Collection<CompletableFuture<DiscordApi>>
). This method automatically obeys the > 5-second delay rule.
You can "ask" Discord to recommend you a total amount of shards. This is done by using the DiscordApiBuilder#setRecommendedTotalShards()
method, which returns a CompletableFuture<DiscordApiBuilder>
after getting the required information.
public class Main {\n\n public static void main(String[] args) {\n new DiscordApiBuilder()\n .setToken(\"top secret\")\n .setRecommendedTotalShards().join()\n .loginAllShards()\n .forEach(shardFuture -> shardFuture\n .thenAccept(Main::onShardLogin)\n .exceptionally(ExceptionLogger.get())\n );\n }\n\n private static void onShardLogin(DiscordApi api) {\n // ...\n }\n\n}\n
You can calculate for which servers a shard is responsible using the server id:
\nboolean isResponsible = (serverId >> 22) % totalShards == currentShard;\n
Private messages are always sent to the first shard (currentShard == 0
).
Sharding is forced for bots which are in more than 2500 servers.
\nSharding for very large bots (> 150,000 servers) is a bit different from "normal" sharding. Discord will contact you once your bot reaches this state. Additional information can be found in the official Discord api documentation
Javacord provides XyzBuilder
classes to create new Discord entities like channels, webhooks, servers, and many more.
You can get the channel builders for a specific server using the Server#createXyzChannelBuilder
or by directly calling the constructor.\nCreating a ServerVoiceChannel
would look like this:
Server server = ...;\nServerVoiceChannel channel = new ServerVoiceChannelBuilder(server)\n .setName(\"example-channel\")\n .setUserlimit(10)\n .create().join();\n
You can get the WebhookBuilder
for a specific text channel:
ServerTextChannel channel = ...;\nWebhook webhook = new WebhookBuilder(channel)\n .setName(\"Captain Hook\")\n .setAvatar(new File(\"C:/Users/Bastian/Pictures/puppy.jpg\"))\n .create().join();\n
You can get the InviteBuilder
for a specific server channel:
ServerTextChannel channel = ...;\nInvite invite = new InviteBuilder(channel)\n .setMaxAgeInSeconds(60*60*24)\n .setMaxUses(42)\n .create().join();\n
You can get the ServerBuilder
from the current api instance:
DiscordApi api = ...;\nlong serverId = new ServerBuilder(api)\n .setName(\"My Awesome Server\")\n .setIcon(api.getYourself().getAvatar())\n .setVerificationLevel(VerificationLevel.HIGH)\n .setDefaultMessageNotificationLevel(DefaultMessageNotificationLevel.ONLY_MENTIONS)\n .setRegion(Region.EU_CENTRAL)\n .create().join();\n
WARNING
\nBy default, bots can only create servers if they are in less than 10 servers. You can contact the Discord support to request a higher limit.
\nEmbeds are attached to messages and have a special design.\nThe usually look like this:
\n\nJavacord provides an EmbedBuilder
which can be used to create embeds:
// Create the embed\nEmbedBuilder embed = new EmbedBuilder()\n .setTitle(\"Title\")\n .setDescription(\"Description\")\n .setAuthor(\"Author Name\", \"http://google.com/\", \"https://cdn.discordapp.com/embed/avatars/0.png\")\n .addField(\"A field\", \"Some text inside the field\")\n .addInlineField(\"An inline field\", \"More text\")\n .addInlineField(\"Another inline field\", \"Even more text\")\n .setColor(Color.BLUE)\n .setFooter(\"Footer\", \"https://cdn.discordapp.com/embed/avatars/1.png\")\n .setImage(new File(\"C:/Users/Bastian/Pictures/puppy.jpg\"))\n .setThumbnail(new File(\"C:/Users/Bastian/Pictures/kitten2.png\"));\n// Send the embed\nchannel.sendMessage(embed);\n
By default, Discord expects embed images to be a link (e.g., the image link used in setFooter(...)
), but you can also use attachments for images.\nIf you provide a non-url image source (e.g. the puppy.jpg
file used in setImage(...)
), Javacord automatically uploads them as an attachment to the message and uses this attachment for the embed.
Type | \nLimit | \n
---|---|
Title | \n256 characters | \n
Description | \n4096 characters | \n
Field Amount | \nUp to 25 fields | \n
Field Name | \n256 characters | \n
Field Value | \n1024 characters | \n
Footer Text | \n2048 characters | \n
Author Name | \n256 characters | \n
In addition to the limits above, the sum of all characters in an embed structure must not exceed 6000 characters.
\nsetAuthor(...)
?.setAuthor(\"Author Name\", \"http://google.com/\", \"https://cdn.discordapp.com/embed/avatars/0.png\")\n
null
.\n\n\n
Normal fields always start in a new line, whereas several inline fields can be in the same line.
\nNo, Discord does not allow different embed layouts.
\nDiscord allows for a subset of markdown to be used. See their docs
There are two different kinds of emojis in Discord: Unicode emojis and custom emojis.
\nUnicode emojis are "normal" text emojis which are supported by (nearly) all chat clients, including Discord. You can find a list with all Unicode emojis here: Full Emoji List
You can either directly add them in your code, e.g.
\nchannel.sendMessage(\"Hi! 😃\");\n
or use the normal "tag" like you would in the Client:
\nchannel.sendMessage(\"Hi! :smiley:\");\n
Adding unicode reactions is only possible by using the "real" reaction. It doesn't support tags like :smiley:
.
message.addReaction(\"😃\"); // works\nmessage.addReaction(\":smiley:\"); // doesn't work\n
Custom emojis are emojis that are created in a server. You can get all custom emojis the bot knows by using DiscordApi#getCustomEmojis()
.
To use custom emojis, you have to know its "tag", which has the format <:name:id>
. You can get it by calling CustomEmoji#getMentionTag()
:
channel.sendMessage(\"Hi! <:javacord:415465982715494402>\");\n
CustomEmoji emoji = ...;\nchannel.sendMessage(\"Hi! \" + emoji.getMentionTag());\n
You can either directly use the custom emoji object or use the tag without the <:
>
if you don't have access a custom emoji object (e.g., because it's from a different shard):
CustomEmoji emoji = ...;\nmessage.addReaction(emoji);\n
message.addReaction(\"javacord:415465982715494402\");\n
Just add a \\
in front of the emoji and press Enter
In Javacord, all Emojis are a child of the Emoji
interface:
Known custom emojis are emojis that the bot knows because it's a member of the server with this emoji. A custom emoji can be unknown if someone adds a reaction with an unknown emoji for example. A KnownCustomEmoji
has additional methods like getServer()
or updateName(String)
.
If you are working a lot with Unicode emojis, it's recommended to use a library like JEmoji
message.addReaction(EmojiManager.getByAlias(\":thumbsup:\"));\n
Discord allows you to "subscribe" to specific groups of events.\nThese "subscriptions" are called intent.\nDisabling intents that are not required for your bot can significantly increase your bot's performance.
\nBelow you can find a table with all intents supported by Discord.
\nIntent | \nSafe to Disable | \nPrivileged | \n
---|---|---|
GUILDS | \n❌ | \n❌ | \n
GUILD_MEMBERS | \n✔️ | \n✔️ | \n
GUILD_BANS | \n⚠️* | \n❌ | \n
GUILD_EMOJIS | \n⚠️* | \n❌ | \n
GUILD_INTEGRATIONS | \n✔️ | \n❌ | \n
GUILD_WEBHOOKS | \n✔️ | \n❌ | \n
GUILD_INVITES | \n✔️ | \n❌ | \n
GUILD_VOICE_STATES | \n⚠️* | \n❌ | \n
GUILD_PRESENCES | \n✔️ | \n✔️ | \n
GUILD_MESSAGES | \n✔️ | \n❌ | \n
GUILD_MESSAGE_REACTIONS | \n✔️ | \n❌ | \n
GUILD_MESSAGE_TYPING | \n✔️ | \n❌ | \n
DIRECT_MESSAGES | \n✔️ | \n❌ | \n
DIRECT_MESSAGE_REACTIONS | \n✔️ | \n❌ | \n
DIRECT_MESSAGE_TYPING | \n✔️ | \n❌ | \n
MESSAGE_CONTENT | \n✔️ | \n✔️ | \n
AUTO_MODERATION_CONFIGURATION | \n✔️ | \n❌ | \n
AUTO_MODERATION_EXECUTION | \n✔️ | \n❌ | \n
* Will most likely work, but needs further testing
\nGood to know!
\nGuild is a synonym for servers, commonly used in Discord's API.\nSee
When you disable some of the listed intents, Javacord will not fire events that belong to the intents and\nwill not update these specific parts of the cache.
\nAt the moment, we don't have a list which events are affected by which intents (but it will come soon™️).\nHowever, most intents should be self-explanatory.\nE.g. when you disable the DIRECT_MESSAGES
intent, your bot will not receive any private messages.
Some intents are defined as "privileged" due to the sensitive nature of the data.\nTo use these intents, you have to go to your bot in the Developer Portal
There are some additionally restrictions for bots that are in over 100 servers:
\nTake a look at the official article from Discord about this topic and how to verify your bot:\nBot Verification and Data Whitelisting
The following two intents are especially noteworthy: GUILD_MEMBERS
and GUILD_PRESENCES
.\nBesides being privileged, they have some special implications for Javacord:
GUILD_PRESENCES
This intent is required to get updates about a user's status (i.e., if they are online, what game they are playing, ...).\nAdditionally, without this intent it might take considerably longer to cache all users because of ratelimits\n(up to 10 minutes for shards with 1000 servers).\nIt is advised against setting DiscordApiBuilder#setWaitForAllUsersOnStartup(true)
without this intent, unless absolutely necessary.
GUILD_MEMBERS
This intent is required to keep all users in Javacord's cache.\nWithout this intent, methods like Server#getMembers()
or DiscordApi#getCachedUsers()
will return empty collections.\nHowever, you will still be able to access users from objects like messages, e.g. Message#getUserAuthor()
will still work.
MESSAGE_CONTENT
This intent is a bit different to the other as it does not act as a toggle to receive any events.\nIt's sole purpose is to receive the message content, attachments, components, and embeds.\nOtherwise, these fields will be empty when you receive a Message
object.
Javacord allows you to specify intents in the DiscordApiBuilder
prior to login.\nThere are many options to set intents.\nThe following example code shows the most common ones:
This method enables all non-privileged intents.\nThis is the default setting in Javacord.
\nDiscordApi api = new DiscordApiBuilder()\n .setToken(\"topc secret\")\n .setAllNonPrivilegedIntents()\n .login()\n .join();\n
This method enabled all non-privileged intents, except the given ones.
\nDiscordApi api = new DiscordApiBuilder()\n .setToken(\"topc secret\")\n .setAllNonPrivilegedIntentsExcept(Intent.GUILD_WEBHOOKS)\n .login()\n .join();\n
This method enabled all intents.
\nDiscordApi api = new DiscordApiBuilder()\n .setToken(\"topc secret\")\n .setAllIntents()\n .login()\n .join();\n
This method enabled all intents, except the given ones.
\nDiscordApi api = new DiscordApiBuilder()\n .setToken(\"topc secret\")\n .setAllIntentsExcept(Intent.GUILD_PRESENCES, Intent.GUILD_WEBHOOKS)\n .login()\n .join();\n
This method only enables the given intents.
\nDiscordApi api = new DiscordApiBuilder()\n .setToken(\"topc secret\")\n .setIntents(Intent.GUILDS, Intent.DIRECT_MESSAGES)\n .login()\n .join();\n
This method adds the intents to the currently enabled ones(by default all non-privileged).\nThis is useful i.e. if you only want to enable 1 privileged intent like the MESSAGE_CONTENT
DiscordApi api = new DiscordApiBuilder()\n .setToken(\"topc secret\")\n .addIntents(Intent.MESSAGE_CONTENT)\n .login()\n .join();\n
This is a list with the most common Discord-related terms:
\nGuild
- A synonym for server
Selfbot
- A client account bot, usually logged in to a user's own accountSharding
- Splitting a bot into several independent shards
, see Token
- Used to login instead of requiring a username + passwordEmbed
- A "fancy" message, see Ratelimit
- Prevents you from spamming actions, see Websocket
- A TCPGateway
- The address for the websocket
Rest
/ Rest Request
- RESTActivity
- The text underneath the username, usually Playing Xyz
Rich Presence
- A more detailed activity, see Discord DocsCreating listeners is extremely easy in Javacord.\nYou can either use Java 8's lambda expressions to register listeners inline or just create a new class for them, if an inline listener would get too messy.
\napi.addMessageCreateListener(event -> {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n});\n
api.addListener(new MyListener());\n
and
\npublic class MyListener implements MessageCreateListener {\n\n @Override\n public void onMessageCreate(MessageCreateEvent event) {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n }\n\n}\n
Sometimes it might be useful to add listeners before calling the DiscordApiBuilder#login()
method.
DiscordApi api = new DiscordApiBuilder()\n // An inline listener\n .addMessageCreateListener(event -> {\n Message message = event.getMessage();\n if (message.getContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n })\n .addServerBecomesAvailableListener(event -> {\n System.out.println(\"Loaded \" + event.getServer().getName());\n })\n // A listener in their own class\n .addListener(new MyListener())\n // Alternative syntax that can be used for classes that require a DiscordApi parameter in their constructor\n .addListener(MyListener::new)\n .setToken(\"top secret\")\n .setWaitForServersOnStartup(false)\n .login()\n .join();\n
\n\nNote: In most cases, it's enough to add listeners after logging in
\n
Another cool feature is the ability to attach listeners directly to objects. An example where this can be useful is, for example, reacting to reactions. The following code would delete the message if someone adds a 👎 reaction.
\nmessage.addReactionAddListener(event -> {\n if (event.getEmoji().equalsEmoji(\"👎\")) {\n event.deleteMessage();\n }\n}).removeAfter(30, TimeUnit.MINUTES);\n
\n\nSeems like the bot is very sensitive to criticism.
\n
There are two ways to remove a listener:
\nEvery time you register a listener, a ListenerManager
is returned which can be used to unregister the listener:
ListenerManager<MessageCreateListener> listenerManager = api.addMessageCreateListener(event -> {\n // Do stuff\n});\n\nlistenerManager.remove();\n
This manager also has some utility methods. You can, for example, remove a listener after a given time, which can be useful for object listeners:
\nmessage.addReactionAddListener(event -> {\n // Do stuff\n}).removeAfter(30, TimeUnit.MINUTES);\n
removeListener(...)
methodYou can remove any listener using the removeListener(...)
method:
MyListener listener = new MyListener();\napi.addListener(listener);\n// ...\napi.removeListener(listener);\n
Logging is an important tool to keep track of what is going on in your application. Javacord uses the Log4j 2 API
\nIf you want more control, add a proper logging framework that supports your needs and configure it accordingly. You can for example configure log messages on a per-class level, change log levels during runtime, or log to a file or database.
Javacord's fallback logger is a simple Log4j logger which always logs INFO
level and higher. It allows you to enable DEBUG
and TRACE
logging manually. As log levels are hierarchical, enabling TRACE
will also implicitly enable DEBUG
, and disabling DEBUG
will also implicitly disable TRACE
.
// Enable debug logging\nFallbackLoggerConfiguration.setDebug(true);\n\n// Enable trace logging\nFallbackLoggerConfiguration.setTrace(true);\n
Changing the log level of the fallback logger only affects newly created loggers. Pre-existing loggers will not have their log level changed. So if you want to configure the fallback logger, you should do this as one of the first actions in your bot code. If you want to change log levels during runtime, you should use a proper logging framework like Log4j 2 Core or another library that supports this.
\nAll fallback logger messages are printed to the standard output stream (System.out
) and thus usually to your console. If you want to log to a file, database, or anything else, you should consider using a proper logging framework which allows you to configure this behavior.
This is how a log line from the fallback logger will look like:
\n<time with date ><level><logger name, usually the logging class > <message > <the thread context, here the shard number>\n2018-08-03 20:00:06.080+0200 DEBUG org.javacord.core.util.gateway.DiscordWebSocketAdapter Received HELLO packet {shard=0}\n
Adding a logging framework of your choice is very straightforward. You can just add it as a dependency, and it will be detected by Log4j automatically. The following example adds Log4j 2 using Gradle:
\ndependencies { runtimeOnly 'org.apache.logging.log4j:log4j-core:2.17.0' }\n
You can also use an SLF4J compatible logging framework using log4j-to-slf4j
. The following example adds Logback Classic using Gradle:
dependencies {\n runtimeOnly 'org.apache.logging.log4j:log4j-to-slf4j:2.17.0'\n runtimeOnly 'ch.qos.logback:logback-classic:1.2.3'\n}\n
Javacord adds the relevant shard to each log message. The facility that stores this information has a different name depending on which logging framework you use. For Log4j 2, this is called Thread Context Map and can be added in a pattern layout with %X{shard}
, or you can add the whole thread context map by using %X
. For Logback Classic, it is called MDC and can be added with the same pattern expressions as for Log4j.
The MessageBuilder
class is a more powerful alternative to the TextChannel#sendMessage(...)
method.
It can be used to construct more complex messages and supports some additional features that are not possible\nwith a simple TextChannel#sendMessage(...)
call.
The following code
\nnew MessageBuilder()\n .append(\"Look at these \")\n .append(\"awesome\", MessageDecoration.BOLD, MessageDecoration.UNDERLINE)\n .append(\" animal pictures! 😃\")\n .appendCode(\"java\", \"System.out.println(\\\"Sweet!\\\");\")\n .addAttachment(new File(\"C:/Users/Bastian/Pictures/kitten.jpg\"))\n .addAttachment(new File(\"C:/Users/Bastian/Pictures/puppy.jpg\"))\n .setEmbed(new EmbedBuilder()\n .setTitle(\"WOW\")\n .setDescription(\"Really cool pictures!\")\n .setColor(Color.ORANGE))\n .send(channel);\n
will be displayed like this:
\n\nThe allowed mentions object lets you control what should be mentioned (pinged) in a message if it contains mentions.
\nThe following code will ping:
\nAnd will not ping:
\nAllowedMentions allowedMentions = new AllowedMentionsBuilder()\n .addUser(user0.getId())\n .setMentionRoles(true)\n .setMentionEveryoneAndHere(false)\n .build();\n\n new MessageBuilder()\n .setAllowedMentions(allowedMentions)\n .append(user0.getMentionTag())\n .append(user1.getMentionTag())\n .append(role.getMentionTag())\n .append(role2.getMentionTag())\n .append(\"@everyone\")\n .send(channel);\n
If you add a user to the mentions object and set setMentionUsers(true)
it will ping every mentioned user. The same applies for setMentionRoles(true)
If you took the time to write a bot, at some point you'll also want to run it, either for use in production or for debugging from the IDE.
\nWhile developing your bot, you will want to run your bot directly from the IDE in order to quickly test changes and new features. For this, create a Run/Debug Configuration in your IDE of choice with your bot's main class. Remember to also add any necessary parameters and environment variables.
\nA working Run/Debug configuration will also enable you to run your bot with a debugger. A debugger is often considered a developer's most important tool, so make sure to familiarize yourself with the debugging integration for your IDE of choice.
\nThis assumes your project is set up correctly, preferably with Gradle, can be built without errors, and does not yet have any run/debug configurations.
\n1. Locate and click the Add Configuration...
button in the top bar next to the start button.
2. In the newly opened window, click the +
button in the top left and select Application
3. Give a name for your configuration and select the module to use the classpath of (usually yourproject.main
).
4. Select your Main class. Use the ...
button to search for it or provide the fully qualified name. If it can not be found, you most likely selected the wrong module in step 3.
5. Optional: Set command line arguments and environment variables. For the environment variables, use the button to the right of the input field for a more convenient input window.
\n6. Click Apply
to finalize the configuration, then OK
to close the window.
7. Select your configuration in the drop-down menu and run or debug it with the buttons to the right.
\n\nThis assumes your project is set up correctly, can be built without errors, and does not yet have any run/debug configurations.
\n1. In the menu bar, click "Run" then "Run Configurations...".
\n2. In the newly opened window, select "Java Application" on the left side, then click the leftmost button in the row above the tree view. A new configuration will appear.
\n\n3. Give a name to your configuration.
\n4. Set the project and the main class. To easily select it, use the "Browse..." and "Search..." buttons.
\n5. Optional: Set command line (and VM) arguments as well as environment variables in their respective tabs.
\n6. Click Apply
to save your configuration, then Close
to close the window.
7. Run or debug your bot via the Buttons in the top row, the Run
menu, or the shortcuts Ctrl+F11 for running and F11 for debugging.
Running from the IDE is only recommended during development and strongly discouraged for production use. Generally, you'll want your build tool to create a convenient distribution format for you to use.
\nFor Gradle, only two further steps are necessary for a basic application. On top of the steps described in the Getting Started Section, also add the Application PluginmainClass
as the fully qualified name of your main class. If you're using an older version of Gradle (earlier than 6.4), the attribute is instead called mainClassName
.
INFO
\nAs with many Gradle solutions, there is actually a whole lot going on under the hood. The application
plugin implicitly also applies the java
and distribution
plugins. Refer to the documentations of the involved plugins for more ways to fine-tune the process.
Your modified build file should now look similar to this:
\nplugins {\n application\n}\n \nversion = \"1.0.0\"\n \njava {\n sourceCompatibility = JavaVersion.VERSION_1_8\n}\n \napplication {\n mainClass.set(\"com.github.yourname.BotMain\")\n // mainClassName.set(\"com.github.yourname.BotMain\") // Gradle < 6.4\n}\n \nrepositories {\n mavenCentral()\n}\n \ndependencies {\n implementation(\"org.javacord:javacord:{{latestVersion}}\")\n}\n
plugins {\n id 'application'\n}\n \nversion '1.0.0'\n \njava {\n sourceCompatibility = JavaVersion.VERSION_1_8\n}\n \napplication {\n mainClass = 'com.github.yourname.BotMain'\n // mainClassName = 'com.github.yourname.BotMain' // for Gradle versions < 6.4\n}\n \nrepositories {\n mavenCentral()\n}\n \ndependencies {\n implementation 'org.javacord:javacord:{{latestVersion}}'\n}\n
Now you can execute the distZip
or distTar
task with Gradle. The task will create a distribution and package it in an archive file that will be placed in the build/distributions
directory. Extract the content of those files on your server or whichever machine you want to run your bot on.
The distribution usually only contains the directories bin
and lib
. From the distribution directory, run either bin/yourbot
or bin/yourbot.bat
, depending on whether you're running the bot on Linux / macOS or windows.
For Maven, add the Appassemblerpom.xml
. The plugin will create a distribution, but not bundle it in a neat archive file, so we'll also add the assembly plugin. We'll bind both to the package
lifecycle phase.
<project>\n ...\n <build>\n <plugins>\n <plugin>\n <groupId>org.codehaus.mojo</groupId>\n <artifactId>appassembler-maven-plugin</artifactId>\n <version>1.10</version>\n <configuration>\n <programs>\n <program>\n <mainClass>org.javacord.examplebot.Main</mainClass>\n <id>examplebot</id>\n </program>\n </programs>\n </configuration>\n <executions>\n <execution>\n <id>create-distribution</id>\n <phase>package</phase>\n <goals>\n <goal>assemble</goal>\n </goals>\n </execution>\n </executions>\n </plugin>\n <plugin>\n <artifactId>maven-assembly-plugin</artifactId>\n <version>3.3.0</version>\n <configuration>\n <descriptors>\n <!-- This must match the location of the descriptor -->\n <descriptor>src/assembly/distribution.xml</descriptor>\n </descriptors>\n </configuration>\n <executions>\n <execution>\n <id>create-archive</id>\n <phase>package</phase>\n <goals>\n <goal>single</goal>\n </goals>\n </execution>\n </executions>\n </plugin>\n </plugins>\n </build>\n</project>\n
Sadly, none of the built-in assembly descriptors match our use case, so we'll put our custom one into src/assembly/distribution.xml
:
<assembly xmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd\">\n <id>distribution</id>\n <formats>\n <!-- See https://maven.apache.org/plugins/maven-assembly-plugin/assembly.html for supported formats -->\n <format>tar.gz</format>\n <format>tar.bz2</format>\n <format>zip</format>\n </formats>\n <fileSets>\n <fileSet>\n <!-- This will also include your project readme, license and similar files-->\n <directory>${project.basedir}</directory>\n <outputDirectory>/</outputDirectory>\n <includes>\n <include>README*</include>\n <include>LICENSE*</include>\n <include>NOTICE*</include>\n </includes>\n </fileSet>\n <fileSet>\n <!-- Change this if you reconfigured the appassembler output directory -->\n <directory>${project.build.directory}/appassembler</directory>\n <outputDirectory>/</outputDirectory>\n </fileSet>\n </fileSets>\n</assembly>\n
Now when you execute mvn package
, a distribution with start scripts for Windows and Linux/macOS will be generated which is then packaged into archive files for every format you specified in the assembly descriptor. You can find the raw distribution (without readme and license files) in target/appassembler
and the archive files directly in target
.
After creating your distribution via Gradle or Maven and extracting/copying it to the machine you want to run it from, you should have a directory containing both a bin
and a lib
(or repo
) directory. Depending on your platform, you can now run the bin/yourbot
or bin/yourbot.bat
script.
These automatically generated scripts will then invoke java with your dependencies on the classpath and run your main class. Your working directory will be the directory you ran the script from.
\nAlthough it is an abuse of the way java works, sometimes you will be forced to create a fat jar, or an uber jar. This is a jar file that contains your application and all its dependencies. This is sometimes used as a lazy way of building a convenient distribution, but should be foregone in favor of the above mentioned distributions.
\nHowever, in some cases (more often than not Bukkit/Spigot addons) it is necessary to provide a fat jar, since the host application's loading mechanism can only handle singular jar files. If you are subject to such a case of bad design, please complain to the maintainer of whatever host application you are using, then use the following instructions to forsake all that is good and just and create a fat jar. Remember to grit your teeth the whole time.
\nFor Gradle, use the shadow
plugins {\n id 'java'\n # ...\n id 'com.github.johnrengelman.shadow' version '7.1.2'\n}\n
With gradlew shadowJar
you can now create a shaded (fat) jar. It will be named build/libs/yourbot-1.0.0-all.jar
or similar, according to your project settings.
For Maven, add the maven-shade-plugin
Some of your dependencies might be signed .jar files. Unfortunately, this will likely break your fat jar. Remove the signatures by defining an exclusion filter as demonstrated below. Let the thought that you had to disable a security feature just to make this work serve as a reminder that creating a fat jar is not how jars are meant to be used.
\n<project>\n ...\n <build>\n <plugins>\n <plugin>\n <groupId>org.apache.maven.plugins</groupId>\n <artifactId>maven-shade-plugin</artifactId>\n <version>3.2.3</version>\n <configuration>\n <shadedArtifactAttached>true</shadedArtifactAttached>\n <shadedClassifierName>fat</shadedClassifierName>\n <transformers>\n <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\">\n <manifestEntries>\n <Main-Class>com.github.yourname.BotMain</Main-Class>\n </manifestEntries>\n </transformer>\n </transformers>\n <filters>\n <filter>\n <artifact>*:*</artifact>\n <excludes>\n <exclude>META-INF/*.SF</exclude>\n <exclude>META-INF/*.DSA</exclude>\n <exclude>META-INF/*.RSA</exclude>\n </excludes>\n </filter>\n </filters>\n </configuration>\n <executions>\n <execution>\n <phase>package</phase>\n <goals>\n <goal>shade</goal>\n </goals>\n </execution>\n </executions>\n </plugin>\n </plugins>\n </build>\n</project>\n
Running mvn package
will now additionally create the yourbot-1.0.0-fat.jar
.
WARNING
\nThis tutorial assumes that you are familiar with lambda expressions.\nTake a look at the
As Javacord is heavily multithreaded, you must understand the concept of\nFutures
A future is basically a wrapper, that will contain a value in the future but might not contain it right now.\nThis is useful, if a method call requires some time and should not block the execution of your current code.\nYou can easily see the difference with a primitive speed comparison:
\nlong currentTime = System.currentTimeMillis();\nchannel.sendMessage(\"Test 1\");\nchannel.sendMessage(\"Test 2\");\nchannel.sendMessage(\"Test 3\");\nchannel.sendMessage(\"Test 4\");\nchannel.sendMessage(\"Test 5\");\n// Prints \"4 ms\"\nSystem.out.println((System.currentTimeMillis() - currentTime) + \" ms\");\n
long currentTime = System.currentTimeMillis();\nchannel.sendMessage(\"Test 1\").join();\nchannel.sendMessage(\"Test 2\").join();\nchannel.sendMessage(\"Test 3\").join();\nchannel.sendMessage(\"Test 4\").join();\nchannel.sendMessage(\"Test 5\").join();\n// Prints \"894 ms\"\nSystem.out.println((System.currentTimeMillis() - currentTime) + \" ms\");\n
TIP
\njoin()
blocks the current thread until the method finished. This will be explained later.
The join
method blocks the current thread until the method finished.\nIt returns the method's result or throws a CompletionException
if anything failed.
The following example would create a new text channel in a given server
and sends a message directly afterwards.
// Create the channel\nServerTextChannel channel = new ServerTextChannelBuilder(server)\n .setName(\"new-channel\")\n .create()\n .join();\n// Send a message in the new channel\nMessage message = channel.sendMessage(\"First!\").join();\n// Adds an reaction to the message. Even though this method doesn't return anything,\n// join() ensures, that an exception is thrown in case something went wrong\nmessage.addReaction(\"👍\").join();\n
DANGER
\nYou should avoid join()
for methods which will be called frequently.
TIP
\nWhile join()
can become a performance issue when you call it very frequently, it is very convenient to use and easy to understand.\nIf you are new to programming and just want to get your first bot working, this is a good method to start with.
Once you gathered more experience, we highly advise against using join
as it negatively impacts your bot's performance!
The thenAccept
method accepts a Consumer
, that consumes the result of the method and is executed asynchronously.\nIt is the method you usually want to use most of the time.
The following example would create a new text channel in a given server
and send a message directly afterwards.
new ServerTextChannelBuilder(server)\n .setName(\"new-channel\")\n .create()\n .thenAccept(channel -> {\n channel.sendMessage(\"First!\").thenAccept(message -> {\n message.addReaction(\"👍\");\n });\n });\n
DANGER
\nThe example code above has a major problem: Any exception that might occur will be completely ignored.\nThis makes it very hard to find bugs.
\nFor example, if your bot doesn't have the permissions to create a new channel, it will just fail silently.
\nThe exceptionally
method accepts a Function
as parameter, which consumes possible exceptions and returns a fallback value.
The following example would create a new text channel in a given server
and send a message directly afterwards.\nIf something fails (e.g., if the bot isn't allowed to create a text channel in the server), it will log an exception.
new ServerTextChannelBuilder(server)\n .setName(\"new-channel\")\n .create()\n .thenAccept(channel -> {\n channel.sendMessage(\"First!\").thenAccept(message -> {\n message.addReaction(\"👍\").exceptionally(e -> {\n e.printStackTrace(); // Adding the reaction failed\n return null;\n });\n }).exceptionally(e -> {\n e.printStackTrace(); // Message sending failed\n return null;\n });\n }).exceptionally(e -> {\n e.printStackTrace(); // Channel creation failed \n return null;\n });\n
Wow! This looks ugly 🤮.\nBut worry not! There are many options to improve this code!
\nTo make things simpler for you, Javacord has the ExceptionLogger
class, which can be used here.\nIt logs every exception you didn't catch manually.
new ServerTextChannelBuilder(server)\n .setName(\"new-channel\")\n .create()\n .thenAccept(channel -> {\n channel.sendMessage(\"First!\").thenAccept(message -> {\n message.addReaction(\"👍\").exceptionally(ExceptionLogger.get());\n }).exceptionally(ExceptionLogger.get());\n }).exceptionally(ExceptionLogger.get());\n
Okay! This is at least a little better, but still not really perfect 🤔.
\nThe thenCompose
methods allows you to chain futures.\nIt takes a Function
The example to create a text channel can now be written like this:
\nnew ServerTextChannelBuilder(server)\n .setName(\"new-channel\")\n .create() \n .thenCompose(channel -> channel.sendMessage(\"First!\"))\n .thenCompose(message -> message.addReaction(\"👍\"))\n .exceptionally(ExceptionLogger.get());\n
Finally 🎉! Now we only need a single exceptionally(...)
call at the end.\nWe also got rid of the nested callbacks (usually referred to as "callback hell").
For better understanding, here's the example with comments that tell you the type at each line:
\nnew ServerTextChannelBuilder(server) // ServerTextChannelBuilder\n .setName(\"new-channel\") // ServerTextChannelBuilder\n .create() // CompletableFuture<ServerTextChannel>\n .thenCompose(channel -> channel.sendMessage(\"First!\")) // CompletableFuture<Message>\n .thenCompose(message -> message.addReaction(\"👍\")) // CompletableFuture<Void>\n .exceptionally(ExceptionLogger.get()); // CompletableFuture<Void>\n
This tutorial only focuses on the absolute basics.\nFor a more detailed introduction to CompletableFutures, you can take a look at\nthis tutorial
You should also take a look at the JavaDoc for a complete list of methods: CompletableFuture JavaDoc
Lambdas are used to implement functional interfaces
@FunctionalInterface\npublic interface MessageCreateListener {\n void onMessageCreate(MessageCreateEvent event);\n}\n
Before Java 8, you would have implemented this kind of listener as an anonymous class
api.addMessageCreateListener(new MessageCreateListener() {\n @Override\n public void onMessageCreate(MessageCreateEvent event) {\n // Do stuff\n event.pinMessage();\n }\n});\n
In Java 8, this can be replaced with a lambda expression, which does exactly the same thing, but in a more readable fashion.\nThe method parameter (in this case event
) is written in front of the ->
arrow, and the method body is written after it.
api.addMessageCreateListener(event -> {\n // Do stuff\n event.pinMessage();\n});\n
TIP
\nIf the method has more than one parameter, it would look like this:
\n(param1, param2) -> { ... }\n
There's even a shorter version: If you are only executing one statement, you can get rid of the { }
brackets as well:
api.addMessageCreateListener(event -> event.pinMessage());\n
However, the above method can be shortened even more, by replacing the lambda expression with a so called "method reference
api.addMessageCreateListener(MessageEvent::pinMessage);\n
There are also plenty classes in Java 8, that make use of lambda expressions.\nOne example would be the Optional class, which is explained
This tutorial only focuses on the absolute basics.\nFor an in-depth introduction to lambda expressions, you can take a look at\nOracle's article about lambda expressions
WARNING
\nThis tutorial assumes that you are familiar with lambda expressions.\nTake a look at the
The Optional class is widely used in Javacord.\nBasically, every method that might return a null
value will return an Optional in Javacord instead.\nOptionals help you to avoid NullPointerExceptions
and make it very clear if a method may not have a result.\nHere's a small example:
User user = api.getCachedUserById(123L);\nif (user != null) {\n user.sendMessage(\"Hi!\");\n}\n
api.getCachedUserById(123L).ifPresent(user -> \n user.sendMessage(\"Hi!\")\n);\n
You can imagine an Optional
like a box 📦 that may or may not contain a value.\nBefore accessing this value, you have to "unpack" this box first.
The Optional class has many useful methods which can all be found in the JavaDocs
The get
method returns the value of the Optional or throws a NoSuchElementException
if it does not contain a value.
TextChannel channel = api.getTextChannelById(123L).get();\nchannel.sendMessage(\"Hi\");\n
DANGER
\nYou should never use this method blindly but only if you are 100% sure the optional contains a value.
\nEvery time you use this method carelessly, a kitten dies 🙀!\nTrue story.
\nThe isPresent
methods checks, if the Optional contains a value.
Optional<TextChannel> channel = api.getTextChannelById(123L);\nif (channel.isPresent()) {\n // A text channel with the id 123 exists. It's safe to call #get() now\n channel.get().sendMessage(\"Hi\");\n}\n
The orElse
methods returns the value of the Optional if it is present. Otherwise, it returns the given default value.
// The user may not have a nickname on the given server. \n// In this case, we use the user's \"regular\" name.\nString displayName = user.getNickname(server).orElse(user.getName());\n
The example above is (mostly) equivalent to the example below but much more concise.
\nString displayName = \"\";\nOptional<String> nickname = user.getNickname(server);\nif (nickname.isPresent()) {\n displayName = nickname.get();\n} else {\n displayName = user.getName();\n}\n
TIP
\nIn this case you can just use user.getDisplayName(server)
instead.
The ifPresent
method is very similar to an if (value != null) { ... }
check.\nIt takes a Consumer
api.getTextChannelById(123L).ifPresent(channel -> {\n channel.sendMessage(\"Hi!\");\n});\n
The example above is (mostly) equivalent to the example below but more concise.
\nOptional<TextChannel> channel = api.getTextChannelById(123L);\nif (channel.isPresent()) {\n channel.get().sendMessage(\"Hi!\");\n}\n
The filter
method filters the Optional for a given criteria.
Optional<User> botUser = api.getCachedUserById(123L).filter(User::isBot);\n
The example above is equivalent to the example below but more concise.
\nOptional<User> user = api.getCachedUserById(123L);\nOptional<User> botUser;\nif (user.isPresent() && user.get().isBot()) {\n botUser = user;\n} else {\n botUser = Optional.empty();\n}\n
The map
method "converts" the type of an Optional.\nThis is useful, if the type of an Optional does not contain the final value you need.
The following example gets the name of the bots current activity (the "Playing xyz" status) or "None" if the bot has no current activity.
\nString activityName = api.getYourself().getActivity().map(Activity::getName).orElse(\"None\");\n
For better understanding, here's the exact same code but with the types as comments:
\nString activityName = api.getYourself() // User\n .getActivity() // Optional<Activity>\n .map(Activity::getName) // Optional<String>\n .orElse(\"None\"); // String\n
The flatMap
method if very similar to the map
methods.\nIt is used to map values that itself are Optionals to prevent Optional nesting (a "box in a box").
String activityName = api.getCachedUserById(123L) // Optional<User>\n .flatMap(User::getActivity) // Optional<Activity>\n .map(Activity::getName) // Optional<String>\n .orElse(\"None\"); // String\n
Without flatMap
, the code would look like this:
String activityName = api.getCachedUserById(123L) // Optional<User>\n .map(User::getActivity) // Optional<Optional<Activity>>\n .filter(Optional::isPresent) // Optional<Optional<Activity>>\n .map(Optional::get) // Optional<Activity>\n .map(Activity::getName) // Optional<String>\n .orElse(\"None\"); // String\n
This tutorial only focuses on the absolute basics.\nFor an in-depth introduction to Optionals, you can take a look at\nOracle's article about optionals
Welcome to the Javacord wiki! 👋
\nThis wiki will help you to get started with your first Discord bot as fast as possible.
\nThe wiki is divided into four groups:
\nWhile the wiki is great and covers many aspects of Javacord, we highly recommended you to join our Discord server if you have any questions:
\nAfter you added Javacord as a dependency with your favorite build manager, you should now create a bot account on the Discord website.\nThis article will guide you through the process.
\nBot
TIP
\nIf you want to, you can rename your application first
\nAdd bot
and confirm the popup\n
\nNDc[...]pCs
. You can just click on Copy
.DANGER
\nThis token is used to login your bot. Keep it secret!
\nBots cannot join a server on their own like normal Discord users can.\nInstead, the owner of a server has to invite the bot using a so called Invite Link
.\nThere are multiple ways to create the invite link:
The easiest way to obtain an invite link for your bot is by letting Javacord do it for you.\nSimply execute the following code, and it will print the invite link to your console:
\nDiscordApi api = new DiscordApiBuilder().setToken(\"your token\").login().join();\nSystem.out.println(api.createBotInvite());\n
If you don't have Javacord setup yet, you can also create the invite link manually.
\nIn order to add a bot to your server you need its client id.
\nYou can get your client id from the same page
With this id you can create an invite link for your bot.
\nIf you are the owner or admin of the server, you can use this link to add your bot to your server. Otherwise, you have to give the link to the server owner/admins and ask them to add your bot.
\nTIP
\nUnlike the token, you don't have to keep your client id secret
\nJust use the following link and replace 123456789
with your own client id.
https://discord.com/api/oauth2/authorize?client_id=123456789&scope=applications.commands%20bot&permissions=0
\nYou can calculate the permissions (in the link above it's the 0
) on the page where you created the bot:
You can now open the link and add the bot to your server:
\n\nTIP
\nOnly the owner and admins of a server can invite bots. If you do not own a server yet, it is recommended to create one for testing.
\nThe recommended way to get Javacord is to use a build manager, like Gradle or Maven.
\nIf you are not familiar with build managers, you can follow one of the beginner ide setup guides (see navigation) or\ndownload Javacord directly from GitHub
repositories { mavenCentral() }\ndependencies { implementation 'org.javacord:javacord:$latest-version' }\n
<dependency>\n <groupId>org.javacord</groupId>\n <artifactId>javacord</artifactId>\n <version>$latest-version</version>\n <type>pom</type>\n</dependency>\n
libraryDependencies ++= Seq(\"org.javacord\" % \"javacord\" % \"$latest-version\")\n
Snapshots are automatically deployed from the development
repositories {\n maven {\n url \"https://oss.sonatype.org/content/repositories/snapshots/\"\n }\n}\ndependencies {\n implementation 'org.javacord:javacord:$latest-snapshot-version'\n}\n
<repository>\n <id>snapshots-repo</id>\n <url>https://oss.sonatype.org/content/repositories/snapshots/</url>\n</repository>\n
<dependency>\n <groupId>org.javacord</groupId>\n <artifactId>javacord</artifactId>\n <version>$latest-snapshot-version</version>\n <type>pom</type>\n</dependency>\n
resolvers += \"snapshots-repo\" at \"https://oss.sonatype.org/content/repositories/snapshots/\"\nlibraryDependencies ++= Seq(\"org.javacord\" % \"javacord\" % \"$latest-snapshot-version\")\n
In addition to Javacord, it is also recommended to install a Log4j-2-compatible logging framework.\nA logging framework can be used to provide a more sophisticated logging experience with being able to configure log\nformat, log targets (console, file, database, Discord direct message, ...), log levels per class, and much more.
\nFor example, Log4j Core:
\ndependencies { runtimeOnly 'org.apache.logging.log4j:log4j-core:2.17.0' }\n
<dependency>\n <groupId>org.apache.logging.log4j</groupId>\n <artifactId>log4j-core</artifactId>\n <version>2.17.0</version>\n</dependency>\n
libraryDependencies ++= Seq(\"org.apache.logging.log4j\" % \"log4j-core\" % \"2.17.0\")\n
Take a look at the
Here you will find answers to some of the most asked questions.
\nYou are missing the privileged MESSAGE_CONTENT
intent. For more information of how to enable privileged intents and enable them in your code see
...
in the code examples?You have to replace the ...
with an instance that can be assigned to the datatype seen left.
For example, if you see TextChannel channel = ...
, you have to replace ...
with an instance that is a TextChannel which you can get from the API api.getTextChannelById(CHANNEL_ID)
(note this returns an Optional<TextChannel>) or from an event like messageCreateEvent.getChannel()
.
There are multiple reasons why your code might not work. The most common ones are:
\n.exceptionally(ExceptionLogger.get())
User#getRoles(Server)
do not return the roles of the user. To fix this make sure to add the GUILD_MEMBERS
intent.NoSuchElementException
. Congratulations, you have killed a kitten! You are most likely getting this Exception because you handle Optionals wrong. Read the article on Optionals to learn how to use them correctly.If none of these tips will help you, you can ask your question in our Discord Server
Don't ask:
\nWhy is my code not working?\n//Code\n
Why am I getting Exception X?\n
To ensure all information is provided that is needed to solve your issue, you should ask your question in a format like:
\nI have an issue with: YOUR_ISSUE\nI want to do: WHAT_YOU_WANT_TO_DO\nCurrently this happens: WHAT_HAPPENS_NOW\n\n//Code\n\n//Exception\nThe exception is thrown in the following line(not the number): CODE_LINE\n
While all 3 libraries are Wrappers for the programming language Java, they use different techniques and concepts for their API.
\nnull
.\nchannel.sendMessage("Javacord")
message.getMessageAuthor().asUser().isPresent()
null
if values are not present.\nchannel.sendMessage("JDA").queue()
message.getMember() != null
reactive
approach.\nchannel.createMessage("Pong!").block();
After you have successfully added Javacord as a dependency, created a bot user, and got its token, you are now ready to create your first simple bot! 🎉
\nBy default, all non-privileged intents are enabled. To receive the message content, attachments, components, and embeds you need a special privileged intent MESSAGE_CONTENT
.\nTo enable this privileged intent please see the
Slash Commands
\nGenerally it is recommended to use
Everything starts with the DiscordApiBuilder
class.\nIt is used to create a DiscordApi
object which is the most important class of your bot.
DiscordApi api = new DiscordApiBuilder()\n .setToken(\"<your super secret token>\")\n .addIntents(Intent.MESSAGE_CONTENT)\n .login().join();\n
After executing this code, you should already see your bot online in Discord.\nOf course, just being online is not enough, so let's add some more code!
\nAfter you got your api
instance, let's continue by adding a listener that answers every !ping
message with a simple Pong!
.
api.addMessageCreateListener(event -> {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n});\n
A good place for your code is the main(...)
method that every executable Java program must have.\nYour complete class may look like this:
public class MyFirstBot {\n\n public static void main(String[] args) {\n // Log the bot in\n DiscordApi api = new DiscordApiBuilder()\n .setToken(\"<your super secret token>\")\n .addIntents(Intent.MESSAGE_CONTENT)\n .login().join();\n\n // Add a listener which answers with \"Pong!\" if someone writes \"!ping\"\n api.addMessageCreateListener(event -> {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n });\n }\n\n}\n
Congratulations, that's already everything you have to know for the beginning.\nNow, you can play around a little bit by exploring other listeners and methods.\nOr you just continue reading articles in the Basic Tutorials category.
\n","path":"/wiki/getting-started/writing-your-first-bot.html","keywords":[]},{"title":"Interaction Commands aka. Slash Commands","headers":[{"level":2,"title":"💡 Creating a Command","slug":"creating-a-command","children":[{"level":3,"title":"📔 Notes on creating commands:","slug":"notes-on-creating-commands","children":[]}]},{"level":2,"title":"⤵️ Get your commands","slug":"get-your-commands","children":[]},{"level":2,"title":"🔨 Updating Commands","slug":"updating-commands","children":[]},{"level":2,"title":"✍️ Bulk overwriting commands","slug":"bulk-overwriting-commands","children":[]},{"level":2,"title":"👮♂️ Permissions","slug":"permissions","children":[]},{"level":2,"title":"❗ Limits","slug":"limits","children":[{"level":3,"title":"Registering a command","slug":"registering-a-command","children":[]},{"level":3,"title":"General","slug":"general","children":[]}]}],"content":"INFO
\nThere are a lot of convenient methods which aim to make your life easier with i.e., not\nbeing able to have an invalid configuration of your builder.\nTherefore, the following examples will only show the usage with the convenient methods.
\nINFO
\nThere are 2 different types of Commands:
\ncreateGlobal(DiscordApi)
.createForServer(Server)
.Let's get started with the most basic command, a ping command.
\nSlashCommand command = SlashCommand.with(\"ping\", \"Checks the functionality of this command\")\n .createGlobal(api)\n .join();\n
That's all you have to do!
\nLet's have a look at a more complex command which involves nearly all possibilities:
\nSlashCommand command =\n SlashCommand.with(\"channel\", \"A command dedicated to channels\",\n Arrays.asList(\n SlashCommandOption.createWithOptions(SlashCommandOptionType.SUB_COMMAND_GROUP, \"edit\", \"Edits a channel\",\n Arrays.asList(\n SlashCommandOption.createWithOptions(SlashCommandOptionType.SUB_COMMAND, \"allow\", \"Allows a permission to a user for a channel\",\n Arrays.asList(\n SlashCommandOption.create(SlashCommandOptionType.CHANNEL, \"channel\", \"The channel to modify\", true),\n SlashCommandOption.create(SlashCommandOptionType.USER, \"user\", \"The user which permissions should be changed\", true),\n SlashCommandOption.createWithChoices(SlashCommandOptionType.DECIMAL, \"permission\", \"The permission to allow\", true,\n Arrays.asList(\n SlashCommandOptionChoice.create(\"manage\", 0),\n SlashCommandOptionChoice.create(\"show\", 1)))\n ))))))\n .createGlobal(api)\n .join();\n
Let that sink in first!
\nWhat are we doing here?
\nchannel
.edit
which basically is just a folder where you can put your commands in.allow
which is our actual command. Therefore, our complete argument looks like channel edit allow
.REQUIRED
attributeYou can only mark the last argument as being not required. This means it can be optionally set by the command executor.\nIn the above example you could i.e. set the PERMISSIONS
argument to false
.
Your command has to follow these structures in order to be successfully created:
\nVALID\n\ncommand\n|\n|__ subcommand\n|\n|__ subcommand\n\n----\n\ncommand\n|\n|__ subcommand-group\n |\n |__ subcommand\n|\n|__ subcommand-group\n |\n |__ subcommand\n\n----\n\nVALID\n\ncommand\n|\n|__ subcommand-group\n |\n |__ subcommand\n|\n|__ subcommand\n\n-------\n\nINVALID\n\n\ncommand\n|\n|__ subcommand-group\n |\n |__ subcommand-group\n|\n|__ subcommand-group\n |\n |__ subcommand-group\n\n----\n\nINVALID\n\ncommand\n|\n|__ subcommand\n |\n |__ subcommand-group\n|\n|__ subcommand\n |\n |__ subcommand-group\n
All global commands:
\nSet<SlashCommand> commands = api.getGlobalSlashCommands().join();\n
All commands only available on a single server:
\nServer server = ...;\nSet<SlashCommand> commands = api.getServerSlashCommands(server).join();\n
WARNING
\nGetting all commands from a server only contains the commands you have created on this specific server.\nTherefore, the returned list does not include any global command!
\nWhen updating your commands you only have to include what you actually want to change.\nThe following updater will change the previous created command and change its base name from channel
to channels
.
SlashCommand updatedCommand =\n new SlashCommandUpdater(commandId)\n .setName(\"channels\")\n .updateGlobal(api)\n .join();\n
If you have to update / create multiple commands at once it advised to use the batch updater to only have to do 1 request.
\nDiscordApi api = ...;\n\nSet<SlashCommandBuilder> builders = new HashSet<>();\nbuilders.add(new SlashCommandBuilder().setName(\"server\").setDescription(\"A command for the server\"));\nbuilders.add(new SlashCommandBuilder().setName(\"permission\").setDescription(\"A command for permissions\"));\n \napi.bulkOverwriteGlobalApplicationCommands(builders).join();\n
Permissions exist to enable / disable the usage of your commands for certain things. These things may be:
\nWhen you create a command you can specify which permissions are required to use it.\nIn addition to the required permissions, you can also specify whether the command should be available in DMs.
\nSlashCommand.with(\"ping\",\"Ping!\")\n .setDefaultEnabledForPermissions(PermissionType.ADMINISTRATOR, PermissionType.BAN_MEMBERS)\n //.setDefaultDisabled() Effectively the same as setDefaultEnabledForPermissions(PermissionType.ADMINISTRATOR) but this will lead to the default type by Discord.\n .setEnabledInDms(false)\n .createGlobal(api)\n .join();\n
INFO
\nOnce your bot has been invited to a server, you can not change the permissions afterwards on this server.\nThen it's up to the server administrators / owner to correctly set up the commands for users / roles / channels.
\nComponents are interactive elements like buttons or hidden elements like the ActionRow which use is for displaying the visible components. You can add them to a message and interact with users in a very convenient way.\nCurrently, the only interactive components available at the moment are buttons. They differ in style and behaviour(link redirect) seen in the picture below:\n
\nSending a component with your message is a simple as that:
\nTextChannel channel = ...;\n\nnew MessageBuilder()\n .setContent(\"Click on one of these Buttons!\")\n .addComponents(\n ActionRow.of(Button.success(\"success\", \"Send a message\"),\n Button.danger(\"danger\", \"Delete this message\"),\n Button.secondary(\"secondary\", \"Remind me after 5 minutes\")))\n .send(channel);\n
You simply add a High Level component like an ActionRow which is a container for displaying your components.\nIn turn the ActionRow consist of the components you can interact with like Buttons.
\nThis works for Select Menus as well:
\nTextChannel channel = ...;\n\nnew MessageBuilder()\n .setContent(\"Select an option of this list!\")\n .addComponents(\n ActionRow.of(SelectMenu.create(\"options\", \"Click here to show the options\", 1, 1,\n Arrays.asList(SelectMenuOption.create(\"Option One\", \"You selected Option One!\", \"Click here to select Option One\"),\n SelectMenuOption.create(\"Option Two\", \"You selected Option Two!\", \"Click here to select Option Two\"),\n SelectMenuOption.create(\"Option Three\", \"You selected Option Three!\", \"Click here to select Option Three\")))))\n .send(channel);\n
Interactions are a means of accepting user input through Discord. They have been introduced to provide a more standardized,\ncontrolled way for commands than parsing messages. They can even be used with applications that do not provide a bot user.
\nThe "old" way of doing commands was done through parsed text messages, like !ping
, !userinfo James
or\n!mute James 100s
. While such commands are easy in theory, they come with several problems, such as:
Interactions come in a variety of shapes. The most complex and versatile is the
Context Menu commands
INFO
\nCreation of interactions is detailed on the pages linked in the previous section.
\nUnlike chat message commands, interactions and interaction commands need to be registered with Discord. In order for\na bot's interactions to be available in a server, the bot must be added to the server with the applications.commands
\nOAUTH scope. The scope is included in links created by DiscordApi#createInviteLink
. If your bot is older, it may need to\nbe invited with the new link to add the scope. It is not necessary to remove the bot from the server to do this.
While being more complicated to utilize, interactions have many benefits over pure text commands.
\nWARNING
\nIf a bot replies to a slash command with a public message, the command used, including all parameters, is visible to\nother users.
\nInteractions can used by any application, not only bots. While interactions can also be handled through webhooks,\nJavacord only offers support for dealing with them through the gateway. See the\nDiscord Documentation
WARNING
\nThe methods of handling interactions can not be mixed. If you register a webhook for your interaction commands, the bot\nwill no longer receive any interaction events.
\nThere are many ways to respond to interactions and some are only available for certain interactions.\nThe following will be usable for every interaction.
\nevent.getInteraction()\n .createImmediateResponder()\n .setContent(\"YOUR_RESPONSE\")\n .respond();\n
INFO
\nNote that you have to respond withing 3 seconds, or the command will fail. If you need longer than 3 seconds you have to\nrespond with respondLater()
which allows you to respond within 15 minutes.
Because of this time limitation, sending any files when creating an immediate response is not possible.\nIf you want a file to be embedded either use respondLater
or include a web link in the message content.\nDepending on the media type of the link and the server configuration, Discord will then display an appropriate embed for the file.
When you want to respond ephemerally, you can use the setFlags
method. Your new responder would look like the\nfollowing:
event.getInteraction()\n .createImmediateResponder()\n .setContent(\"YOUR_RESPONSE\")\n .setFlags(MessageFlag.EPHEMERAL)\n .respond();\n
If your computations takes longer than the 3 seconds limit, you can respond later and the Discord Client will show that\nyour bot is thinking until you respond.
\nevent.getInteraction()\n .respondLater()\n .thenAccept(interactionOriginalResponseUpdater -> {\n interactionOriginalResponseUpdater.setContent(\"Update message after some time\").update();\n });\n
You can respond ephemerally when responding later too. For that you have pass a true
boolean to the respondLater
method.
event.getInteraction()\n .respondLater(true)\n .thenAccept(interactionOriginalResponseUpdater -> {\n interactionOriginalResponseUpdater.setContent(\"Update message after some time\").update();\n });\n
Followup messages can be sent within 15 minutes after the command has been invoked. You can send as many followup\nmessages as you want.
\napi.addSlashCommandCreateListener(event -> {\n SlashCommandInteraction slashCommandInteraction = event.getSlashCommandInteraction();\n slashCommandInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> {\n interactionOriginalResponseUpdater.setContent(\"You will receive the answer in a few minutes!\").update();\n\n // time < 15 minutes\n \n slashCommandInteraction.createFollowupMessageBuilder()\n .setContent(\"Thank you for your patience, it took a while but the answer to the universe is 42\")\n .send();\n });\n});\n
A modal is a popup dialog which can be shown when responding to an interaction. It focuses the users to explicitly fill out this form to continue with the workflow.\nCurrently, only the TextInput
(SelectMenu
has been seen working too, but is not yet officially supported) is supported.
api.addMessageComponentCreateListener(event -> {\n event.getInteraction().respondWithModal(\"modalId\",\"Modal Title\",\n ActionRow.of(TextInput.create(TextInputStyle.SHORT, \"text_input_id\", \"This is a Text Input Field\")));\n});\n
Which results in
\n\nFor example, you have created a slash command with the name "settings" and a subcommand "color". If you want to check if\nexactly this command has been used, you can check it as follows:
\napi.addSlashCommandCreateListener(event -> {\n SlashCommandInteraction interaction = event.getSlashCommandInteraction();\n if (interaction.getFullCommandName().equals(\"settings color\")) {\n //Code if command matches the full name\n }\n});\n
api.addAutocompleteCreateListener(event -> {\n event.getAutocompleteInteraction()\n .respondWithChoices(Arrays.asList(\n SlashCommandOptionChoice.create(\"one\", 1),\n SlashCommandOptionChoice.create(\"two\", 2))\n );\n});\n
When dealing with message components, you don't necessarily have to respond or update a message.\nYou can simply acknowledge the interaction and let the user know that the task is done.
\napi.addMessageComponentCreateListener(event -> {\n event.getMessageComponentInteraction().acknowledge();\n});\n
The following code snipped shows how you can respond to the example created in
api.addMessageComponentCreateListener(event -> {\n MessageComponentInteraction messageComponentInteraction = event.getMessageComponentInteraction();\n String customId = messageComponentInteraction.getCustomId();\n\n switch (customId) {\n case \"success\":\n messageComponentInteraction.createImmediateResponder()\n .setContent(\"You clicked a button!\")\n .respond();\n break;\n case \"danger\":\n messageComponentInteraction.getMessage().ifPresent(Message::delete);\n break;\n case \"secondary\":\n messageComponentInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> {\n //Code to respond after 5 minutes\n });\n break;\n case \"options\":\n messageComponentInteraction.createImmediateResponder()\n\t\t\t\t\t.setContent(\"You selected an option in a select menu!\")\n\t\t\t\t\t.respond();\n break;\n }\n});\n
Info
\nWe recommend to use
File
-> New
-> Project
)Maven Project
Next
Create a simple project
Next
com.github.yourname
)myfirstbot
)Finish
pom.xml
filepom.xml
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n <modelVersion>4.0.0</modelVersion>\n\n <groupId>your.package.name</groupId>\n <artifactId>myfirstbot</artifactId>\n <version>1.0-SNAPSHOT</version>\n\n <dependencies>\n <dependency>\n <groupId>org.javacord</groupId>\n <artifactId>javacord</artifactId>\n <version>$latest-version</version>\n <type>pom</type>\n </dependency>\n </dependencies>\n\n</project>\n
src/main/java
folder\n
\n\n
\npackage com.github.yourname.myfirstbot;\n\nimport org.javacord.api.DiscordApi;\nimport org.javacord.api.DiscordApiBuilder;\n\npublic class Main {\n\n public static void main(String[] args) {\n // Insert your bot's token here\n String token = \"your token\";\n\n DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();\n\n // Add a listener which answers with \"Pong!\" if someone writes \"!ping\"\n api.addMessageCreateListener(event -> {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n });\n\n // Print the invite url of your bot\n System.out.println(\"You can invite the bot by using the following url: \" + api.createBotInvite());\n }\n\n}\n
You can run your code by clicking on the small green arrow\n
\n","path":"/wiki/getting-started/setup/eclipse-maven.html","keywords":[]},{"title":"IntelliJ + Gradle","headers":[{"level":2,"title":"🔧 Setup","slug":"setup","children":[]},{"level":2,"title":"🏃♀️ Run the code","slug":"run-the-code","children":[]}],"content":"This tutorial provides a beginner-friendly click by click guide to set up Javacord with Intellij and Gradle.\nIf you are already familiar with IntelliJ and Gradle, you can just see the artifact locations at
File
-> New
-> Project
)Gradle
Next
com.github.yourname
)You can choose whatever you want
\nmyfirstbot
)You can choose whatever you want
\nNext
Use auto-import
Next
Finish
build.gradle
file and open itbuild.gradle
file should now look like thisplugins {\n id 'java'\n}\n\ngroup 'com.github.yourname'\nversion '1.0-SNAPSHOT'\n\nsourceCompatibility = 1.8\n\nrepositories {\n mavenCentral()\n}\n\ndependencies {\n implementation 'org.javacord:javacord:$latest-version'\n}\n
src/main/java
folder\n
\n\n
\nExample code:
\npackage com.github.yourname;\n\nimport org.javacord.api.DiscordApi;\nimport org.javacord.api.DiscordApiBuilder;\n\npublic class Main {\n\n public static void main(String[] args) {\n // Insert your bot's token here\n String token = \"your token\";\n\n DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();\n\n // Add a listener which answers with \"Pong!\" if someone writes \"!ping\"\n api.addMessageCreateListener(event -> {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n });\n\n // Print the invite url of your bot\n System.out.println(\"You can invite the bot by using the following url: \" + api.createBotInvite());\n }\n\n}\n
You can run your code by clicking on the small green arrow\n
\n","path":"/wiki/getting-started/setup/intellij-gradle.html","keywords":[]},{"title":"IntelliJ + Maven","headers":[{"level":2,"title":"🔧 Setup","slug":"setup","children":[]},{"level":2,"title":"🏃♀️ Run the code","slug":"run-the-code","children":[]},{"level":2,"title":"🚧 Possible problems","slug":"possible-problems","children":[]}],"content":"This tutorial provides a beginner-friendly click by click guide to set up Javacord with Intellij and Maven.\nIf you are already familiar with IntelliJ and Maven, you can just see the artifact locations at
Info
\nWe recommend to use
File
-> New
-> Project
)Maven
Next
com.github.yourname
)myfirstbot
)Next
Finish
Enable Auto-Import
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n <modelVersion>4.0.0</modelVersion>\n\n <groupId>your.package.name</groupId>\n <artifactId>myfirstbot</artifactId>\n <version>1.0-SNAPSHOT</version>\n\n <dependencies>\n <dependency>\n <groupId>org.javacord</groupId>\n <artifactId>javacord</artifactId>\n <version>$latest-version</version>\n <type>pom</type>\n </dependency>\n </dependencies>\n\n</project>\n
\n
\n\n
\npackage com.github.yourname;\n\nimport org.javacord.api.DiscordApi;\nimport org.javacord.api.DiscordApiBuilder;\n\npublic class Main {\n\n public static void main(String[] args) {\n // Insert your bot's token here\n String token = \"your token\";\n\n DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();\n\n // Add a listener which answers with \"Pong!\" if someone writes \"!ping\"\n api.addMessageCreateListener(event -> {\n if (event.getMessageContent().equalsIgnoreCase(\"!ping\")) {\n event.getChannel().sendMessage(\"Pong!\");\n }\n });\n\n // Print the invite url of your bot\n System.out.println(\"You can invite the bot by using the following url: \" + api.createBotInvite());\n }\n \n}\n
You can run your code by clicking on the small green arrow\n
\nNote: If you get the following error:\n
\nyou have to change your language level to 1.8
Effective date: October 27, 2018
+ + +Javacord ("us", "we", or "our") operates the https://javacord.org website (the "Service").
+ +This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our Service and the choices you have associated with that data. Our Privacy Policy for Javacord is managed through Free Privacy Policy.
+ +We use your data to provide and improve the Service. By using the Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, accessible from https://javacord.org
+ + +We collect several different types of information for various purposes to provide and improve our Service to you.
+ +While using our Service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to:
+ +We may also collect information how the Service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.
+ +We use cookies and similar tracking technologies to track the activity on our Service and hold certain information.
+Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our Service.
+You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our Service.
+Examples of Cookies we use:
+We use the collected data for various purposes:
+Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.
+If you are located outside Germany and choose to provide information to us, please note that we transfer the data, including Personal Data, to Germany and process it there.
+Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.
+We will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.
+ +We may disclose your Personal Data in the good faith belief that such action is necessary to:
+The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.
+ +We may employ third party companies and individuals to facilitate our Service ("Service Providers"), to provide the Service on our behalf, to perform Service-related services or to assist us in analyzing how our Service is used.
+These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.
+ +We may use third-party Service Providers to monitor and analyze the use of our Service.
+Google Analytics
+Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our Service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.
+You can opt-out of having made your activity on the Service available to Google Analytics by installing the Google Analytics opt-out browser add-on. The add-on prevents the Google Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google Analytics about visits activity.
For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: https://policies.google.com/privacy?hl=en
+Our Service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.
+We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.
+ + +Our Service does not address anyone under the age of 18 ("Children").
+We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.
+ + +We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
+We will let you know via email and/or a prominent notice on our Service, prior to the change becoming effective and update the "effective date" at the top of this Privacy Policy.
+You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
+ + +If you have any questions about this Privacy Policy, please contact us:
+It's important to know the life-cycle of a discord bot to properly handle disconnects. The following state diagram shows the 4 states a bot can have:
The bot is connected to the websocket and receives all events.
The bot is not connected to the websocket and receives no events. It's not uncommon for a bot to occasionally lose connection. This can have various reasons, for example:
The bot will periodically try to resume/reconnect to the websocket. It will start with a small frequency and increase it with every failed reconnect attempt. You can modify the reconnect delay with the DiscordApi#setReconnectDelay(...)
method. The following example code would increase the delay linearly. The 1st attempt would be delayed for 2
seconds, the 2nd attempt for 4
seconds, the 3rd attempts for 6
seconds, ...
api.setReconnectDelay(attempt -> attempt * 2);
+
Important: Bots can only reconnect 1000 times in a 24-hour period (every ~90 seconds). This limit is global and across all shards. Upon hitting this limit, all active sessions for the bot will be terminated, the bot's token will be reset, and you will receive an email notification. This is the reason Javacord increases the reconnect delay with every attempt.
By default, the $default_delay$ formula below is used to calculate the reconnect delay
$$ default_delay(a) = \lfloor a^{1.5} - \frac{a^{1.5}}{\frac{1}{(0.1 \cdot a)} + 1} \rceil $$
with $a$ being the attempt.
The formula will generate the following reconnect delay:
Attempt | Delay |
---|---|
1 | 1 |
2 | 2 |
3 | 4 |
4 | 6 |
5 | 7 |
... | ... |
10 | 16 |
15 | 23 |
20 | 30 |
... | ... |
50 | 59 |
100 | 91 |
150 | 115 |
... | ... |
Resuming is only possible for a short time after being disconnected. If the bot can successfully resume the connection, you will not miss any events. Your bot will receive all events you missed while being disconnected. The cache gets updated accordingly.
If your bot reconnects (not resumes!), the whole cache gets wiped, and you will not receive any missed events.
What does this mean?
Server
, User
, Channel
, ...) will be outdated. This is why you should never store entities, but the id instead. See Entity Cache.For most bots, there's nothing you have to do. All registered listeners are reconnect-resistant, which means if your bot is only reacting to events, it will work fine after a restart. For example, the following code will not be affected by a reconnect (besides maybe some missed !ping
messages):
api.addMessageCreateListener(event -> {
+ if (event.getMessage().getContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+});
+
In case you want to handle reconnects (e.g. fetch the message history to detect missed messages), there are special connection-related listeners which can be used to track the state of the bot:
LostConnectionListener
ReconnectListener
ResumeListener
Javacord keeps an internal cache for entities (e.g. Servers, Channels, Users, ...). It is important to know how the cache behaves to properly use it.
Nearly every entity known by the bot is guaranteed to be in the cache. There are a few exceptions though:
Users are only cached when you have the GUILD_MEMBERS
intent enabled. See Gateway Intents for more information.
Not every single message is in the cache, which means you can encounter messages which exist but are not in the cache. This can happen for most message events, e.g. the ReactionAddEvent
. You can, however, interact with these messages without having them in the cache. Every message event has methods like event.deleteMessage()
, event.editMessage("New Content")
. If you need the message (e.g. to get its content), you can request it using event.requestMessage()
.
Additionally, you can use the static methods in the Message
class which only require the channel and message id, e.g. Message.edit(api, channelId, messageId, "New content");
. This is very useful if you want to store them in a database.
Webhooks and Invites are not kept in the cache at all and won't receive any updates.
Embeds from message.getEmbed()
won't receive updates. If a message's embed gets edited, getEmbed()
will return a completely new embed object.
Javacord's cache exclusively uses websocket events to keep the cache up to date. This means that the content of your objects might be outdated, even though you modified it yourself:
Messages message = ...;
+System.out.println(message.getContent()); // Prints the old content, e.g. "old content"
+message.edit("new content").join(); // Edits the message and waits for success
+System.out.println(message.getContent()); // Still prints "old content"
+Thread.sleep(1000);
+System.out.println(message.getContent()); // Most likely prints "new content" now
+
Even though entities are usually kept in the cache for a very long time, you should not keep references to these objects for a longer period of time, but store the id / use event methods:
// Bad
+Message message = ...;
+message.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("👎")) {
+ message.delete(); // Prevents "message" from being garbage collected
+ }
+});
+
+// Good
+Message message = ...;
+message.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("👎")) {
+ event.deleteMessage(); // Does not use the message object
+ }
+});
+
// Bad
+Set<User> usersWithBadMood = new HashSet<>();
+api.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("😦")) {
+ usersWithBadMood.add(event.getUser());
+ }
+});
+
+// Good
+Set<Long> usersWithBadMood = new HashSet<>();
+api.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("😦")) {
+ usersWithBadMood.add(event.getUser().getId());
+ }
+});
+
Some examples of when cached entities are invalidated:
Channel
, because you left and rejoined a serverBy default, Javacord waits for all servers and members to be loaded on startup. You can disable this behavior in the DiscordApiBuilder
before logging in:
new DiscordApiBuilder()
+ .setToken("abc")
+ .setWaitForServersOnStartup(false)
+ .login()
+ .thenAccept(api -> {
+ // Do something
+ }).exceptionally(ExceptionLogger.get());
+
Depending on the size of your bot, this can significantly speed up the login process. This comes with one downside however: The api.getServers()
collection is empty directly after logging in. You will receive ServerBecomesAvailableEvent
s for every server which finished loading.
In order to reduce memory usage, you can completely disable the message cache or reduce the number of cached messages. By default, Javacord caches up to 50 messages per channel and removes messages from the cache which are older than 12 hours. You can lower this limit by using DiscordApi#setMessageCacheSize(Capacity, StorageTimeInSeconds)
.
// Cache a maximum of 10 messages per channel for and remove messages older than 1 hour
+api.setMessageCacheSize(10, 60*60);
+
You can even set this limit on a per-channel basis:
TextChannel channel = ...;
+channel.getMessageCache().setCapacity(10);
+channel.getMessageCache().setStorageTimeInSeconds(60*60);
+
If you update several settings of an entity (server, channel, ...) at once, you should use the updater for this entity instead of the updateXyz(...)
methods.
// Sends 1 request to Discord
+ServerTextChannel channel = ...;
+new ServerTextChannelUpdater(channel)
+ .setName("example-channel")
+ .setTopic("This is an example channel")
+ .setNsfwFlag(true)
+ .update();
+
instead of
// Sends 3 requests to Discord
+ServerTextChannel channel = ...;
+channel.updateName("example-channel");
+channel.updateTopic("This is an example channel");
+channel.updateNsfwFlag(true);
+
WARNING
Support for audio was added to Javacord very recently. If you encounter any bugs, please create an issue on GitHub!
Javacord allows your bot to connect to voice channels and play audio (e.g., music). This short tutorial gives you an introduction on how to connect to a voice channel and play your favorite music.
Connecting to a voice channel is very straight forward: Calling #connect()
on an instance of ServerVoiceChannel
will connect your bot to this voice channel and return a future with an AudioConnection
object.
The following example will connect the bot to the voice channel of the user that typed !music
in the chat:
ServerVoiceChannel channel = ...;
+channel.connect().thenAccept(audioConnection -> {
+ // Do stuff
+}).exceptionally(e -> {
+ // Failed to connect to voice channel (no permissions?)
+ e.printStackTrace();
+ return null;
+});
+
There are plenty of sources for audio (e.g., YouTube, local files, etc.). The current de facto standard library for extracting audio from these sources with Java is the LavaPlayer library.
To use it with Javacord, you have to add it as a dependency to your project (e.g., with Gradle or Maven) and create a Javacord audio source like this:
public class LavaplayerAudioSource extends AudioSourceBase {
+
+ private final AudioPlayer audioPlayer;
+ private AudioFrame lastFrame;
+
+ /**
+ * Creates a new lavaplayer audio source.
+ *
+ * @param api A discord api instance.
+ * @param audioPlayer An audio player from Lavaplayer.
+ */
+ public LavaplayerAudioSource(DiscordApi api, AudioPlayer audioPlayer) {
+ super(api);
+ this.audioPlayer = audioPlayer;
+ }
+
+ @Override
+ public byte[] getNextFrame() {
+ if (lastFrame == null) {
+ return null;
+ }
+ return applyTransformers(lastFrame.getData());
+ }
+
+ @Override
+ public boolean hasFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean hasNextFrame() {
+ lastFrame = audioPlayer.provide();
+ return lastFrame != null;
+ }
+
+ @Override
+ public AudioSource copy() {
+ return new LavaplayerAudioSource(getApi(), audioPlayer);
+ }
+}
+
With this audio source, you can now start using Lavaplayer, e.g. to play a YouTube video:
// Create a player manager
+AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
+playerManager.registerSourceManager(new YoutubeAudioSourceManager());
+AudioPlayer player = playerManager.createPlayer();
+
+// Create an audio source and add it to the audio connection's queue
+AudioSource source = new LavaplayerAudioSource(api, player);
+audioConnection.setAudioSource(source);
+
+// You can now use the AudioPlayer like you would normally do with Lavaplayer, e.g.,
+playerManager.loadItem("https://www.youtube.com/watch?v=NvS351QKFV4", new AudioLoadResultHandler() {
+ @Override
+ public void trackLoaded(AudioTrack track) {
+ player.playTrack(track);
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist playlist) {
+ for (AudioTrack track : playlist.getTracks()) {
+ player.playTrack(track);
+ }
+ }
+
+ @Override
+ public void noMatches() {
+ // Notify the user that we've got nothing
+ }
+
+ @Override
+ public void loadFailed(FriendlyException throwable) {
+ // Notify the user that everything exploded
+ }
+});
+
There are basically two kinds of proxies: HTTP proxies and SOCKS proxies. Both may or may not support or require authentication depending on version, capabilities, and configuration. Due to the underlying libraries used, currently, Javacord fully supports HTTP proxies and partially supports SOCKS proxies.
Javacord uses HTTPS connections to communicate with the Discord REST API and a WSS connection to communicate with the Discord WebSocket endpoint. Both these protocols are secure protocols and thus do not honor settings for HTTP connections, only settings for HTTPS connections.
If you did not explicitly set a proxy in the DiscordApiBuilder
and did not set a system default ProxySelector
, the default proxy selector of the JRE is used. This proxy selector honors, amongst others, the relevant standard system properties https.proxyHost
, https.proxyPort
, socksProxyHost
, socksProxyPort
, and socksProxyVersion
. Use the former two to configure an HTTP proxy, or the latter three to configure a SOCKS proxy, although you will not need socksProxyVersion
, as SOCKS4 is currently not supported.
You can use java.net.ProxySelector.setDefault(ProxySelector)
to set a system default proxy selector that replaces the default one. In its implementation, you can dynamically determine which proxy to use for each connection.
Using the method DiscordApiBuilder.setProxy(Proxy)
you can set a proxy instance directly in the DiscordApiBuilder
that is solely used for Javacord connections and does not affect the unrelated code running in the JVM.
Using the method DiscordApiBuilder.setProxySelector(ProxySelector)
you can set a proxy selector instance directly in the DiscordApiBuilder
that is solely used for Javacord connections and does not affect the remaining code running in the JVM. In its implementation, you can dynamically determine which proxy to use for each connection.
You can use java.net.Authenticator.setDefault(Authenticator)
to set a system default authenticator that is used to provide username and password pairs for connections. This authenticator is only used if the proxy supports the Basic
authentication scheme. If you need to support any other authentication scheme, use an explicitly configured authenticator. The java.net.Authenticator
interface is too inflexible to support this.
Using the method DiscordApiBuilder.setProxyAuthenticator(Authenticator)
, you can set a custom authenticator that is much more powerful than the java.net.Authenticator
. You get much more information about the connection to be established, and you can return any HTTP header that is necessary for a successful authentication. This should cover all sorts of available authentication mechanisms.
HTTP proxies are fully supported.
SOCKS 4 is currently not supported.
The WebSocket library we use does not support SOCKS proxies at all, and the HTTP library we use has a bug that prevents SOCKS 4 to be used. Additionally, you would need to use at least Java 9 or a separate socket factory supporting SOCKS 4, as the JRE implementation is not working in Java 8 and got fixed only in Java 9+.
SOCKS 4a is currently only partially supported.
The WebSocket library we use does not support SOCKS proxies at all, so it could be used for the REST connections only. Additionally, you would need to use a separate socket factory supporting SOCKS 4a, as the JRE implementation is not capable of doing SOCKS 4a, only SOCKS 4 and SOCKS 5 are supported at the time of creation of this wiki article.
SOCKS 5 is currently only partially supported.
The WebSocket library we use does not support SOCKS proxies at all, so it could be used for the REST connections only.
Ratelimits is a Discord restriction which prevents you from performing actions in a very fast rate. Most ratelimits are on a per-channel or a per-server basis.
Action | Ratelimit | Type |
---|---|---|
Send Messages | 5 / 5s | per channel |
Delete Messages | 5 / 1s | per channel |
Add/Remove Reactions | 1 / 0.25s | per channel |
Edit Server Members | 10 / 10s | per server |
Edit Member Nickname | 1 / 1s | per server |
Edit Bot Username | 2 / 1h | per account |
Update Channels | 2 / 10m | per account |
All Actions Combined | 50 / 1s | per account |
Usually Javacord takes care about these limitations for you. As a user, there's nothing you have to do, but you should at least know that ratelimits exist.
The following code
// Who even needs loops?
+channel.sendMessage("Ratelimit Example #1");
+channel.sendMessage("Ratelimit Example #2");
+channel.sendMessage("Ratelimit Example #3");
+channel.sendMessage("Ratelimit Example #4");
+channel.sendMessage("Ratelimit Example #5");
+channel.sendMessage("Ratelimit Example #6");
+channel.sendMessage("Ratelimit Example #7");
+channel.sendMessage("Ratelimit Example #8");
+channel.sendMessage("Ratelimit Example #9");
+channel.sendMessage("Ratelimit Example #10");
+channel.sendMessage("Ratelimit Example #11");
+channel.sendMessage("Ratelimit Example #12");
+
would look like this in the client:
You can clearly see the delay between every 5 sent messages.
No. Ratelimits are a limitation from Discord itself, which you cannot circumvent.
Discord allows (and forces) you to "split" larger bots into several independent parts. This behavior is called "sharding", and the independent parts are called "shards". You can think of shards as completely independent bots. Every shard is responsible for a disjoint set of servers.
Logging in with a single shard is pretty much the same as logging in without sharding:
DiscordApi api = new DiscordApiBuilder()
+ .setToken("top secret")
+ .setCurrentShard(0)
+ .setTotalShards(2)
+ .login().join();
+System.out.println("Shard " + api.getCurrentShard() + " logged in!");
+
Note:
current shard
starts counting at0
! This means in the example above you would have current shard0
and shard1
with atotal amount
of2
shards.
Important: There must be a > 5-second delay between each shard-login
You can manually set a fixed amount of total shards and log in all of them:
public class Main {
+
+ public static void main(String[] args) {
+ new DiscordApiBuilder()
+ .setToken("top secret")
+ .setTotalShards(10)
+ .loginAllShards()
+ .forEach(shardFuture -> shardFuture
+ .thenAcceptAsync(Main::onShardLogin)
+ .exceptionally(ExceptionLogger.get())
+ );
+ }
+
+ private static void onShardLogin(DiscordApi api) {
+ System.out.println("Shard " + api.getCurrentShard() + " logged in!");
+ // You can treat the shard like a normal bot account, e.g. registering listeners
+ api.addMessageCreateListener(event -> {
+ // ...
+ });
+ }
+
+}
+
loginAllShards()
returns a collection with completable futures (Collection<CompletableFuture<DiscordApi>>
). This method automatically obeys the > 5-second delay rule.
You can "ask" Discord to recommend you a total amount of shards. This is done by using the DiscordApiBuilder#setRecommendedTotalShards()
method, which returns a CompletableFuture<DiscordApiBuilder>
after getting the required information.
public class Main {
+
+ public static void main(String[] args) {
+ new DiscordApiBuilder()
+ .setToken("top secret")
+ .setRecommendedTotalShards().join()
+ .loginAllShards()
+ .forEach(shardFuture -> shardFuture
+ .thenAccept(Main::onShardLogin)
+ .exceptionally(ExceptionLogger.get())
+ );
+ }
+
+ private static void onShardLogin(DiscordApi api) {
+ // ...
+ }
+
+}
+
You can calculate for which servers a shard is responsible using the server id:
boolean isResponsible = (serverId >> 22) % totalShards == currentShard;
+
Private messages are always sent to the first shard (currentShard == 0
).
Sharding is forced for bots which are in more than 2500 servers.
Sharding for very large bots (> 150,000 servers) is a bit different from "normal" sharding. Discord will contact you once your bot reaches this state. Additional information can be found in the official Discord api documentation.
Javacord provides XyzBuilder
classes to create new Discord entities like channels, webhooks, servers, and many more.
You can get the channel builders for a specific server using the Server#createXyzChannelBuilder
or by directly calling the constructor. Creating a ServerVoiceChannel
would look like this:
Server server = ...;
+ServerVoiceChannel channel = new ServerVoiceChannelBuilder(server)
+ .setName("example-channel")
+ .setUserlimit(10)
+ .create().join();
+
You can get the WebhookBuilder
for a specific text channel:
ServerTextChannel channel = ...;
+Webhook webhook = new WebhookBuilder(channel)
+ .setName("Captain Hook")
+ .setAvatar(new File("C:/Users/Bastian/Pictures/puppy.jpg"))
+ .create().join();
+
You can get the InviteBuilder
for a specific server channel:
ServerTextChannel channel = ...;
+Invite invite = new InviteBuilder(channel)
+ .setMaxAgeInSeconds(60*60*24)
+ .setMaxUses(42)
+ .create().join();
+
You can get the ServerBuilder
from the current api instance:
DiscordApi api = ...;
+long serverId = new ServerBuilder(api)
+ .setName("My Awesome Server")
+ .setIcon(api.getYourself().getAvatar())
+ .setVerificationLevel(VerificationLevel.HIGH)
+ .setDefaultMessageNotificationLevel(DefaultMessageNotificationLevel.ONLY_MENTIONS)
+ .setRegion(Region.EU_CENTRAL)
+ .create().join();
+
WARNING
By default, bots can only create servers if they are in less than 10 servers. You can contact the Discord support to request a higher limit.
Embeds are attached to messages and have a special design. The usually look like this:
Javacord provides an EmbedBuilder
which can be used to create embeds:
// Create the embed
+EmbedBuilder embed = new EmbedBuilder()
+ .setTitle("Title")
+ .setDescription("Description")
+ .setAuthor("Author Name", "http://google.com/", "https://cdn.discordapp.com/embed/avatars/0.png")
+ .addField("A field", "Some text inside the field")
+ .addInlineField("An inline field", "More text")
+ .addInlineField("Another inline field", "Even more text")
+ .setColor(Color.BLUE)
+ .setFooter("Footer", "https://cdn.discordapp.com/embed/avatars/1.png")
+ .setImage(new File("C:/Users/Bastian/Pictures/puppy.jpg"))
+ .setThumbnail(new File("C:/Users/Bastian/Pictures/kitten2.png"));
+// Send the embed
+channel.sendMessage(embed);
+
By default, Discord expects embed images to be a link (e.g., the image link used in setFooter(...)
), but you can also use attachments for images. If you provide a non-url image source (e.g. the puppy.jpg
file used in setImage(...)
), Javacord automatically uploads them as an attachment to the message and uses this attachment for the embed.
Type | Limit |
---|---|
Title | 256 characters |
Description | 4096 characters |
Field Amount | Up to 25 fields |
Field Name | 256 characters |
Field Value | 1024 characters |
Footer Text | 2048 characters |
Author Name | 256 characters |
In addition to the limits above, the sum of all characters in an embed structure must not exceed 6000 characters.
setAuthor(...)
?.setAuthor("Author Name", "http://google.com/", "https://cdn.discordapp.com/embed/avatars/0.png")
+
null
.Normal fields always start in a new line, whereas several inline fields can be in the same line.
No, Discord does not allow different embed layouts.
Discord allows for a subset of markdown to be used. See their docs for the specifics.
There are two different kinds of emojis in Discord: Unicode emojis and custom emojis.
Unicode emojis are "normal" text emojis which are supported by (nearly) all chat clients, including Discord. You can find a list with all Unicode emojis here: Full Emoji List.
You can either directly add them in your code, e.g.
channel.sendMessage("Hi! 😃");
+
or use the normal "tag" like you would in the Client:
channel.sendMessage("Hi! :smiley:");
+
Adding unicode reactions is only possible by using the "real" reaction. It doesn't support tags like :smiley:
.
message.addReaction("😃"); // works
+message.addReaction(":smiley:"); // doesn't work
+
Custom emojis are emojis that are created in a server. You can get all custom emojis the bot knows by using DiscordApi#getCustomEmojis()
.
To use custom emojis, you have to know its "tag", which has the format <:name:id>
. You can get it by calling CustomEmoji#getMentionTag()
:
channel.sendMessage("Hi! <:javacord:415465982715494402>");
+
CustomEmoji emoji = ...;
+channel.sendMessage("Hi! " + emoji.getMentionTag());
+
You can either directly use the custom emoji object or use the tag without the <:
>
if you don't have access a custom emoji object (e.g., because it's from a different shard):
CustomEmoji emoji = ...;
+message.addReaction(emoji);
+
message.addReaction("javacord:415465982715494402");
+
Just add a \
in front of the emoji and press Enter
In Javacord, all Emojis are a child of the Emoji
interface:
Known custom emojis are emojis that the bot knows because it's a member of the server with this emoji. A custom emoji can be unknown if someone adds a reaction with an unknown emoji for example. A KnownCustomEmoji
has additional methods like getServer()
or updateName(String)
.
If you are working a lot with Unicode emojis, it's recommended to use a library like JEmoji. It enables you to do things like the following:
message.addReaction(EmojiManager.getByAlias(":thumbsup:"));
+
Discord allows you to "subscribe" to specific groups of events. These "subscriptions" are called intent. Disabling intents that are not required for your bot can significantly increase your bot's performance.
Below you can find a table with all intents supported by Discord.
Intent | Safe to Disable | Privileged |
---|---|---|
GUILDS | ❌ | ❌ |
GUILD_MEMBERS | ✔️ | ✔️ |
GUILD_BANS | ⚠️* | ❌ |
GUILD_EMOJIS | ⚠️* | ❌ |
GUILD_INTEGRATIONS | ✔️ | ❌ |
GUILD_WEBHOOKS | ✔️ | ❌ |
GUILD_INVITES | ✔️ | ❌ |
GUILD_VOICE_STATES | ⚠️* | ❌ |
GUILD_PRESENCES | ✔️ | ✔️ |
GUILD_MESSAGES | ✔️ | ❌ |
GUILD_MESSAGE_REACTIONS | ✔️ | ❌ |
GUILD_MESSAGE_TYPING | ✔️ | ❌ |
DIRECT_MESSAGES | ✔️ | ❌ |
DIRECT_MESSAGE_REACTIONS | ✔️ | ❌ |
DIRECT_MESSAGE_TYPING | ✔️ | ❌ |
MESSAGE_CONTENT | ✔️ | ✔️ |
AUTO_MODERATION_CONFIGURATION | ✔️ | ❌ |
AUTO_MODERATION_EXECUTION | ✔️ | ❌ |
* Will most likely work, but needs further testing
Good to know!
Guild is a synonym for servers, commonly used in Discord's API. See Glossary.
When you disable some of the listed intents, Javacord will not fire events that belong to the intents and will not update these specific parts of the cache.
At the moment, we don't have a list which events are affected by which intents (but it will come soon™️). However, most intents should be self-explanatory. E.g. when you disable the DIRECT_MESSAGES
intent, your bot will not receive any private messages.
Some intents are defined as "privileged" due to the sensitive nature of the data. To use these intents, you have to go to your bot in the Developer Portal (where you created bot) and manually enable the intents:
There are some additionally restrictions for bots that are in over 100 servers:
Take a look at the official article from Discord about this topic and how to verify your bot: Bot Verification and Data Whitelisting.
The following two intents are especially noteworthy: GUILD_MEMBERS
and GUILD_PRESENCES
. Besides being privileged, they have some special implications for Javacord:
GUILD_PRESENCES
This intent is required to get updates about a user's status (i.e., if they are online, what game they are playing, ...). Additionally, without this intent it might take considerably longer to cache all users because of ratelimits (up to 10 minutes for shards with 1000 servers). It is advised against setting DiscordApiBuilder#setWaitForAllUsersOnStartup(true)
without this intent, unless absolutely necessary.
GUILD_MEMBERS
This intent is required to keep all users in Javacord's cache. Without this intent, methods like Server#getMembers()
or DiscordApi#getCachedUsers()
will return empty collections. However, you will still be able to access users from objects like messages, e.g. Message#getUserAuthor()
will still work.
MESSAGE_CONTENT
This intent is a bit different to the other as it does not act as a toggle to receive any events. It's sole purpose is to receive the message content, attachments, components, and embeds. Otherwise, these fields will be empty when you receive a Message
object.
Javacord allows you to specify intents in the DiscordApiBuilder
prior to login. There are many options to set intents. The following example code shows the most common ones:
This method enables all non-privileged intents. This is the default setting in Javacord.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllNonPrivilegedIntents()
+ .login()
+ .join();
+
This method enabled all non-privileged intents, except the given ones.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllNonPrivilegedIntentsExcept(Intent.GUILD_WEBHOOKS)
+ .login()
+ .join();
+
This method enabled all intents.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllIntents()
+ .login()
+ .join();
+
This method enabled all intents, except the given ones.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setAllIntentsExcept(Intent.GUILD_PRESENCES, Intent.GUILD_WEBHOOKS)
+ .login()
+ .join();
+
This method only enables the given intents.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .setIntents(Intent.GUILDS, Intent.DIRECT_MESSAGES)
+ .login()
+ .join();
+
This method adds the intents to the currently enabled ones(by default all non-privileged). This is useful i.e. if you only want to enable 1 privileged intent like the MESSAGE_CONTENT
DiscordApi api = new DiscordApiBuilder()
+ .setToken("topc secret")
+ .addIntents(Intent.MESSAGE_CONTENT)
+ .login()
+ .join();
+
This is a list with the most common Discord-related terms:
Guild
- A synonym for server
Selfbot
- A client account bot, usually logged in to a user's own accountSharding
- Splitting a bot into several independent shards
, see ShardingToken
- Used to login instead of requiring a username + passwordEmbed
- A "fancy" message, see Embed FAQRatelimit
- Prevents you from spamming actions, see Ratelimit FAQWebsocket
- A TCP "connection" to Discord that receives events, see WikipediaGateway
- The address for the websocket
Rest
/ Rest Request
- REST is used to perform actions like sending messages. Rest Requests do not require an active websocket connection.Activity
- The text underneath the username, usually Playing Xyz
Rich Presence
- A more detailed activity, see Discord DocsINFO
There are a lot of convenient methods which aim to make your life easier with i.e., not being able to have an invalid configuration of your builder. Therefore, the following examples will only show the usage with the convenient methods.
INFO
There are 2 different types of Commands:
createGlobal(DiscordApi)
.createForServer(Server)
.Let's get started with the most basic command, a ping command.
SlashCommand command = SlashCommand.with("ping", "Checks the functionality of this command")
+ .createGlobal(api)
+ .join();
+
That's all you have to do!
Let's have a look at a more complex command which involves nearly all possibilities:
SlashCommand command =
+ SlashCommand.with("channel", "A command dedicated to channels",
+ Arrays.asList(
+ SlashCommandOption.createWithOptions(SlashCommandOptionType.SUB_COMMAND_GROUP, "edit", "Edits a channel",
+ Arrays.asList(
+ SlashCommandOption.createWithOptions(SlashCommandOptionType.SUB_COMMAND, "allow", "Allows a permission to a user for a channel",
+ Arrays.asList(
+ SlashCommandOption.create(SlashCommandOptionType.CHANNEL, "channel", "The channel to modify", true),
+ SlashCommandOption.create(SlashCommandOptionType.USER, "user", "The user which permissions should be changed", true),
+ SlashCommandOption.createWithChoices(SlashCommandOptionType.DECIMAL, "permission", "The permission to allow", true,
+ Arrays.asList(
+ SlashCommandOptionChoice.create("manage", 0),
+ SlashCommandOptionChoice.create("show", 1)))
+ ))))))
+ .createGlobal(api)
+ .join();
+
Let that sink in first!
What are we doing here?
channel
.edit
which basically is just a folder where you can put your commands in.allow
which is our actual command. Therefore, our complete argument looks like channel edit allow
.REQUIRED
attributeYou can only mark the last argument as being not required. This means it can be optionally set by the command executor. In the above example you could i.e. set the PERMISSIONS
argument to false
.
Your command has to follow these structures in order to be successfully created:
VALID
+
+command
+|
+|__ subcommand
+|
+|__ subcommand
+
+----
+
+command
+|
+|__ subcommand-group
+ |
+ |__ subcommand
+|
+|__ subcommand-group
+ |
+ |__ subcommand
+
+----
+
+VALID
+
+command
+|
+|__ subcommand-group
+ |
+ |__ subcommand
+|
+|__ subcommand
+
+-------
+
+INVALID
+
+
+command
+|
+|__ subcommand-group
+ |
+ |__ subcommand-group
+|
+|__ subcommand-group
+ |
+ |__ subcommand-group
+
+----
+
+INVALID
+
+command
+|
+|__ subcommand
+ |
+ |__ subcommand-group
+|
+|__ subcommand
+ |
+ |__ subcommand-group
+
All global commands:
Set<SlashCommand> commands = api.getGlobalSlashCommands().join();
+
All commands only available on a single server:
Server server = ...;
+Set<SlashCommand> commands = api.getServerSlashCommands(server).join();
+
WARNING
Getting all commands from a server only contains the commands you have created on this specific server. Therefore, the returned list does not include any global command!
When updating your commands you only have to include what you actually want to change. The following updater will change the previous created command and change its base name from channel
to channels
.
SlashCommand updatedCommand =
+ new SlashCommandUpdater(commandId)
+ .setName("channels")
+ .updateGlobal(api)
+ .join();
+
If you have to update / create multiple commands at once it advised to use the batch updater to only have to do 1 request.
DiscordApi api = ...;
+
+Set<SlashCommandBuilder> builders = new HashSet<>();
+builders.add(new SlashCommandBuilder().setName("server").setDescription("A command for the server"));
+builders.add(new SlashCommandBuilder().setName("permission").setDescription("A command for permissions"));
+
+api.bulkOverwriteGlobalApplicationCommands(builders).join();
+
Permissions exist to enable / disable the usage of your commands for certain things. These things may be:
When you create a command you can specify which permissions are required to use it. In addition to the required permissions, you can also specify whether the command should be available in DMs.
SlashCommand.with("ping","Ping!")
+ .setDefaultEnabledForPermissions(PermissionType.ADMINISTRATOR, PermissionType.BAN_MEMBERS)
+ //.setDefaultDisabled() Effectively the same as setDefaultEnabledForPermissions(PermissionType.ADMINISTRATOR) but this will lead to the default type by Discord.
+ .setEnabledInDms(false)
+ .createGlobal(api)
+ .join();
+
INFO
Once your bot has been invited to a server, you can not change the permissions afterwards on this server. Then it's up to the server administrators / owner to correctly set up the commands for users / roles / channels.
Components are interactive elements like buttons or hidden elements like the ActionRow which use is for displaying the visible components. You can add them to a message and interact with users in a very convenient way. Currently, the only interactive components available at the moment are buttons. They differ in style and behaviour(link redirect) seen in the picture below:
Sending a component with your message is a simple as that:
TextChannel channel = ...;
+
+new MessageBuilder()
+ .setContent("Click on one of these Buttons!")
+ .addComponents(
+ ActionRow.of(Button.success("success", "Send a message"),
+ Button.danger("danger", "Delete this message"),
+ Button.secondary("secondary", "Remind me after 5 minutes")))
+ .send(channel);
+
You simply add a High Level component like an ActionRow which is a container for displaying your components. In turn the ActionRow consist of the components you can interact with like Buttons.
This works for Select Menus as well:
TextChannel channel = ...;
+
+new MessageBuilder()
+ .setContent("Select an option of this list!")
+ .addComponents(
+ ActionRow.of(SelectMenu.create("options", "Click here to show the options", 1, 1,
+ Arrays.asList(SelectMenuOption.create("Option One", "You selected Option One!", "Click here to select Option One"),
+ SelectMenuOption.create("Option Two", "You selected Option Two!", "Click here to select Option Two"),
+ SelectMenuOption.create("Option Three", "You selected Option Three!", "Click here to select Option Three")))))
+ .send(channel);
+
Interactions are a means of accepting user input through Discord. They have been introduced to provide a more standardized, controlled way for commands than parsing messages. They can even be used with applications that do not provide a bot user.
The "old" way of doing commands was done through parsed text messages, like !ping
, !userinfo James
or !mute James 100s
. While such commands are easy in theory, they come with several problems, such as:
Interactions come in a variety of shapes. The most complex and versatile is the command interaction, which allows for commands directed at a particular bot with information and assistance on subcommands and parameters being integrated into the discord client.
Context Menu commands are available from the context menu in the client either on a message or a server member.
Message components come in the flavor of buttons, select menus and other form elements and can be attached directly to a message.
INFO
Creation of interactions is detailed on the pages linked in the previous section.
Unlike chat message commands, interactions and interaction commands need to be registered with Discord. In order for a bot's interactions to be available in a server, the bot must be added to the server with the applications.commands
OAUTH scope. The scope is included in links created by DiscordApi#createInviteLink
. If your bot is older, it may need to be invited with the new link to add the scope. It is not necessary to remove the bot from the server to do this.
While being more complicated to utilize, interactions have many benefits over pure text commands.
WARNING
If a bot replies to a slash command with a public message, the command used, including all parameters, is visible to other users.
Interactions can used by any application, not only bots. While interactions can also be handled through webhooks, Javacord only offers support for dealing with them through the gateway. See the Discord Documentation for more information.
WARNING
The methods of handling interactions can not be mixed. If you register a webhook for your interaction commands, the bot will no longer receive any interaction events.
There are many ways to respond to interactions and some are only available for certain interactions. The following will be usable for every interaction.
event.getInteraction()
+ .createImmediateResponder()
+ .setContent("YOUR_RESPONSE")
+ .respond();
+
INFO
Note that you have to respond withing 3 seconds, or the command will fail. If you need longer than 3 seconds you have to respond with respondLater()
which allows you to respond within 15 minutes.
Because of this time limitation, sending any files when creating an immediate response is not possible. If you want a file to be embedded either use respondLater
or include a web link in the message content. Depending on the media type of the link and the server configuration, Discord will then display an appropriate embed for the file.
When you want to respond ephemerally, you can use the setFlags
method. Your new responder would look like the following:
event.getInteraction()
+ .createImmediateResponder()
+ .setContent("YOUR_RESPONSE")
+ .setFlags(MessageFlag.EPHEMERAL)
+ .respond();
+
If your computations takes longer than the 3 seconds limit, you can respond later and the Discord Client will show that your bot is thinking until you respond.
event.getInteraction()
+ .respondLater()
+ .thenAccept(interactionOriginalResponseUpdater -> {
+ interactionOriginalResponseUpdater.setContent("Update message after some time").update();
+ });
+
You can respond ephemerally when responding later too. For that you have pass a true
boolean to the respondLater
method.
event.getInteraction()
+ .respondLater(true)
+ .thenAccept(interactionOriginalResponseUpdater -> {
+ interactionOriginalResponseUpdater.setContent("Update message after some time").update();
+ });
+
Followup messages can be sent within 15 minutes after the command has been invoked. You can send as many followup messages as you want.
api.addSlashCommandCreateListener(event -> {
+ SlashCommandInteraction slashCommandInteraction = event.getSlashCommandInteraction();
+ slashCommandInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> {
+ interactionOriginalResponseUpdater.setContent("You will receive the answer in a few minutes!").update();
+
+ // time < 15 minutes
+
+ slashCommandInteraction.createFollowupMessageBuilder()
+ .setContent("Thank you for your patience, it took a while but the answer to the universe is 42")
+ .send();
+ });
+});
+
A modal is a popup dialog which can be shown when responding to an interaction. It focuses the users to explicitly fill out this form to continue with the workflow. Currently, only the TextInput
(SelectMenu
has been seen working too, but is not yet officially supported) is supported.
api.addMessageComponentCreateListener(event -> {
+ event.getInteraction().respondWithModal("modalId","Modal Title",
+ ActionRow.of(TextInput.create(TextInputStyle.SHORT, "text_input_id", "This is a Text Input Field")));
+});
+
Which results in
For example, you have created a slash command with the name "settings" and a subcommand "color". If you want to check if exactly this command has been used, you can check it as follows:
api.addSlashCommandCreateListener(event -> {
+ SlashCommandInteraction interaction = event.getSlashCommandInteraction();
+ if (interaction.getFullCommandName().equals("settings color")) {
+ //Code if command matches the full name
+ }
+});
+
api.addAutocompleteCreateListener(event -> {
+ event.getAutocompleteInteraction()
+ .respondWithChoices(Arrays.asList(
+ SlashCommandOptionChoice.create("one", 1),
+ SlashCommandOptionChoice.create("two", 2))
+ );
+});
+
When dealing with message components, you don't necessarily have to respond or update a message. You can simply acknowledge the interaction and let the user know that the task is done.
api.addMessageComponentCreateListener(event -> {
+ event.getMessageComponentInteraction().acknowledge();
+});
+
The following code snipped shows how you can respond to the example created in Components.
api.addMessageComponentCreateListener(event -> {
+ MessageComponentInteraction messageComponentInteraction = event.getMessageComponentInteraction();
+ String customId = messageComponentInteraction.getCustomId();
+
+ switch (customId) {
+ case "success":
+ messageComponentInteraction.createImmediateResponder()
+ .setContent("You clicked a button!")
+ .respond();
+ break;
+ case "danger":
+ messageComponentInteraction.getMessage().ifPresent(Message::delete);
+ break;
+ case "secondary":
+ messageComponentInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> {
+ //Code to respond after 5 minutes
+ });
+ break;
+ case "options":
+ messageComponentInteraction.createImmediateResponder()
+ .setContent("You selected an option in a select menu!")
+ .respond();
+ break;
+ }
+});
+
Creating listeners is extremely easy in Javacord. You can either use Java 8's lambda expressions to register listeners inline or just create a new class for them, if an inline listener would get too messy.
api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+});
+
api.addListener(new MyListener());
+
and
public class MyListener implements MessageCreateListener {
+
+ @Override
+ public void onMessageCreate(MessageCreateEvent event) {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ }
+
+}
+
Sometimes it might be useful to add listeners before calling the DiscordApiBuilder#login()
method.
DiscordApi api = new DiscordApiBuilder()
+ // An inline listener
+ .addMessageCreateListener(event -> {
+ Message message = event.getMessage();
+ if (message.getContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ })
+ .addServerBecomesAvailableListener(event -> {
+ System.out.println("Loaded " + event.getServer().getName());
+ })
+ // A listener in their own class
+ .addListener(new MyListener())
+ // Alternative syntax that can be used for classes that require a DiscordApi parameter in their constructor
+ .addListener(MyListener::new)
+ .setToken("top secret")
+ .setWaitForServersOnStartup(false)
+ .login()
+ .join();
+
Note: In most cases, it's enough to add listeners after logging in
Another cool feature is the ability to attach listeners directly to objects. An example where this can be useful is, for example, reacting to reactions. The following code would delete the message if someone adds a 👎 reaction.
message.addReactionAddListener(event -> {
+ if (event.getEmoji().equalsEmoji("👎")) {
+ event.deleteMessage();
+ }
+}).removeAfter(30, TimeUnit.MINUTES);
+
Seems like the bot is very sensitive to criticism.
There are two ways to remove a listener:
Every time you register a listener, a ListenerManager
is returned which can be used to unregister the listener:
ListenerManager<MessageCreateListener> listenerManager = api.addMessageCreateListener(event -> {
+ // Do stuff
+});
+
+listenerManager.remove();
+
This manager also has some utility methods. You can, for example, remove a listener after a given time, which can be useful for object listeners:
message.addReactionAddListener(event -> {
+ // Do stuff
+}).removeAfter(30, TimeUnit.MINUTES);
+
removeListener(...)
methodYou can remove any listener using the removeListener(...)
method:
MyListener listener = new MyListener();
+api.addListener(listener);
+// ...
+api.removeListener(listener);
+
Logging is an important tool to keep track of what is going on in your application. Javacord uses the Log4j 2 API, which allows you to use your favorite logging framework to log messages in your own code and have all logging messages end up in the same destination. In case you do not add your own logging framework, a fallback logger is used that logs to the console.
If you want more control, add a proper logging framework that supports your needs and configure it accordingly. You can for example configure log messages on a per-class level, change log levels during runtime, or log to a file or database.
Javacord's fallback logger is a simple Log4j logger which always logs INFO
level and higher. It allows you to enable DEBUG
and TRACE
logging manually. As log levels are hierarchical, enabling TRACE
will also implicitly enable DEBUG
, and disabling DEBUG
will also implicitly disable TRACE
.
// Enable debug logging
+FallbackLoggerConfiguration.setDebug(true);
+
+// Enable trace logging
+FallbackLoggerConfiguration.setTrace(true);
+
Changing the log level of the fallback logger only affects newly created loggers. Pre-existing loggers will not have their log level changed. So if you want to configure the fallback logger, you should do this as one of the first actions in your bot code. If you want to change log levels during runtime, you should use a proper logging framework like Log4j 2 Core or another library that supports this.
All fallback logger messages are printed to the standard output stream (System.out
) and thus usually to your console. If you want to log to a file, database, or anything else, you should consider using a proper logging framework which allows you to configure this behavior.
This is how a log line from the fallback logger will look like:
<time with date ><level><logger name, usually the logging class > <message > <the thread context, here the shard number>
+2018-08-03 20:00:06.080+0200 DEBUG org.javacord.core.util.gateway.DiscordWebSocketAdapter Received HELLO packet {shard=0}
+
Adding a logging framework of your choice is very straightforward. You can just add it as a dependency, and it will be detected by Log4j automatically. The following example adds Log4j 2 using Gradle:
dependencies { runtimeOnly 'org.apache.logging.log4j:log4j-core:2.17.0' }
+
You can also use an SLF4J compatible logging framework using log4j-to-slf4j
. The following example adds Logback Classic using Gradle:
dependencies {
+ runtimeOnly 'org.apache.logging.log4j:log4j-to-slf4j:2.17.0'
+ runtimeOnly 'ch.qos.logback:logback-classic:1.2.3'
+}
+
Javacord adds the relevant shard to each log message. The facility that stores this information has a different name depending on which logging framework you use. For Log4j 2, this is called Thread Context Map and can be added in a pattern layout with %X{shard}
, or you can add the whole thread context map by using %X
. For Logback Classic, it is called MDC and can be added with the same pattern expressions as for Log4j.
The MessageBuilder
class is a more powerful alternative to the TextChannel#sendMessage(...)
method.
It can be used to construct more complex messages and supports some additional features that are not possible with a simple TextChannel#sendMessage(...)
call.
The following code
new MessageBuilder()
+ .append("Look at these ")
+ .append("awesome", MessageDecoration.BOLD, MessageDecoration.UNDERLINE)
+ .append(" animal pictures! 😃")
+ .appendCode("java", "System.out.println(\"Sweet!\");")
+ .addAttachment(new File("C:/Users/Bastian/Pictures/kitten.jpg"))
+ .addAttachment(new File("C:/Users/Bastian/Pictures/puppy.jpg"))
+ .setEmbed(new EmbedBuilder()
+ .setTitle("WOW")
+ .setDescription("Really cool pictures!")
+ .setColor(Color.ORANGE))
+ .send(channel);
+
will be displayed like this:
The allowed mentions object lets you control what should be mentioned (pinged) in a message if it contains mentions.
The following code will ping:
And will not ping:
AllowedMentions allowedMentions = new AllowedMentionsBuilder()
+ .addUser(user0.getId())
+ .setMentionRoles(true)
+ .setMentionEveryoneAndHere(false)
+ .build();
+
+ new MessageBuilder()
+ .setAllowedMentions(allowedMentions)
+ .append(user0.getMentionTag())
+ .append(user1.getMentionTag())
+ .append(role.getMentionTag())
+ .append(role2.getMentionTag())
+ .append("@everyone")
+ .send(channel);
+
If you add a user to the mentions object and set setMentionUsers(true)
it will ping every mentioned user. The same applies for setMentionRoles(true)
If you took the time to write a bot, at some point you'll also want to run it, either for use in production or for debugging from the IDE.
While developing your bot, you will want to run your bot directly from the IDE in order to quickly test changes and new features. For this, create a Run/Debug Configuration in your IDE of choice with your bot's main class. Remember to also add any necessary parameters and environment variables.
A working Run/Debug configuration will also enable you to run your bot with a debugger. A debugger is often considered a developer's most important tool, so make sure to familiarize yourself with the debugging integration for your IDE of choice.
This assumes your project is set up correctly, preferably with Gradle, can be built without errors, and does not yet have any run/debug configurations.
1. Locate and click the Add Configuration...
button in the top bar next to the start button.
2. In the newly opened window, click the +
button in the top left and select Application
3. Give a name for your configuration and select the module to use the classpath of (usually yourproject.main
).
4. Select your Main class. Use the ...
button to search for it or provide the fully qualified name. If it can not be found, you most likely selected the wrong module in step 3.
5. Optional: Set command line arguments and environment variables. For the environment variables, use the button to the right of the input field for a more convenient input window.
6. Click Apply
to finalize the configuration, then OK
to close the window.
7. Select your configuration in the drop-down menu and run or debug it with the buttons to the right.
This assumes your project is set up correctly, can be built without errors, and does not yet have any run/debug configurations.
1. In the menu bar, click "Run" then "Run Configurations...".
2. In the newly opened window, select "Java Application" on the left side, then click the leftmost button in the row above the tree view. A new configuration will appear.
3. Give a name to your configuration.
4. Set the project and the main class. To easily select it, use the "Browse..." and "Search..." buttons.
5. Optional: Set command line (and VM) arguments as well as environment variables in their respective tabs.
6. Click Apply
to save your configuration, then Close
to close the window.
7. Run or debug your bot via the Buttons in the top row, the Run
menu, or the shortcuts Ctrl+F11 for running and F11 for debugging.
Running from the IDE is only recommended during development and strongly discouraged for production use. Generally, you'll want your build tool to create a convenient distribution format for you to use.
For Gradle, only two further steps are necessary for a basic application. On top of the steps described in the Getting Started Section, also add the Application Plugin and define your mainClass
as the fully qualified name of your main class. If you're using an older version of Gradle (earlier than 6.4), the attribute is instead called mainClassName
.
INFO
As with many Gradle solutions, there is actually a whole lot going on under the hood. The application
plugin implicitly also applies the java
and distribution
plugins. Refer to the documentations of the involved plugins for more ways to fine-tune the process.
Your modified build file should now look similar to this:
plugins {
+ application
+}
+
+version = "1.0.0"
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+}
+
+application {
+ mainClass.set("com.github.yourname.BotMain")
+ // mainClassName.set("com.github.yourname.BotMain") // Gradle < 6.4
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("org.javacord:javacord:{{latestVersion}}")
+}
+
plugins {
+ id 'application'
+}
+
+version '1.0.0'
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+}
+
+application {
+ mainClass = 'com.github.yourname.BotMain'
+ // mainClassName = 'com.github.yourname.BotMain' // for Gradle versions < 6.4
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.javacord:javacord:{{latestVersion}}'
+}
+
Now you can execute the distZip
or distTar
task with Gradle. The task will create a distribution and package it in an archive file that will be placed in the build/distributions
directory. Extract the content of those files on your server or whichever machine you want to run your bot on.
The distribution usually only contains the directories bin
and lib
. From the distribution directory, run either bin/yourbot
or bin/yourbot.bat
, depending on whether you're running the bot on Linux / macOS or windows.
For Maven, add the Appassembler plugin to your pom.xml
. The plugin will create a distribution, but not bundle it in a neat archive file, so we'll also add the assembly plugin. We'll bind both to the package
lifecycle phase.
<project>
+ ...
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>appassembler-maven-plugin</artifactId>
+ <version>1.10</version>
+ <configuration>
+ <programs>
+ <program>
+ <mainClass>org.javacord.examplebot.Main</mainClass>
+ <id>examplebot</id>
+ </program>
+ </programs>
+ </configuration>
+ <executions>
+ <execution>
+ <id>create-distribution</id>
+ <phase>package</phase>
+ <goals>
+ <goal>assemble</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>3.3.0</version>
+ <configuration>
+ <descriptors>
+ <!-- This must match the location of the descriptor -->
+ <descriptor>src/assembly/distribution.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>create-archive</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
Sadly, none of the built-in assembly descriptors match our use case, so we'll put our custom one into src/assembly/distribution.xml
:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <id>distribution</id>
+ <formats>
+ <!-- See https://maven.apache.org/plugins/maven-assembly-plugin/assembly.html for supported formats -->
+ <format>tar.gz</format>
+ <format>tar.bz2</format>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <!-- This will also include your project readme, license and similar files-->
+ <directory>${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <includes>
+ <include>README*</include>
+ <include>LICENSE*</include>
+ <include>NOTICE*</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <!-- Change this if you reconfigured the appassembler output directory -->
+ <directory>${project.build.directory}/appassembler</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
+
Now when you execute mvn package
, a distribution with start scripts for Windows and Linux/macOS will be generated which is then packaged into archive files for every format you specified in the assembly descriptor. You can find the raw distribution (without readme and license files) in target/appassembler
and the archive files directly in target
.
After creating your distribution via Gradle or Maven and extracting/copying it to the machine you want to run it from, you should have a directory containing both a bin
and a lib
(or repo
) directory. Depending on your platform, you can now run the bin/yourbot
or bin/yourbot.bat
script.
These automatically generated scripts will then invoke java with your dependencies on the classpath and run your main class. Your working directory will be the directory you ran the script from.
Although it is an abuse of the way java works, sometimes you will be forced to create a fat jar, or an uber jar. This is a jar file that contains your application and all its dependencies. This is sometimes used as a lazy way of building a convenient distribution, but should be foregone in favor of the above mentioned distributions.
However, in some cases (more often than not Bukkit/Spigot addons) it is necessary to provide a fat jar, since the host application's loading mechanism can only handle singular jar files. If you are subject to such a case of bad design, please complain to the maintainer of whatever host application you are using, then use the following instructions to forsake all that is good and just and create a fat jar. Remember to grit your teeth the whole time.
For Gradle, use the shadow plugin. If you want the fat jar to be executable, you will need to specify a main class via the application plugin.
plugins {
+ id 'java'
+ # ...
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+}
+
With gradlew shadowJar
you can now create a shaded (fat) jar. It will be named build/libs/yourbot-1.0.0-all.jar
or similar, according to your project settings.
For Maven, add the maven-shade-plugin to your build. As with the other solutions, configure your main class.
Some of your dependencies might be signed .jar files. Unfortunately, this will likely break your fat jar. Remove the signatures by defining an exclusion filter as demonstrated below. Let the thought that you had to disable a security feature just to make this work serve as a reminder that creating a fat jar is not how jars are meant to be used.
<project>
+ ...
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.3</version>
+ <configuration>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>fat</shadedClassifierName>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Main-Class>com.github.yourname.BotMain</Main-Class>
+ </manifestEntries>
+ </transformer>
+ </transformers>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
Running mvn package
will now additionally create the yourbot-1.0.0-fat.jar
.
WARNING
This tutorial assumes that you are familiar with lambda expressions. Take a look at the lambda introduction first, if you are not!
As Javacord is heavily multithreaded, you must understand the concept of Futures in general, as well as their most common implementation, the CompletableFuture. This little introduction gives you a quick overview of the basics you need to know in order to work with Futures.
A future is basically a wrapper, that will contain a value in the future but might not contain it right now. This is useful, if a method call requires some time and should not block the execution of your current code. You can easily see the difference with a primitive speed comparison:
long currentTime = System.currentTimeMillis();
+channel.sendMessage("Test 1");
+channel.sendMessage("Test 2");
+channel.sendMessage("Test 3");
+channel.sendMessage("Test 4");
+channel.sendMessage("Test 5");
+// Prints "4 ms"
+System.out.println((System.currentTimeMillis() - currentTime) + " ms");
+
long currentTime = System.currentTimeMillis();
+channel.sendMessage("Test 1").join();
+channel.sendMessage("Test 2").join();
+channel.sendMessage("Test 3").join();
+channel.sendMessage("Test 4").join();
+channel.sendMessage("Test 5").join();
+// Prints "894 ms"
+System.out.println((System.currentTimeMillis() - currentTime) + " ms");
+
TIP
join()
blocks the current thread until the method finished. This will be explained later.
The join
method blocks the current thread until the method finished. It returns the method's result or throws a CompletionException
if anything failed.
The following example would create a new text channel in a given server
and sends a message directly afterwards.
// Create the channel
+ServerTextChannel channel = new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .join();
+// Send a message in the new channel
+Message message = channel.sendMessage("First!").join();
+// Adds an reaction to the message. Even though this method doesn't return anything,
+// join() ensures, that an exception is thrown in case something went wrong
+message.addReaction("👍").join();
+
DANGER
You should avoid join()
for methods which will be called frequently.
TIP
While join()
can become a performance issue when you call it very frequently, it is very convenient to use and easy to understand. If you are new to programming and just want to get your first bot working, this is a good method to start with.
Once you gathered more experience, we highly advise against using join
as it negatively impacts your bot's performance!
The thenAccept
method accepts a Consumer
, that consumes the result of the method and is executed asynchronously. It is the method you usually want to use most of the time.
The following example would create a new text channel in a given server
and send a message directly afterwards.
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenAccept(channel -> {
+ channel.sendMessage("First!").thenAccept(message -> {
+ message.addReaction("👍");
+ });
+ });
+
DANGER
The example code above has a major problem: Any exception that might occur will be completely ignored. This makes it very hard to find bugs.
For example, if your bot doesn't have the permissions to create a new channel, it will just fail silently.
The exceptionally
method accepts a Function
as parameter, which consumes possible exceptions and returns a fallback value.
The following example would create a new text channel in a given server
and send a message directly afterwards. If something fails (e.g., if the bot isn't allowed to create a text channel in the server), it will log an exception.
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenAccept(channel -> {
+ channel.sendMessage("First!").thenAccept(message -> {
+ message.addReaction("👍").exceptionally(e -> {
+ e.printStackTrace(); // Adding the reaction failed
+ return null;
+ });
+ }).exceptionally(e -> {
+ e.printStackTrace(); // Message sending failed
+ return null;
+ });
+ }).exceptionally(e -> {
+ e.printStackTrace(); // Channel creation failed
+ return null;
+ });
+
Wow! This looks ugly 🤮. But worry not! There are many options to improve this code!
To make things simpler for you, Javacord has the ExceptionLogger
class, which can be used here. It logs every exception you didn't catch manually.
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenAccept(channel -> {
+ channel.sendMessage("First!").thenAccept(message -> {
+ message.addReaction("👍").exceptionally(ExceptionLogger.get());
+ }).exceptionally(ExceptionLogger.get());
+ }).exceptionally(ExceptionLogger.get());
+
Okay! This is at least a little better, but still not really perfect 🤔.
The thenCompose
methods allows you to chain futures. It takes a Function as parameter, that consumes the future's value and expects a new future to be returned.
The example to create a text channel can now be written like this:
new ServerTextChannelBuilder(server)
+ .setName("new-channel")
+ .create()
+ .thenCompose(channel -> channel.sendMessage("First!"))
+ .thenCompose(message -> message.addReaction("👍"))
+ .exceptionally(ExceptionLogger.get());
+
Finally 🎉! Now we only need a single exceptionally(...)
call at the end. We also got rid of the nested callbacks (usually referred to as "callback hell").
For better understanding, here's the example with comments that tell you the type at each line:
new ServerTextChannelBuilder(server) // ServerTextChannelBuilder
+ .setName("new-channel") // ServerTextChannelBuilder
+ .create() // CompletableFuture<ServerTextChannel>
+ .thenCompose(channel -> channel.sendMessage("First!")) // CompletableFuture<Message>
+ .thenCompose(message -> message.addReaction("👍")) // CompletableFuture<Void>
+ .exceptionally(ExceptionLogger.get()); // CompletableFuture<Void>
+
This tutorial only focuses on the absolute basics. For a more detailed introduction to CompletableFutures, you can take a look at this tutorial.
You should also take a look at the JavaDoc for a complete list of methods: CompletableFuture JavaDoc.
Lambdas are used to implement functional interfaces. Simply said, functional interfaces are interfaces with a single method definition. All listeners in Javacord are functional interfaces and look like this internally (simplified):
@FunctionalInterface
+public interface MessageCreateListener {
+ void onMessageCreate(MessageCreateEvent event);
+}
+
Before Java 8, you would have implemented this kind of listener as an anonymous class, which would look like this:
api.addMessageCreateListener(new MessageCreateListener() {
+ @Override
+ public void onMessageCreate(MessageCreateEvent event) {
+ // Do stuff
+ event.pinMessage();
+ }
+});
+
In Java 8, this can be replaced with a lambda expression, which does exactly the same thing, but in a more readable fashion. The method parameter (in this case event
) is written in front of the ->
arrow, and the method body is written after it.
api.addMessageCreateListener(event -> {
+ // Do stuff
+ event.pinMessage();
+});
+
TIP
If the method has more than one parameter, it would look like this:
(param1, param2) -> { ... }
+
There's even a shorter version: If you are only executing one statement, you can get rid of the { }
brackets as well:
api.addMessageCreateListener(event -> event.pinMessage());
+
However, the above method can be shortened even more, by replacing the lambda expression with a so called "method reference".
api.addMessageCreateListener(MessageEvent::pinMessage);
+
There are also plenty classes in Java 8, that make use of lambda expressions. One example would be the Optional class, which is explained here.
This tutorial only focuses on the absolute basics. For an in-depth introduction to lambda expressions, you can take a look at Oracle's article about lambda expressions.
WARNING
This tutorial assumes that you are familiar with lambda expressions. Take a look at the lambda introduction first, if you are not!
The Optional class is widely used in Javacord. Basically, every method that might return a null
value will return an Optional in Javacord instead. Optionals help you to avoid NullPointerExceptions
and make it very clear if a method may not have a result. Here's a small example:
User user = api.getCachedUserById(123L);
+if (user != null) {
+ user.sendMessage("Hi!");
+}
+
api.getCachedUserById(123L).ifPresent(user ->
+ user.sendMessage("Hi!")
+);
+
You can imagine an Optional
like a box 📦 that may or may not contain a value. Before accessing this value, you have to "unpack" this box first.
The Optional class has many useful methods which can all be found in the JavaDocs. This tutorial gives a short introduction to the most common ones.
The get
method returns the value of the Optional or throws a NoSuchElementException
if it does not contain a value.
TextChannel channel = api.getTextChannelById(123L).get();
+channel.sendMessage("Hi");
+
DANGER
You should never use this method blindly but only if you are 100% sure the optional contains a value.
Every time you use this method carelessly, a kitten dies 🙀! True story.
The isPresent
methods checks, if the Optional contains a value.
Optional<TextChannel> channel = api.getTextChannelById(123L);
+if (channel.isPresent()) {
+ // A text channel with the id 123 exists. It's safe to call #get() now
+ channel.get().sendMessage("Hi");
+}
+
The orElse
methods returns the value of the Optional if it is present. Otherwise, it returns the given default value.
// The user may not have a nickname on the given server.
+// In this case, we use the user's "regular" name.
+String displayName = user.getNickname(server).orElse(user.getName());
+
The example above is (mostly) equivalent to the example below but much more concise.
String displayName = "";
+Optional<String> nickname = user.getNickname(server);
+if (nickname.isPresent()) {
+ displayName = nickname.get();
+} else {
+ displayName = user.getName();
+}
+
TIP
In this case you can just use user.getDisplayName(server)
instead.
The ifPresent
method is very similar to an if (value != null) { ... }
check. It takes a Consumer as it's argument. This consumer is called if the Optional contains a value. Together with lambda expressions this can be a very handy method.
api.getTextChannelById(123L).ifPresent(channel -> {
+ channel.sendMessage("Hi!");
+});
+
The example above is (mostly) equivalent to the example below but more concise.
Optional<TextChannel> channel = api.getTextChannelById(123L);
+if (channel.isPresent()) {
+ channel.get().sendMessage("Hi!");
+}
+
The filter
method filters the Optional for a given criteria.
Optional<User> botUser = api.getCachedUserById(123L).filter(User::isBot);
+
The example above is equivalent to the example below but more concise.
Optional<User> user = api.getCachedUserById(123L);
+Optional<User> botUser;
+if (user.isPresent() && user.get().isBot()) {
+ botUser = user;
+} else {
+ botUser = Optional.empty();
+}
+
The map
method "converts" the type of an Optional. This is useful, if the type of an Optional does not contain the final value you need.
The following example gets the name of the bots current activity (the "Playing xyz" status) or "None" if the bot has no current activity.
String activityName = api.getYourself().getActivity().map(Activity::getName).orElse("None");
+
For better understanding, here's the exact same code but with the types as comments:
String activityName = api.getYourself() // User
+ .getActivity() // Optional<Activity>
+ .map(Activity::getName) // Optional<String>
+ .orElse("None"); // String
+
The flatMap
method if very similar to the map
methods. It is used to map values that itself are Optionals to prevent Optional nesting (a "box in a box").
String activityName = api.getCachedUserById(123L) // Optional<User>
+ .flatMap(User::getActivity) // Optional<Activity>
+ .map(Activity::getName) // Optional<String>
+ .orElse("None"); // String
+
Without flatMap
, the code would look like this:
String activityName = api.getCachedUserById(123L) // Optional<User>
+ .map(User::getActivity) // Optional<Optional<Activity>>
+ .filter(Optional::isPresent) // Optional<Optional<Activity>>
+ .map(Optional::get) // Optional<Activity>
+ .map(Activity::getName) // Optional<String>
+ .orElse("None"); // String
+
This tutorial only focuses on the absolute basics. For an in-depth introduction to Optionals, you can take a look at Oracle's article about optionals.
After you added Javacord as a dependency with your favorite build manager, you should now create a bot account on the Discord website. This article will guide you through the process.
Bot
TIP
If you want to, you can rename your application first
Add bot
and confirm the popupNDc[...]pCs
. You can just click on Copy
.DANGER
This token is used to login your bot. Keep it secret!
Bots cannot join a server on their own like normal Discord users can. Instead, the owner of a server has to invite the bot using a so called Invite Link
. There are multiple ways to create the invite link:
The easiest way to obtain an invite link for your bot is by letting Javacord do it for you. Simply execute the following code, and it will print the invite link to your console:
DiscordApi api = new DiscordApiBuilder().setToken("your token").login().join();
+System.out.println(api.createBotInvite());
+
If you don't have Javacord setup yet, you can also create the invite link manually.
In order to add a bot to your server you need its client id.
You can get your client id from the same page where you created it.
With this id you can create an invite link for your bot.
If you are the owner or admin of the server, you can use this link to add your bot to your server. Otherwise, you have to give the link to the server owner/admins and ask them to add your bot.
TIP
Unlike the token, you don't have to keep your client id secret
Just use the following link and replace 123456789
with your own client id.
https://discord.com/api/oauth2/authorize?client_id=123456789&scope=applications.commands%20bot&permissions=0
You can calculate the permissions (in the link above it's the 0
) on the page where you created the bot:
You can now open the link and add the bot to your server:
TIP
Only the owner and admins of a server can invite bots. If you do not own a server yet, it is recommended to create one for testing.
The recommended way to get Javacord is to use a build manager, like Gradle or Maven.
If you are not familiar with build managers, you can follow one of the beginner ide setup guides (see navigation) or download Javacord directly from GitHub.
repositories { mavenCentral() }
+dependencies { implementation 'org.javacord:javacord:$latest-version' }
+
<dependency>
+ <groupId>org.javacord</groupId>
+ <artifactId>javacord</artifactId>
+ <version>$latest-version</version>
+ <type>pom</type>
+</dependency>
+
libraryDependencies ++= Seq("org.javacord" % "javacord" % "$latest-version")
+
Snapshots are automatically deployed from the development branch.
repositories {
+ maven {
+ url "https://oss.sonatype.org/content/repositories/snapshots/"
+ }
+}
+dependencies {
+ implementation 'org.javacord:javacord:$latest-snapshot-version'
+}
+
<repository>
+ <id>snapshots-repo</id>
+ <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+</repository>
+
<dependency>
+ <groupId>org.javacord</groupId>
+ <artifactId>javacord</artifactId>
+ <version>$latest-snapshot-version</version>
+ <type>pom</type>
+</dependency>
+
resolvers += "snapshots-repo" at "https://oss.sonatype.org/content/repositories/snapshots/"
+libraryDependencies ++= Seq("org.javacord" % "javacord" % "$latest-snapshot-version")
+
In addition to Javacord, it is also recommended to install a Log4j-2-compatible logging framework. A logging framework can be used to provide a more sophisticated logging experience with being able to configure log format, log targets (console, file, database, Discord direct message, ...), log levels per class, and much more.
For example, Log4j Core:
dependencies { runtimeOnly 'org.apache.logging.log4j:log4j-core:2.17.0' }
+
<dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.17.0</version>
+</dependency>
+
libraryDependencies ++= Seq("org.apache.logging.log4j" % "log4j-core" % "2.17.0")
+
Take a look at the logger configuration wiki article for further information.
Here you will find answers to some of the most asked questions.
You are missing the privileged MESSAGE_CONTENT
intent. For more information of how to enable privileged intents and enable them in your code see Gateway Intents.
...
in the code examples?You have to replace the ...
with an instance that can be assigned to the datatype seen left.
For example, if you see TextChannel channel = ...
, you have to replace ...
with an instance that is a TextChannel which you can get from the API api.getTextChannelById(CHANNEL_ID)
(note this returns an Optional<TextChannel>) or from an event like messageCreateEvent.getChannel()
.
There are multiple reasons why your code might not work. The most common ones are:
.exceptionally(ExceptionLogger.get())
to every CompletableFuture (like when sending a message) to show any exceptions that might come from Discord.User#getRoles(Server)
do not return the roles of the user. To fix this make sure to add the GUILD_MEMBERS
intent.NoSuchElementException
. Congratulations, you have killed a kitten! You are most likely getting this Exception because you handle Optionals wrong. Read the article on Optionals to learn how to use them correctly.If none of these tips will help you, you can ask your question in our Discord Server.
Don't ask:
Why is my code not working?
+//Code
+
Why am I getting Exception X?
+
To ensure all information is provided that is needed to solve your issue, you should ask your question in a format like:
I have an issue with: YOUR_ISSUE
+I want to do: WHAT_YOU_WANT_TO_DO
+Currently this happens: WHAT_HAPPENS_NOW
+
+//Code
+
+//Exception
+The exception is thrown in the following line(not the number): CODE_LINE
+
While all 3 libraries are Wrappers for the programming language Java, they use different techniques and concepts for their API.
null
. channel.sendMessage("Javacord")
message.getMessageAuthor().asUser().isPresent()
null
if values are not present. channel.sendMessage("JDA").queue()
message.getMember() != null
reactive
approach. channel.createMessage("Pong!").block();
Info
We recommend to use Intellij + Gradle unless you already have experience with one of the other IDEs or build managers.
File
-> New
-> Project
)Maven Project
Next
Create a simple project
Next
com.github.yourname
)myfirstbot
)Finish
pom.xml
filepom.xml
<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>your.package.name</groupId>
+ <artifactId>myfirstbot</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.javacord</groupId>
+ <artifactId>javacord</artifactId>
+ <version>$latest-version</version>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+
+</project>
+
src/main/java
folderpackage com.github.yourname.myfirstbot;
+
+import org.javacord.api.DiscordApi;
+import org.javacord.api.DiscordApiBuilder;
+
+public class Main {
+
+ public static void main(String[] args) {
+ // Insert your bot's token here
+ String token = "your token";
+
+ DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+
+ // Print the invite url of your bot
+ System.out.println("You can invite the bot by using the following url: " + api.createBotInvite());
+ }
+
+}
+
You can run your code by clicking on the small green arrow
This tutorial provides a beginner-friendly click by click guide to set up Javacord with Intellij and Gradle. If you are already familiar with IntelliJ and Gradle, you can just see the artifact locations at Download / Installation.
File
-> New
-> Project
)Gradle
Next
com.github.yourname
)You can choose whatever you want
myfirstbot
)You can choose whatever you want
Next
Use auto-import
Next
Finish
build.gradle
file and open itbuild.gradle
file should now look like thisplugins {
+ id 'java'
+}
+
+group 'com.github.yourname'
+version '1.0-SNAPSHOT'
+
+sourceCompatibility = 1.8
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.javacord:javacord:$latest-version'
+}
+
src/main/java
folderExample code:
package com.github.yourname;
+
+import org.javacord.api.DiscordApi;
+import org.javacord.api.DiscordApiBuilder;
+
+public class Main {
+
+ public static void main(String[] args) {
+ // Insert your bot's token here
+ String token = "your token";
+
+ DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+
+ // Print the invite url of your bot
+ System.out.println("You can invite the bot by using the following url: " + api.createBotInvite());
+ }
+
+}
+
You can run your code by clicking on the small green arrow
This tutorial provides a beginner-friendly click by click guide to set up Javacord with Intellij and Maven. If you are already familiar with IntelliJ and Maven, you can just see the artifact locations at Download / Installation.
Info
We recommend to use Intellij + Gradle unless you already have experience with one of the other IDEs or build managers.
File
-> New
-> Project
)Maven
Next
com.github.yourname
)myfirstbot
)Next
Finish
Enable Auto-Import
<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>your.package.name</groupId>
+ <artifactId>myfirstbot</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.javacord</groupId>
+ <artifactId>javacord</artifactId>
+ <version>$latest-version</version>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+
+</project>
+
package com.github.yourname;
+
+import org.javacord.api.DiscordApi;
+import org.javacord.api.DiscordApiBuilder;
+
+public class Main {
+
+ public static void main(String[] args) {
+ // Insert your bot's token here
+ String token = "your token";
+
+ DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+
+ // Print the invite url of your bot
+ System.out.println("You can invite the bot by using the following url: " + api.createBotInvite());
+ }
+
+}
+
You can run your code by clicking on the small green arrow
Note: If you get the following error:
you have to change your language level to 1.8
After you have successfully added Javacord as a dependency, created a bot user, and got its token, you are now ready to create your first simple bot! 🎉
By default, all non-privileged intents are enabled. To receive the message content, attachments, components, and embeds you need a special privileged intent MESSAGE_CONTENT
. To enable this privileged intent please see the Gateway Intents wiki article.
Slash Commands
Generally it is recommended to use Slash Commands instead of text commands because they offer many advantages like auto-completion, fixed and optional arguments, different kind of arguments with built-in types: numbers(with ranges), text, channel and a lot more.
Everything starts with the DiscordApiBuilder
class. It is used to create a DiscordApi
object which is the most important class of your bot.
DiscordApi api = new DiscordApiBuilder()
+ .setToken("<your super secret token>")
+ .addIntents(Intent.MESSAGE_CONTENT)
+ .login().join();
+
After executing this code, you should already see your bot online in Discord. Of course, just being online is not enough, so let's add some more code!
After you got your api
instance, let's continue by adding a listener that answers every !ping
message with a simple Pong!
.
api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+});
+
A good place for your code is the main(...)
method that every executable Java program must have. Your complete class may look like this:
public class MyFirstBot {
+
+ public static void main(String[] args) {
+ // Log the bot in
+ DiscordApi api = new DiscordApiBuilder()
+ .setToken("<your super secret token>")
+ .addIntents(Intent.MESSAGE_CONTENT)
+ .login().join();
+
+ // Add a listener which answers with "Pong!" if someone writes "!ping"
+ api.addMessageCreateListener(event -> {
+ if (event.getMessageContent().equalsIgnoreCase("!ping")) {
+ event.getChannel().sendMessage("Pong!");
+ }
+ });
+ }
+
+}
+
Congratulations, that's already everything you have to know for the beginning. Now, you can play around a little bit by exploring other listeners and methods. Or you just continue reading articles in the Basic Tutorials category.
Welcome to the Javacord wiki! 👋
This wiki will help you to get started with your first Discord bot as fast as possible.
The wiki is divided into four groups:
While the wiki is great and covers many aspects of Javacord, we highly recommended you to join our Discord server if you have any questions: