Skip to content

Commit

Permalink
Interactions, Slash-Commands, and Buttons (#1501)
Browse files Browse the repository at this point in the history
- Button Interactions
- Slash Command Interactions
  • Loading branch information
MinnDevelopment authored Jun 4, 2021
1 parent 1dad81d commit c9f76f3
Show file tree
Hide file tree
Showing 116 changed files with 15,009 additions and 435 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,40 @@ public class Bot extends ListenerAdapter
}
```

**Slash-Commands**:

```java
public class Bot extends ListenerAdapter
{
public static void main(String[] args) throws LoginException
{
if (args.length < 1) {
System.out.println("You have to provide a token as first argument!");
System.exit(1);
}
// args[0] should be the token
// We don't need any intents for this bot. Slash commands work without any intents!
JDA jda = JDABuilder.createLight(args[0], Collections.emptyList())
.addEventListeners(new Bot())
.setActivity(Activity.playing("Type /ping"))
.build();

jda.upsertCommand("ping", "Calculate ping of the bot").queue(); // This can take up to 1 hour to show up in the client
}

@Override
public void onSlashCommand(SlashCommandEvent event)
{
if (!event.getName().equals("ping")) return; // make sure we handle the right command
long time = System.currentTimeMillis();
event.reply("Pong!").setEphemeral(true) // reply or acknowledge
.flatMap(v ->
event.getHook().editOriginalFormat("Pong: %d ms", System.currentTimeMillis() - time) // then edit original
).queue(); // Queue both reply and edit
}
}
```

### RestAction

Through [RestAction](https://ci.dv8tion.net/job/JDA/javadoc/net/dv8tion/jda/api/requests/RestAction.html) we provide request handling with
Expand Down
6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.1")

//Sets the dependencies for the examples
configurations.asMap["examplesCompile"] = configurations["apiElements"]
configurations.asMap["examplesRuntime"] = configurations["implementation"]
configurations["examplesImplementation"].withDependencies {
addAll(configurations["api"].allDependencies)
addAll(configurations["implementation"].allDependencies)
}

testImplementation("org.junit.jupiter:junit-jupiter:5.4.0")
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
196 changes: 196 additions & 0 deletions src/examples/java/SlashBotExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;

import javax.security.auth.login.LoginException;
import java.util.EnumSet;

import static net.dv8tion.jda.api.interactions.commands.OptionType.*;

public class SlashBotExample extends ListenerAdapter
{
public static void main(String[] args) throws LoginException
{
JDA jda = JDABuilder.createLight("BOT_TOKEN_HERE", EnumSet.noneOf(GatewayIntent.class)) // slash commands don't need any intents
.addEventListeners(new SlashBotExample())
.build();

// These commands take up to an hour to be activated after creation/update/delete
CommandListUpdateAction commands = jda.updateCommands();

// Moderation commands with required options
commands.addCommands(
new CommandData("ban", "Ban a user from this server. Requires permission to ban users.")
.addOptions(new OptionData(USER, "user", "The user to ban") // USER type allows to include members of the server or other users by id
.setRequired(true)) // This command requires a parameter
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days.")) // This is optional
);

// Simple reply commands
commands.addCommands(
new CommandData("say", "Makes the bot say what you tell it to")
.addOptions(new OptionData(STRING, "content", "What the bot should say")
.setRequired(true))
);

// Commands without any inputs
commands.addCommands(
new CommandData("leave", "Make the bot leave the server")
);

commands.addCommands(
new CommandData("prune", "Prune messages from this channel")
.addOptions(new OptionData(INTEGER, "amount", "How many messages to prune (Default 100)"))
);

// Send the new set of commands to discord, this will override any existing global commands with the new set provided here
commands.queue();
}


@Override
public void onSlashCommand(SlashCommandEvent event)
{
// Only accept commands from guilds
if (event.getGuild() == null)
return;
switch (event.getName())
{
case "ban":
Member member = event.getOption("user").getAsMember(); // the "user" option is required so it doesn't need a null-check here
User user = event.getOption("user").getAsUser();
ban(event, user, member);
break;
case "say":
say(event, event.getOption("content").getAsString()); // content is required so no null-check here
break;
case "leave":
leave(event);
break;
case "prune": // 2 stage command with a button prompt
prune(event);
break;
default:
event.reply("I can't handle that command right now :(").setEphemeral(true).queue();
}
}

@Override
public void onButtonClick(ButtonClickEvent event)
{
// users can spoof this id so be careful what you do with this
String[] id = event.getComponentId().split(":"); // this is the custom id we specified in our button
String authorId = id[0];
String type = id[1];
// When storing state like this is it is highly recommended to do some kind of verification that it was generated by you, for instance a signature or local cache
if (!authorId.equals(event.getUser().getId()))
return;
event.deferEdit().queue(); // acknowledge the button was clicked, otherwise the interaction will fail

MessageChannel channel = event.getChannel();
switch (type)
{
case "prune":
int amount = Integer.parseInt(id[2]);
event.getChannel().getIterableHistory()
.skipTo(event.getMessageIdLong())
.takeAsync(amount)
.thenAccept(channel::purgeMessages);
// fallthrough delete the prompt message with our buttons
case "delete":
event.getHook().deleteOriginal().queue();
}
}

public void ban(SlashCommandEvent event, User user, Member member)
{
event.deferReply(true).queue(); // Let the user know we received the command before doing anything else
InteractionHook hook = event.getHook(); // This is a special webhook that allows you to send messages without having permissions in the channel and also allows ephemeral messages
hook.setEphemeral(true); // All messages here will now be ephemeral implicitly
if (!event.getMember().hasPermission(Permission.BAN_MEMBERS))
{
hook.sendMessage("You do not have the required permissions to ban users from this server.").queue();
return;
}

Member selfMember = event.getGuild().getSelfMember();
if (!selfMember.hasPermission(Permission.BAN_MEMBERS))
{
hook.sendMessage("I don't have the required permissions to ban users from this server.").queue();
return;
}

if (member != null && !selfMember.canInteract(member))
{
hook.sendMessage("This user is too powerful for me to ban.").queue();
return;
}

int delDays = 0;
OptionMapping option = event.getOption("del_days");
if (option != null) // null = not provided
delDays = (int) Math.max(0, Math.min(7, option.getAsLong()));
// Ban the user and send a success response
event.getGuild().ban(user, delDays)
.flatMap(v -> hook.sendMessage("Banned user " + user.getAsTag()))
.queue();
}

public void say(SlashCommandEvent event, String content)
{
event.reply(content).queue(); // This requires no permissions!
}

public void leave(SlashCommandEvent event)
{
if (!event.getMember().hasPermission(Permission.KICK_MEMBERS))
event.reply("You do not have permissions to kick me.").setEphemeral(true).queue();
else
event.reply("Leaving the server... :wave:") // Yep we received it
.flatMap(v -> event.getGuild().leave()) // Leave server after acknowledging the command
.queue();
}

public void prune(SlashCommandEvent event)
{
OptionMapping amountOption = event.getOption("amount"); // This is configured to be optional so check for null
int amount = amountOption == null
? 100 // default 100
: (int) Math.min(200, Math.max(2, amountOption.getAsLong())); // enforcement: must be between 2-200
String userId = event.getUser().getId();
event.reply("This will delete " + amount + " messages.\nAre you sure?") // prompt the user with a button menu
.addActionRow(// this means "<style>(<id>, <label>)" the id can be spoofed by the user so setup some kinda verification system
Button.secondary(userId + ":delete", "Nevermind!"),
Button.danger(userId + ":prune:" + amount, "Yes!")) // the first parameter is the component id we use in onButtonClick above
.queue();
}
}
Loading

1 comment on commit c9f76f3

@DeveloperTK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳

Please sign in to comment.