A fluent API for JDA interactions - buttons, modals, select menus, panels, and more.
repositories {
mavenCentral()
maven("https://repo.groupez.dev/releases")
}
dependencies {
implementation("fr.traqueur:jdafluent:1.0.0")
}repositories {
mavenCentral()
maven { url 'https://repo.groupez.dev/releases' }
}
dependencies {
implementation 'fr.traqueur:jdafluent:1.0.0'
}<repository>
<id>groupez</id>
<url>https://repo.groupez.dev/releases</url>
</repository>
<dependency>
<groupId>fr.traqueur</groupId>
<artifactId>jdafluent</artifactId>
<version>1.0.0</version>
</dependency>JDA jda = JDABuilder.createDefault("TOKEN").build();
JdaFluent fluent = new JdaFluent(jda);// Register a permanent button handler
ButtonFactory buyButton = fluent.buttons().register("buy", (event, data) -> {
long productId = data.getLong(0);
int quantity = data.getInt(1);
// Process purchase...
event.reply("Purchased!").setEphemeral(true).queue();
});
// Create buttons with data
channel.sendMessage("Buy this item?")
.setComponents(ActionRow.of(
buyButton.primary("Buy x1", productId, 1),
buyButton.primary("Buy x10", productId, 10)
))
.queue();// One-shot: removed after first click
Button button = fluent.buttons().oneShot(
ButtonStyle.PRIMARY,
"Click me",
Duration.ofMinutes(5),
event -> event.reply("Clicked!").queue()
);
// Temporary: stays active until timeout
Button temp = fluent.buttons().temporary(
ButtonStyle.PRIMARY,
"โ",
Duration.ofMinutes(5),
event -> previousPage(event)
);// When one button is clicked, all are invalidated
OneShotGroup group = fluent.buttons().group(Duration.ofMinutes(2));
channel.sendMessage("Delete this server?")
.setComponents(ActionRow.of(
group.danger("Yes, delete", event -> {
deleteServer();
event.editMessage("Deleted.").setComponents().queue();
}),
group.secondary("Cancel", event -> {
event.editMessage("Cancelled.").setComponents().queue();
})
))
.queue();// Simple confirmation
channel.sendMessage("Are you sure?")
.setComponents(fluent.dialogs().confirm("Yes", Duration.ofMinutes(2), event -> {
// Handle confirmation
event.editMessage("Confirmed!").setComponents().queue();
}))
.queue();
// Dangerous action with double confirmation
channel.sendMessage("Delete all data?")
.setComponents(fluent.dialogs().dangerConfirm("Delete", Duration.ofMinutes(1), event -> {
deleteAllData();
event.editMessage("All data deleted.").setComponents().queue();
}))
.queue();
// Typed choices
channel.sendMessage("Select difficulty:")
.setComponents(fluent.dialogs().choices(
Duration.ofMinutes(5),
difficulty -> startGame(difficulty),
Choice.of("Easy", Difficulty.EASY),
Choice.of("Normal", Difficulty.NORMAL),
Choice.of("Hard", Difficulty.HARD)
))
.queue();// Create a ticket panel
Panel<TicketCategory> ticketPanel = fluent.panels()
.<TicketCategory>choices("ticket", ctx -> {
createTicket(ctx.event(), ctx.value());
})
.codec(Enum::name, TicketCategory::valueOf)
.choice("๐ซ", "Support", TicketCategory.SUPPORT)
.choice("๐", "Bug Report", TicketCategory.BUG)
.choice("๐ฐ", "Billing", TicketCategory.BILLING)
.build();
// Send the panel (survives bot restarts)
channel.sendMessageEmbeds(embed)
.setComponents(ticketPanel.toActionRows())
.queue();// Create a role selector
SelectMenuBuilder.BuiltSelectMenu<String> roleMenu = fluent.selects()
.<String>create("roles", ctx -> {
String roleId = ctx.value();
toggleRole(ctx.member(), roleId);
ctx.reply("Role updated!");
})
.codec(s -> s, s -> s)
.placeholder("Choose your roles...")
.multiple()
.option("๐ฎ", "Gamer", "gamer-role-id")
.option("๐จ", "Artist", "artist-role-id")
.option("๐ต", "Music", "music-role-id")
.build();
channel.sendMessage("Select your roles:")
.setComponents(ActionRow.of(roleMenu.toMenu()))
.queue();// Create a modal
ModalBuilder.BuiltModal reportModal = fluent.modals()
.create("report", ctx -> {
String reason = ctx.get("reason");
String details = ctx.getOptional("details").orElse("No details");
submitReport(ctx.user(), reason, details);
ctx.reply("Report submitted!");
})
.title("Report User")
.shortText("reason", "Reason", b -> b
.required()
.placeholder("Why are you reporting?")
.maxLength(100))
.paragraph("details", "Details", b -> b
.optional()
.placeholder("Additional information...")
.minLength(20))
.build();
// Show modal on button click
fluent.buttons().register("report-btn", event -> {
reportModal.show(event);
});// Wait for a message
channel.sendMessage("Type the server name to confirm deletion:").queue();
fluent.awaiters()
.message()
.from(user)
.inChannel(channel)
.timeout(Duration.ofMinutes(1))
.filter(msg -> !msg.getContentRaw().isBlank())
.onResponse(msg -> {
if (msg.getContentRaw().equals("MyServer")) {
deleteServer();
channel.sendMessage("Server deleted.").queue();
} else {
channel.sendMessage("Incorrect name, cancelled.").queue();
}
})
.onTimeout(() -> channel.sendMessage("Timeout, cancelled.").queue())
.start();
// Wait for a button click
fluent.awaiters()
.button()
.from(user)
.onMessage(message)
.timeout(Duration.ofSeconds(30))
.disableAllOnClick()
.onResponse(event -> {
event.reply("You clicked: " + event.getComponentId()).queue();
})
.onTimeout(() -> message.editMessage("Expired").setComponents().queue())
.start();
// Wait for a select menu
fluent.awaiters()
.select()
.from(user)
.onMessage(message)
.timeout(Duration.ofMinutes(2))
.onResponse(event -> {
String selected = event.getValues().get(0);
event.reply("You selected: " + selected).queue();
})
.start();// Simple embed
FluentEmbed.create()
.title("Welcome!")
.description("Thanks for joining our server.")
.colorSuccess()
.thumbnail(user.getAvatarUrl())
.field("Member #", memberCount)
.fieldInline("Joined", "Today")
.fieldInline("Role", "Member")
.footer("Enjoy your stay!")
.timestampNow()
.send(channel)
.queue();
// Using templates
EmbedTemplate errorTemplate = EmbedTemplate.builder()
.color(0xED4245)
.footer("Error occurred")
.timestampNow()
.build();
errorTemplate.withTitle("Oops!")
.description("Something went wrong: " + errorMessage)
.send(channel)
.queue();
// Preset templates
EmbedTemplate.success().withDescription("Operation completed!").send(channel).queue();
EmbedTemplate.error().withDescription("Something failed!").send(channel).queue();
EmbedTemplate.warning().withDescription("Be careful!").send(channel).queue();
EmbedTemplate.info().withDescription("FYI...").send(channel).queue();public class TicketSystem {
private final JdaFluent fluent;
private final Panel<TicketCategory> ticketPanel;
public TicketSystem(JDA jda) {
this.fluent = new JdaFluent(jda);
this.ticketPanel = fluent.panels()
.<TicketCategory>choices("ticket", this::handleTicketCreate)
.codec(Enum::name, TicketCategory::valueOf)
.choice("๐ซ", "General Support", TicketCategory.SUPPORT)
.choice("๐", "Bug Report", TicketCategory.BUG)
.choice("๐ฐ", "Billing Issue", TicketCategory.BILLING)
.build();
}
public void sendTicketPanel(TextChannel channel) {
FluentEmbed.create()
.title("๐ซ Create a Ticket")
.description("Click the button that matches your issue.")
.colorInfo()
.send(channel)
.setComponents(ticketPanel.toActionRows())
.queue();
}
private void handleTicketCreate(PanelContext<TicketCategory> ctx) {
TicketCategory category = ctx.value();
Member member = ctx.member();
// Create ticket channel...
createTicketChannel(ctx.guild(), member, category).queue(channel -> {
sendWelcomeMessage(channel, member, category);
ctx.reply("Ticket created: " + channel.getAsMention());
});
}
private void sendWelcomeMessage(TextChannel channel, Member member, TicketCategory category) {
OneShotGroup actions = fluent.buttons().group(Duration.ofDays(7));
FluentEmbed.create()
.title("Ticket - " + category.getLabel())
.description("Welcome " + member.getAsMention() + "!\n\nDescribe your issue and staff will respond shortly.")
.colorSuccess()
.fieldInline("Category", category.getEmoji() + " " + category.getLabel())
.fieldInline("Status", "๐ข Open")
.thumbnail(member.getEffectiveAvatarUrl())
.timestampNow()
.send(channel)
.setContent(member.getAsMention())
.setComponents(ActionRow.of(
actions.primary("๐", "Add Info", e -> showAddInfoModal(e)),
actions.danger("๐", "Close Ticket", e -> confirmClose(e, channel))
))
.queue();
}
private void showAddInfoModal(ButtonInteractionEvent event) {
fluent.modals()
.create("add-info", ctx -> {
String info = ctx.get("info");
ctx.channel().sendMessage("**Additional info from " + ctx.user().getAsMention() + ":**\n" + info).queue();
ctx.reply("Info added!");
})
.title("Add Information")
.paragraph("info", "Additional Information", b -> b.required().minLength(10))
.build()
.show(event);
}
private void confirmClose(ButtonInteractionEvent event, TextChannel channel) {
event.reply("Are you sure you want to close this ticket?")
.setEphemeral(true)
.setComponents(fluent.dialogs().confirm("Close", Duration.ofMinutes(2), e -> {
channel.delete().queue();
}))
.queue();
}
}