feat: implement button, modal and emoji tasks

This commit is contained in:
anthorob committed 2026-08-24 17:00:24 -04:00
1 parent aa587ddf3c
commit 4fa790259e
14 files changed
+744

No files matched your search

@@ -0,0 +1,26 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
public abstract class AbstractAdminCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return true;
}
@Override
public DefaultMemberPermissions getSlashCommandPermission() {
return DefaultMemberPermissions.DISABLED;
}
@Override
public CommandType getType() {
return CommandType.ADMINISTRATION;
}
}
@@ -0,0 +1,141 @@
package me.bobert.netneutrality.context;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class MessageCommandContext extends CommandContext {
private final MessageReceivedEvent event;
private final Message message;
public MessageCommandContext(MessageReceivedEvent event) {
super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getAuthor());
this.event = event;
this.message = event.getMessage();
}
@Override
public MessageReceivedEvent getEvent() {
return event;
}
public String getArgument(int index) {
return getArgs()[index];
}
public String[] getArgs() {
String msg = message.getContentDisplay().trim();
msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
String cmd = msg.split(" ", 2)[0];
String[] args = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split(" ");
if (args.length == 1 && args[0].isEmpty())
args = new String[0];
return args;
}
public String getMessageText() {
String msg = message.getContentDisplay().trim();
msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
String cmd = msg.split(" ", 2)[0];
return msg.replaceFirst("(" + cmd + ")( )?", "");
}
public Message getMessage() {
return message;
}
@Override
public boolean isAcknowledged() {
return true;
}
@Override
public InteractionHook getInteractionHook() {
return null;
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
reply("Bot is thinking...", consumer);
}
@Override
public void reply(MessageCreateData messageCreateData, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (getChannel() != null && getChannel().canTalk()) {
message.reply(messageCreateData).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1)));
}
}
@Override
public void reply(Modal modal) {
}
@Override
public void replyEphemeral(MessageCreateData message) {
sendMessageInPrivate(message);
}
@Override
public void ack(boolean ephemeral) {
}
@Override
public void deleteMessage() {
if (message != null) {
message.delete().queueAfter(1, TimeUnit.SECONDS);
}
}
public boolean isMessageFromOwner() {
return getGuildUser().isOwner();
}
public boolean isMessageFromCreator() {
String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER");
return creatorId.equals(getDiscordUser().getId());
}
@Override
public String parseCommandName() {
String rawMessage = message.getContentDisplay().trim();
String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$");
if (rawMessage.startsWith(prefix)) {
rawMessage = rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim();
// rawMessage = rawMessage.substring(prefix.length()).trim();
return rawMessage.split(" ", 2)[0];
}
return null;
}
@Override
public String getSubCommand() {
if (getArgs().length > 0) {
return getArgument(0);
}
return "";
}
}
@@ -0,0 +1,88 @@
package me.bobert.netneutrality.context;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.CommandContext;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.function.Consumer;
public class SlashCommandContext extends CommandContext {
private final SlashCommandInteractionEvent event;
public SlashCommandContext(SlashCommandInteractionEvent event) {
super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser());
this.event = event;
}
@Override
public SlashCommandInteractionEvent getEvent() {
return event;
}
@Override
public boolean isAcknowledged() {
return event.isAcknowledged();
}
@Override
public InteractionHook getInteractionHook() {
if (!event.isAcknowledged())
throw new RuntimeException("Interaction isn't acknowledged yet");
return event.getHook();
}
@Override
public void deleteMessage() {
// not implemented
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook)));
}
@Override
public void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (isAcknowledged()) {
getInteractionHook().sendMessage(message).setEphemeral(ephemeral).setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
return;
}
event.reply(message).setEphemeral(ephemeral).setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
}
@Override
public void reply(Modal modal) {
event.replyModal(modal).queue();
}
@Override
public void ack(boolean ephemeral) {
if (event.isAcknowledged())
throw new RuntimeException("Interaction is already acknowledged, please use getInteractionHook() instead.");
event.deferReply(ephemeral).queue();
}
@Override
public String parseCommandName() {
return event.getName();
}
@Override
public String getSubCommand() {
return event.getFullCommandName().split(" ")[1];
}
}
@@ -0,0 +1,97 @@
package me.bobert.netneutrality.core;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.AbstractContext;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import java.util.function.Consumer;
public class AsyncMessage extends AbstractContext {
private final InteractionHook hook;
private final Message message;
public AsyncMessage(InteractionHook hook) {
super(hook.getJDA(), hook.getInteraction().getGuild(), hook.getInteraction().getMessageChannel(), hook.getInteraction().getMember(), hook.getInteraction().getUser());
this.hook = hook;
this.message = null;
}
public AsyncMessage(Message message) {
super(message.getJDA(), message.getGuild(), message.getChannel(), message.getMember(), message.getAuthor());
this.hook = null;
this.message = message;
}
@Override
public Event getEvent() {
return null;
}
@Override
public InteractionHook getInteractionHook() {
return hook;
}
@Override
public boolean isAcknowledged() {
return true;
}
@Override
public void deleteMessage() {
if (hook != null) {
hook.deleteOriginal().queue();
}
if (message != null) {
message.delete().queue();
}
}
@Override
public void reply(Modal modal) {
}
@Override
public void reply(MessageCreateData messageCreateData, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (hook != null) {
hook.sendMessage(messageCreateData).queue();
}
if (message != null) {
message.reply(messageCreateData).queue();
}
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
}
@Override
public void ack(boolean ephemeral) {
}
@Override
public void edit(MessageEditData messageEditData, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (hook != null) {
hook.editOriginal(messageEditData).queue();
}
if (message != null) {
message.editMessage(messageEditData).queue();
}
}
}
@@ -0,0 +1,39 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import org.json.JSONObject;
public abstract class AbstractPersistableTask extends AbstractTask implements PersistableTask {
@Override
public boolean isInsertable() {
return true;
}
@Override
public boolean oneshot() {
return false;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public void replyToInteraction(CommandContext context) {
}
@Override
public String toPayload() {
JSONObject payload = new JSONObject(this);
return payload.toString();
}
}
@@ -0,0 +1,46 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.time.OffsetDateTime;
import java.util.Objects;
public abstract class AbstractTask implements Task {
private final OffsetDateTime creationTime;
public AbstractTask() {
creationTime = OffsetDateTime.now();
}
@Override
public boolean isInsertable() {
return true;
}
@Override
public OffsetDateTime getCreationTime() {
return creationTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractTask that = (AbstractTask) o;
return Objects.equals(getId(), that.getId());
}
@Override
public int hashCode() {
return Objects.hashCode(getId());
}
@Override
public String toString() {
return String.format("Task{id=%s, class=%s, name=%s, oneshot=%b, creationTime=%s}", getId(), getClass().getSimpleName(), getName(), oneshot(), getCreationTime());
}
}
@@ -0,0 +1,22 @@
package me.bobert.netneutrality.hooks.interaction;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.ButtonTaskContext;
import me.bobert.netneutrality.core.tasks.TaskManager;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class ButtonInteractionHook extends ListenerAdapter {
@Override
public void onButtonInteraction(@NotNull ButtonInteractionEvent event) {
ButtonTaskContext context = new ButtonTaskContext(event);
TaskManager.getInstance().handle(context);
}
}
@@ -0,0 +1,22 @@
package me.bobert.netneutrality.hooks.interaction;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.ModalTaskContext;
import me.bobert.netneutrality.core.tasks.TaskManager;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class ModalInteractionHook extends ListenerAdapter {
@Override
public void onModalInteraction(@NotNull ModalInteractionEvent event) {
ModalTaskContext context = new ModalTaskContext(event);
TaskManager.getInstance().handle(context);
}
}
@@ -0,0 +1,22 @@
package me.bobert.netneutrality.hooks.message;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.EmojiTaskContext;
import me.bobert.netneutrality.core.tasks.TaskManager;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class EmojiReactionHook extends ListenerAdapter {
@Override
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
EmojiTaskContext context = new EmojiTaskContext(event);
TaskManager.getInstance().handle(context);
}
}
@@ -0,0 +1,43 @@
package me.bobert.netneutrality.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.ButtonTaskContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractTask;
import java.time.OffsetDateTime;
import java.util.UUID;
public abstract class ButtonTask extends AbstractTask {
private final UUID id = UUID.randomUUID();
@Override
public String getId() {
return id.toString();
}
@Override
public void execute(TaskContext context) {
if (context instanceof ButtonTaskContext) {
execute((ButtonTaskContext) context);
} else {
throw new IllegalArgumentException("Invalid context for this task");
}
}
public abstract void execute(ButtonTaskContext context);
@Override
public boolean oneshot() {
return true;
}
@Override
public boolean isExpired() {
return getCreationTime().plusMinutes(15).isBefore(OffsetDateTime.now());
}
}
@@ -0,0 +1,44 @@
package me.bobert.netneutrality.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.EmojiTaskContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractPersistableTask;
public abstract class EmojiTask extends AbstractPersistableTask {
private final String guildId;
private final String messageId;
public EmojiTask(String guildId, String messageId) {
this.guildId = guildId;
this.messageId = messageId;
}
public String getGuildId() {
return guildId;
}
public String getMessageId() {
return messageId;
}
@Override
public String getId() {
return String.format("%s:%s", getGuildId(), getMessageId());
}
@Override
public void execute(TaskContext context) {
if (context instanceof EmojiTaskContext) {
execute((EmojiTaskContext) context);
} else {
throw new IllegalArgumentException("Invalid context for this task");
}
}
public abstract void execute(EmojiTaskContext context);
}
@@ -0,0 +1,43 @@
package me.bobert.netneutrality.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.ModalTaskContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractTask;
import java.time.OffsetDateTime;
import java.util.UUID;
public abstract class ModalTask extends AbstractTask {
private final UUID id = UUID.randomUUID();
@Override
public String getId() {
return id.toString();
}
@Override
public void execute(TaskContext context) {
if (context instanceof ModalTaskContext) {
execute((ModalTaskContext) context);
} else {
throw new IllegalArgumentException("Invalid context for this task");
}
}
public abstract void execute(ModalTaskContext context);
@Override
public boolean oneshot() {
return true;
}
@Override
public boolean isExpired() {
return getCreationTime().plusMinutes(5).isBefore(OffsetDateTime.now());
}
}
@@ -0,0 +1,66 @@
package me.bobert.netneutrality.tasks.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import me.bobert.netneutrality.context.tasks.ButtonTaskContext;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.SourceFinder;
import me.bobert.netneutrality.music.TrackScheduler;
import me.bobert.netneutrality.tasks.ButtonTask;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
public class AddAllTracksTask extends ButtonTask implements WithParent<LoadPlaylistTask> {
private final LoadPlaylistTask parentTask;
List<AudioTrack> tracks;
public AddAllTracksTask(LoadPlaylistTask parentTask, List<AudioTrack> tracks) {
this.parentTask = parentTask;
this.tracks = tracks;
}
@Override
public String getName() {
return "add-all-tracks";
}
@Override
public void replyToInteraction(CommandContext context) {
}
@Override
public void execute(ButtonTaskContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
TrackScheduler scheduler = manager.getScheduler();
Member member = context.getGuildUser();
if (MusicUtils.connect(context)) {
for (AudioTrack track : tracks) {
scheduler.getQueueManager().queue(track, member);
}
context.reply(MessageFunction.buildAddingPlaylistMessage(tracks, parentTask.getPlaylist().getName(), member, SourceFinder.AudioSourceType.Youtube));
}
}
@Override
public LoadPlaylistTask getParentTask() {
return parentTask;
}
@Override
public boolean shouldKeepChilds() {
return false;
}
}
@@ -0,0 +1,45 @@
package me.bobert.netneutrality.tasks.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.ButtonTaskContext;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.tasks.ButtonTask;
public class CancelLoadTask extends ButtonTask implements WithParent<LoadPlaylistTask>, NotAcknowledgeable {
private final LoadPlaylistTask parentTask;
public CancelLoadTask(LoadPlaylistTask parentTask) {
this.parentTask = parentTask;
}
@Override
public String getName() {
return "cancel-playlist-loading";
}
@Override
public void replyToInteraction(CommandContext context) {
}
@Override
public LoadPlaylistTask getParentTask() {
return parentTask;
}
@Override
public boolean shouldKeepChilds() {
return false;
}
@Override
public void execute(ButtonTaskContext context) {
// Interaction will get removed after execution
}
}