Implement built-in tasks, improve message commands with arguments, fix command context in DM, better help message

This commit is contained in:
anthorob committed 2024-11-21 01:33:55 -05:00
1 parent e0ef3f9915
commit e6afe6e4c1
39 files changed
+436 -213

No files matched your search

@@ -23,4 +23,9 @@ public enum CommandType {
public int getId() {
return id;
}
@Override
public String toString() {
return description;
}
}
@@ -6,13 +6,23 @@ package me.bobert.netneutrality.command.basic;
*/
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.Command;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.context.MessageCommandContext;
import me.bobert.netneutrality.context.SlashCommandContext;
import me.bobert.netneutrality.core.command.CommandExecutionResult;
import me.bobert.netneutrality.core.command.CommandManager;
import me.bobert.netneutrality.core.context.Context;
import me.bobert.netneutrality.core.tasks.BuiltInTask;
import me.bobert.netneutrality.tasks.help.BotInfo;
import me.bobert.netneutrality.tasks.help.CommandHelp;
import me.bobert.netneutrality.tasks.help.ReportBug;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import java.util.List;
@@ -47,6 +57,7 @@ public class HelpCommand extends AbstractCommand {
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addOption(OptionType.STRING, "command", "The name of the command you want help with", false);
}
@Override
@@ -56,21 +67,36 @@ public class HelpCommand extends AbstractCommand {
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
if (context.getArgs().length == 0) {
List<MessageEmbed> help = MessageFunction.buildHelpMessage();
context.reply(new MessageCreateBuilder().addContent(context.getDiscordUser().getAsMention()).addContent(" help has been sent your way!").build());
context.sendMessageInPrivate(help);
}
sendHelp(context, context.getArgument(0));
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
List<MessageEmbed> help = MessageFunction.buildHelpMessage();
sendHelp(context, context.getEvent().getOption("command", null, OptionMapping::getAsString));
context.sendMessageInPrivate(help);
return CommandExecutionResult.success("Help has been sent in DM!");
}
public void sendHelp(Context context, String commandStr) {
if (commandStr == null) {
Button commands = BuiltInTask.createButton(CommandHelp.class).primary("Commands");
Button info = BuiltInTask.createButton(BotInfo.class).primary("Info");
Button reportbug = BuiltInTask.createButton(ReportBug.class).danger("Report bug");
ActionRow row = ActionRow.of(commands, info, reportbug);
context.sendMessageInPrivate("Thanks for using the Net Neutrality Bot! What do you need help with?", List.of(row));
return;
}
Command command = CommandManager.getCommand(commandStr);
if (command == null) {
context.reply("Command does not exist");
return;
}
context.reply(MessageFunction.buildCommandHelpMessage(command));
}
}
@@ -51,7 +51,7 @@ public class PingCommand extends AbstractCommand {
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
if (context.getArgs().length == 0)
if (context.getArgs().isEmpty())
context.reply(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing()));
return CommandExecutionResult.none();
@@ -64,14 +64,14 @@ public class ConfigureCommand extends AbstractAdminCommand {
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
String[] args = context.getArgs();
Map<Integer, String> args = context.getArgs();
GuildManager guildManager = GuildManager.getManager(context.getGuild());
if (args.length > 0) {
if (!args.isEmpty()) {
// Role for music commands
if (context.getArgument(0).equals("music-role")) {
if (args.length == 1) {
if (args.size() == 1) {
String roleDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE);
Role role = null;
if (!roleDb.isEmpty()) {
@@ -82,7 +82,7 @@ public class ConfigureCommand extends AbstractAdminCommand {
context.reply("Reset role using 'conf music-role reset' or set it using 'conf musicrole @'");
}
if (args.length == 2) {
if (args.size() == 2) {
if (context.getArgument(1).equals("reset")) {
if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE).isEmpty()) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
@@ -104,7 +104,7 @@ public class ConfigureCommand extends AbstractAdminCommand {
// Role for music commands
if (context.getArgument(0).equals("music-channel")) {
if (args.length == 1) {
if (args.size() == 1) {
String channelDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL);
TextChannel channel = null;
if (!channelDb.isEmpty()) {
@@ -115,7 +115,7 @@ public class ConfigureCommand extends AbstractAdminCommand {
context.reply("Reset channel using 'conf music-channel reset' or set it using 'conf music-channel #'");
}
if (args.length == 2) {
if (args.size() == 2) {
if (context.getArgument(1).equals("reset")) {
if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL).isEmpty()) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null);
@@ -137,16 +137,16 @@ public class ConfigureCommand extends AbstractAdminCommand {
// Default volume for music player
if (context.getArgument(0).equals("music-volume")) {
if (args.length == 1) {
if (args.size() == 1) {
int volume = Integer.parseInt(guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_VOLUME));
context.reply("Default volume for music player: %d", volume);
context.reply("Set using 'conf music-volume <value between 0 and 100>'");
}
if (args.length == 2) {
if (args.size() == 2) {
try {
int volume = Integer.parseInt(args[1]);
int volume = Integer.parseInt(args.get(1));
if (volume > 0 && volume <= 100) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume));
@@ -164,15 +164,15 @@ public class ConfigureCommand extends AbstractAdminCommand {
// Debug
if (context.getArgument(0).equals("debug")) {
if (args.length == 1) {
if (args.size() == 1) {
boolean debug = guildManager.isDebugEnabled();
context.reply("Error logging is currently %s for this guild.", (debug ? "enabled" : "disabled"));
context.reply("Use 'conf debug 0/1' to toggle between states (0 = disabled, 1 = enabled).");
}
if (args.length == 2) {
String str = args[1];
if (args.size() == 2) {
String str = args.get(1);
boolean debug = guildManager.isDebugEnabled();
if (str.equals("0") || str.equalsIgnoreCase("disable")) {
debug = false;
@@ -21,6 +21,7 @@ import org.json.JSONObject;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;
import java.util.Map;
public class CryptoPriceCommand extends AbstractCommand {
@@ -59,27 +60,27 @@ public class CryptoPriceCommand extends AbstractCommand {
@Override
public CommandType getType() {
return CommandType.CRYPTO;
return CommandType.UTILITY;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
String[] args = context.getArgs();
Map<Integer, String> args = context.getArgs();
String currency = "USD";
if (args.length == 0) {
if (args.isEmpty()) {
return CommandExecutionResult.warn("Usage: crypto <symbol> <currency>");
}
if (args.length == 2) {
if (args.size() == 2) {
try {
currency = Currency.getInstance(args[1].toUpperCase()).getCurrencyCode();
currency = Currency.getInstance(args.get(1).toUpperCase()).getCurrencyCode();
} catch (IllegalArgumentException exception) {
return CommandExecutionResult.warn("Please enter a valid currency");
}
}
String crypto = args[0].toUpperCase();
String crypto = args.get(0).toUpperCase();
String finalCurrency = currency;
context.async("Retreiving price, please wait...", asyncMessage -> {
@@ -41,7 +41,7 @@ public class PlayCommand extends AbstractMusicCommand {
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
if (context.getArgs().length == 0) {
if (context.getArgs().isEmpty()) {
return CommandExecutionResult.warn("Please specify a query");
}
@@ -50,7 +50,7 @@ public class SkipCommand extends AbstractMusicCommand {
}
if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) {
if (UserFunction.isAdmin(context.getGuildUser()) && context.getArgs().length > 0) {
if (UserFunction.isAdmin(context.getGuildUser()) && !context.getArgs().isEmpty()) {
try {
int skips = Integer.parseInt(context.getArgument(0));
return manager.getQueueManager().skip(skips);
@@ -28,7 +28,7 @@ public class AsyncContext extends AbstractContext {
}
public AsyncContext(Message message) {
super(message.getJDA(), message.getGuild(), message.getChannel(), message.getMember(), message.getAuthor());
super(message.getJDA(), message.isFromGuild() ? message.getGuild() : null, message.getChannel(), message.getMember(), message.getAuthor());
this.hook = null;
this.message = message;
}
@@ -14,7 +14,9 @@ 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -23,7 +25,7 @@ public class MessageCommandContext extends CommandContext {
private final Message message;
public MessageCommandContext(MessageReceivedEvent event) {
super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getAuthor());
super(event.getJDA(), event.isFromGuild() ? event.getGuild() : null, event.getChannel(), event.getMember(), event.getAuthor());
this.event = event;
this.message = event.getMessage();
}
@@ -34,24 +36,31 @@ public class MessageCommandContext extends CommandContext {
}
public String getArgument(int index) {
return getArgs()[index];
return getArgs().get(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(" ");
public Map<Integer, String> getArgs() {
Map<Integer, String> args = new HashMap<>();
if (args.length == 1 && args[0].isEmpty())
args = new String[0];
String msg = message.getContentDisplay().trim();
msg = msg.substring(Settings.getInstance().getPrefix().length()).trim();
String cmd = msg.split(" ", 2)[0];
String[] array = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split(" ");
for (int i = 0; i < array.length; i++) {
args.put(i, array[i]);
}
if (args.size() == 1 && args.get(0).isEmpty()) {
args.remove(0);
}
return args;
}
public String getMessageText() {
String msg = message.getContentDisplay().trim();
msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
msg = msg.substring(Settings.getInstance().getPrefix().length()).trim();
String cmd = msg.split(" ", 2)[0];
return msg.replaceFirst("(" + cmd + ")( )?", "");
@@ -123,7 +132,7 @@ public class MessageCommandContext extends CommandContext {
@Override
public String parseCommandName() {
String rawMessage = message.getContentDisplay().trim();
String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$");
String prefix = Settings.getInstance().getPrefix();
if (rawMessage.startsWith(prefix)) {
rawMessage = rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim();
@@ -137,7 +146,7 @@ public class MessageCommandContext extends CommandContext {
@Override
public String getSubCommand() {
if (getArgs().length > 0) {
if (!getArgs().isEmpty()) {
return getArgument(0);
}
@@ -7,8 +7,6 @@ package me.bobert.netneutrality.context.tasks;
import me.bobert.netneutrality.context.AsyncContext;
import me.bobert.netneutrality.core.context.TaskContext;
import net.dv8tion.jda.api.entities.MessageReaction;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
@@ -19,20 +17,12 @@ import java.util.function.Consumer;
public class EmojiTaskContext extends TaskContext {
private final MessageReactionAddEvent event;
private final EmojiUnion emoji;
public EmojiTaskContext(MessageReactionAddEvent event) {
super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser());
super(event.getJDA(), event.isFromGuild() ? event.getGuild() : null, event.getChannel(), event.getMember(), event.getUser());
this.event = event;
this.emoji = event.getEmoji();
}
public String getEmoji() {
return emoji.getAsReactionCode();
}
public MessageReaction getReaction() {
return event.getReaction();
addContextData("emoji", event.getEmoji());
addContextData("emoji-code", event.getEmoji().getAsReactionCode());
}
@Override
@@ -15,6 +15,7 @@ public class ModalTaskContext extends TaskContext implements InteractionContext
public ModalTaskContext(ModalInteractionEvent event) {
super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser());
this.event = event;
addContextData("values", event.getValues());
}
@Override
@@ -20,7 +20,6 @@ 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.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -69,11 +68,19 @@ public interface Context {
reply(messageCreateData, false, List.of(), asyncMessage -> {});
}
default void reply(MessageCreateData messageCreateData, List<LayoutComponent> components) {
reply(messageCreateData, false, components, asyncMessage -> {});
}
default void reply(String message) {
reply(MessageCreateData.fromContent(message));
}
default void reply(MessageEmbed... embeds) {
default void reply(MessageEmbed embed) {
reply(List.of(embed));
}
default void reply(List<MessageEmbed> embeds) {
reply(MessageCreateData.fromEmbeds(embeds));
}
@@ -103,13 +110,17 @@ public interface Context {
}
default void edit(MessageEmbed embed) {
edit(MessageEditData.fromEmbeds(embed));
edit(List.of(embed), List.of());
}
default void edit(MessageEmbed embeds, List<LayoutComponent> components) {
default void edit(List<MessageEmbed> embeds, List<LayoutComponent> components) {
edit(MessageEditData.fromEmbeds(embeds), components);
}
default void edit(MessageEmbed embed, List<LayoutComponent> components) {
edit(List.of(embed), components);
}
default void edit(String format, Object... arguments) {
edit(String.format(format, arguments));
}
@@ -154,11 +165,15 @@ public interface Context {
}
default void sendMessageInPrivate(String message) {
sendMessageInPrivate(MessageCreateData.fromContent(message));
sendMessageInPrivate(message, List.of());
}
default void sendMessageInPrivate(MessageEmbed... embeds) {
sendMessageInPrivate(Arrays.asList(embeds));
default void sendMessageInPrivate(String message, List<LayoutComponent> components) {
sendMessageInPrivate(MessageCreateData.fromContent(message), components);
}
default void sendMessageInPrivate(MessageEmbed embed) {
sendMessageInPrivate(List.of(embed));
}
default void sendMessageInPrivate(List<MessageEmbed> embeds) {
@@ -12,10 +12,15 @@ import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.modals.Modal;
import java.util.HashMap;
import java.util.Map;
public abstract class TaskContext extends AbstractContext {
private final Map<String, Object> contextData;
public TaskContext(JDA jda, Guild guild, MessageChannel channel, Member member, User user) {
super(jda, guild, channel, member, user);
this.contextData = new HashMap<>();
}
@Override
@@ -29,4 +34,12 @@ public abstract class TaskContext extends AbstractContext {
}
public abstract String getId();
public void addContextData(String key, Object value) {
contextData.put(key, value);
}
public Object getContextData(String key) {
return contextData.get(key);
}
}
@@ -0,0 +1,36 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltInTask {
@Override
public String getName() {
return getId();
}
@Override
public boolean isInsertable() {
return true;
}
@Override
public boolean oneshot() {
return false;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public void replyToInteraction(CommandContext context) {
// Task is loaded at runtime, this will never be called
}
}
@@ -27,7 +27,7 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe
@Override
public void replyToInteraction(CommandContext context) {
// Task loaded from database, this will never be called
}
@Override
@@ -25,6 +25,11 @@ public abstract class AbstractTask implements Task {
return creationTime;
}
@Override
public TaskButtonCreator createButton() {
return new TaskButtonCreator(getId());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -0,0 +1,23 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.lang.reflect.InvocationTargetException;
/**
* Represents a task that has a known ID and is loaded at runtime.
* This can be any task and is not necessarily a button.
*/
public interface BuiltInTask extends Task {
static TaskButtonCreator createButton(Class<? extends BuiltInTask> clazz) {
try {
BuiltInTask task = clazz.getConstructor().newInstance();
return new TaskButtonCreator(task.getId());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
@@ -8,11 +8,11 @@ package me.bobert.netneutrality.core.tasks;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import java.time.OffsetDateTime;
public interface Task {
/**
* <p>This method should generate a unique ID to differentiate it from other tasks.</p>
* <p>
@@ -59,4 +59,30 @@ public interface Task {
boolean isExpired();
OffsetDateTime getCreationTime();
TaskButtonCreator createButton();
class TaskButtonCreator {
private final String id;
public TaskButtonCreator(String id) {
this.id = id;
}
public Button primary(String label) {
return Button.primary(id, label);
}
public Button secondary(String label) {
return Button.secondary(id, label);
}
public Button success(String label) {
return Button.success(id, label);
}
public Button danger(String label) {
return Button.danger(id, label);
}
}
}
@@ -21,28 +21,22 @@ import me.bobert.netneutrality.thread.CleanupTaskThread;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class TaskManager {
private static final Logger logger = LogManager.getLogger(TaskManager.class);
private static final ScheduledExecutorService executor = ThreadManager.getScheduledExecutor();
private static TaskManager instance;
private final TaskService taskService;
private final Map<String, Task> tasks;
private final Map<String, PersistableTask> persistableTasks;
private final Map<String, CommandContext> deletableTasksContext;
private ScheduledFuture<?> cleanupThread;
private TaskManager() {
this.tasks = new HashMap<>();
this.persistableTasks = new HashMap<>();
this.deletableTasksContext = new HashMap<>();
this.taskService = new TaskService(this);
}
@@ -56,7 +50,10 @@ public class TaskManager {
}
public void init() {
loadBuiltInTasks();
taskService.loadTasks();
ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new CleanupTaskThread(), 1, 5, TimeUnit.MINUTES);
}
public TaskService getTaskService() {
@@ -64,37 +61,25 @@ public class TaskManager {
}
public Task getTask(String id) {
Task task = tasks.get(id);
if (task == null) {
task = persistableTasks.get(id);
}
return task;
return tasks.get(id);
}
public Collection<Task> getTasks() {
return tasks.values();
}
public Map<String, PersistableTask> getPersistableTaskMap() {
return persistableTasks;
public Map<String, Task> getTaskMap() {
return tasks;
}
public boolean exists(String id) {
boolean exists = tasks.containsKey(id);
if (!exists) {
exists = persistableTasks.containsKey(id);
}
return exists;
return tasks.containsKey(id);
}
public void insert(CommandContext context, Task task) {
String id = task.getId();
if (tasks.containsKey(id) || persistableTasks.containsKey(id)) {
if (tasks.containsKey(id)) {
context.reply("A task with the same id already exists!");
return;
}
@@ -108,12 +93,11 @@ public class TaskManager {
if (task.isInsertable()) {
logger.info("Inserting task: {}", task);
if (task instanceof PersistableTask) {
taskService.persistTask((PersistableTask) task);
persistableTasks.put(id, (PersistableTask) task);
} else {
tasks.put(id, task);
if (task instanceof PersistableTask persistableTask) {
taskService.persistTask(persistableTask);
}
tasks.put(id, task);
}
if (task instanceof WithChildren taskWithChildren) {
@@ -123,10 +107,6 @@ public class TaskManager {
insert(context, child);
}
}
if (!tasks.isEmpty() && cleanupThread == null) {
cleanupThread = executor.scheduleWithFixedDelay(new CleanupTaskThread(), 1, 5, TimeUnit.MINUTES);
}
}
public void removeTask(Task task) {
@@ -136,19 +116,11 @@ public class TaskManager {
logger.debug("Removing task: {}", task);
tasks.remove(id);
}
if (tasks.isEmpty() && cleanupThread != null) {
cleanupThread.cancel(false);
cleanupThread = null;
}
}
public void handle(TaskContext context) {
Task task = tasks.get(context.getId());
if (task == null) {
task = persistableTasks.get(context.getId());
}
String taskId = context.getId();
Task task = tasks.get(taskId);
if (task == null && (context instanceof ModalTaskContext || context instanceof ButtonTaskContext)) {
context.replyEphemeral("This task is no longer available, someone might have already used it or it might have expired");
@@ -182,6 +154,27 @@ public class TaskManager {
}
}
private void loadBuiltInTasks() {
logger.info("Loading built-in tasks...");
Set<Class<? extends BuiltInTask>> classes = new Reflections("me.bobert.netneutrality.tasks").getSubTypesOf(BuiltInTask.class);
for (Class<? extends BuiltInTask> s : classes) {
try {
if (Modifier.isAbstract(s.getModifiers())) {
continue;
}
BuiltInTask task = s.getConstructor().newInstance();
logger.info("Loaded task: {}", task);
tasks.put(task.getId(), task);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
logger.error("Failed to instantiate built-in task", e);
}
}
}
private void deleteInteraction(Task task) {
if (task instanceof WithParent<?> taskWithParent) {
Task parent = (Task) taskWithParent.getParentTask();
@@ -220,6 +213,10 @@ public class TaskManager {
}
private void removeTaskAndChildTasks(Task task) {
if (task instanceof BuiltInTask) {
return;
}
tasks.remove(task.getId());
if (task instanceof WithParent<?> taskWithParent && !taskWithParent.shouldKeepChilds()) {
@@ -233,6 +230,10 @@ public class TaskManager {
}
private void reinsertTaskAndChildTasks(Task task) {
if (task instanceof BuiltInTask) {
return;
}
tasks.put(task.getId(), task);
if (task instanceof WithParent<?> taskWithParent && taskWithParent.shouldKeepChilds()) {
@@ -11,7 +11,6 @@ import me.bobert.netneutrality.core.tasks.TaskManager;
import java.util.List;
public interface WithChildren {
List<Task> getChildTasks();
default void removeAllChildTasks() {
@@ -10,7 +10,6 @@ public enum GSettings {
MUSIC_ROLE("MUSIC_ROLE", ""),
MUSIC_CHANNEL("MUSIC_CHANNEL", ""),
MUSIC_VOLUME("MUSIC_VOLUME", "30"),
MUSIC_SEARCHRESULTS_LIMIT("MUSIC_SEARCHRESULTS_LIMIT", "5"),
MUSIC_MAXPLAYLISTSIZE("MUSIC_MAXPLAYLISTSIZE", "20");
private final String name;
@@ -43,8 +43,13 @@ public class TaskService {
JSONObject json = new JSONObject(payload);
PersistableTask task = creator.fromPayload(json);
if (TaskManager.getInstance().exists(task.getId())) {
logger.error("Task with id {} already exists", task.getId());
continue;
}
logger.info("Loaded task: {}", task);
TaskManager.getInstance().getPersistableTaskMap().put(task.getId(), task);
TaskManager.getInstance().getTaskMap().put(task.getId(), task);
} catch (Exception e) {
logger.error("Error loading task: {}", payload, e);
}
@@ -76,6 +81,6 @@ public class TaskService {
TaskRepository taskRepository = new TaskRepository();
taskRepository.deleteBy(task.getId());
taskManager.getPersistableTaskMap().remove(task.getId());
taskManager.getTaskMap().remove(task.getId());
}
}
@@ -57,7 +57,7 @@ public class TrackLoadingHandler implements AudioLoadResultHandler {
public void playlistLoaded(AudioPlaylist playlist) {
logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier);
TaskManager.getInstance().insert(context, new LoadPlaylistTask(playlist));
TaskManager.getInstance().insert(context, new LoadPlaylistTask(playlist, type));
}
@Override
@@ -13,7 +13,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import me.bobert.netneutrality.context.MusicChannelContext;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.Context;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.exceptions.ContextException;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import org.apache.logging.log4j.LogManager;
@@ -115,13 +115,8 @@ public class TrackScheduler extends AudioEventAdapter {
@Override
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
logger.error("Guild {}: exception while playing track: {}", guild.getId(), track.getIdentifier());
LogHandler.log(exception);
context.reply(
MessageFunction.buildSentryErrorMessage(
new RuntimeException(String.format("Error while playing track: %s", track.getInfo().title), exception)
)
);
LogHandler.log(new ContextException(String.format("Error while playing track: %s", track.getInfo().title), getContext(), exception));
}
@Override
@@ -132,7 +127,7 @@ public class TrackScheduler extends AudioEventAdapter {
lastStuckTrack = track;
logger.debug("Trying to load stuck track again: {}", track.getIdentifier());
context.async("Current track is stuck, trying to load it again", asyncMessage -> {
context.async("Current track is stuck, trying to load it again", asyncContext -> {
try {
TrackInfo info = queueManager.peekHeadOfQueue();
if (info != null && info.getTrack().getIdentifier().equals(track.getIdentifier())) {
@@ -152,15 +147,15 @@ public class TrackScheduler extends AudioEventAdapter {
playing.setPosition(lastPosition);
lastStuckTrack = null;
} else {
asyncMessage.edit("Failed to seek track to last timestamp, the track will play from the beginning");
asyncContext.edit("Failed to seek track to last timestamp, the track will play from the beginning");
}
} else {
asyncMessage.edit("Failed to load last playing track, skipping to next..");
asyncContext.edit("Failed to load last playing track, skipping to next..");
queueManager.startNextTrack();
}
} catch (Exception e) {
LogHandler.log(e);
asyncMessage.edit("Failed to load last playing track, skipping to next..");
asyncContext.edit("Failed to load last playing track, skipping to next..");
queueManager.startNextTrack();
}
});
@@ -5,8 +5,6 @@ package me.bobert.netneutrality.tasks;
* 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;
@@ -20,17 +18,6 @@ public abstract class ButtonTask extends AbstractTask {
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;
@@ -5,8 +5,6 @@ package me.bobert.netneutrality.tasks;
* 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 {
@@ -30,15 +28,4 @@ public abstract class EmojiTask extends AbstractPersistableTask {
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);
}
@@ -5,11 +5,12 @@ package me.bobert.netneutrality.tasks;
* 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 net.dv8tion.jda.api.interactions.modals.ModalMapping;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public abstract class ModalTask extends AbstractTask {
@@ -20,17 +21,6 @@ public abstract class ModalTask extends AbstractTask {
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;
@@ -40,4 +30,13 @@ public abstract class ModalTask extends AbstractTask {
public boolean isExpired() {
return getCreationTime().plusMinutes(5).isBefore(OffsetDateTime.now());
}
public ModalMapping getValue(String key, TaskContext context) {
List<?> mappings = (List<?>) context.getContextData("values");
return mappings.stream()
.filter(o -> o instanceof ModalMapping)
.map(o -> (ModalMapping) o)
.filter(mapping -> mapping.getId().equals(key))
.findFirst().orElse(null);
}
}
@@ -5,7 +5,7 @@ package me.bobert.netneutrality.tasks.emoji;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.EmojiTaskContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.tasks.EmojiTask;
import net.dv8tion.jda.api.entities.Role;
@@ -43,9 +43,11 @@ public class EmojiReactionRoleTask extends EmojiTask {
}
@Override
public void execute(EmojiTaskContext context) {
if (getEmojiMapping().containsKey(context.getEmoji())) {
Role role = context.getGuild().getRoleById(getEmojiMapping().get(context.getEmoji()));
public void execute(TaskContext context) {
String emoji = (String) context.getContextData("emoji-code");
if (getEmojiMapping().containsKey(emoji)) {
Role role = context.getGuild().getRoleById(getEmojiMapping().get(emoji));
if (role != null) {
context.getGuildManager().addRolesToMember(context.getGuildUser(), role);
@@ -6,9 +6,9 @@ package me.bobert.netneutrality.tasks.git;
*/
import io.sentry.Sentry;
import me.bobert.netneutrality.context.tasks.ModalTaskContext;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.tasks.ModalTask;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.entities.User;
@@ -53,7 +53,7 @@ public class ReportIssueTask extends ModalTask {
}
@Override
public void execute(ModalTaskContext context) {
public void execute(TaskContext context) {
context.ephemeral();
String gitlabHost = Settings.getInstance().get("GITLAB_HOST");
@@ -66,8 +66,8 @@ public class ReportIssueTask extends ModalTask {
return;
}
ModalMapping modalTitle = context.getEvent().getValue("title");
ModalMapping modalDescription = context.getEvent().getValue("description");
ModalMapping modalTitle = getValue("title", context);
ModalMapping modalDescription = getValue("description", context);
if (modalTitle == null || modalDescription == null) {
context.reply("Failed to create issue.");
@@ -0,0 +1,23 @@
package me.bobert.netneutrality.tasks.help;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask;
public class BotInfo extends AbstractBuiltInTask {
@Override
public String getId() {
return "help-info";
}
@Override
public void execute(TaskContext context) {
context.reply("For information about the bot and to follow the development, " +
"you can join the discord here: https://discord.gg/udVJwTgV");
}
}
@@ -0,0 +1,23 @@
package me.bobert.netneutrality.tasks.help;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask;
import me.bobert.netneutrality.utils.MessageFunction;
public class CommandHelp extends AbstractBuiltInTask {
@Override
public String getId() {
return "help-commands";
}
@Override
public void execute(TaskContext context) {
context.reply(MessageFunction.buildHelpMessage());
}
}
@@ -0,0 +1,23 @@
package me.bobert.netneutrality.tasks.help;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask;
public class ReportBug extends AbstractBuiltInTask {
@Override
public String getId() {
return "help-reportbug";
}
@Override
public void execute(TaskContext context) {
context.reply("To report a buuug with the bot, you can either use the /report-bug command " +
"or join the discord here: https://discord.gg/udVJwTgV");
}
}
@@ -6,12 +6,11 @@ package me.bobert.netneutrality.tasks.music;
*/
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.context.TaskContext;
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;
@@ -39,7 +38,7 @@ public class AddAllTracksTask extends ButtonTask implements WithParent<LoadPlayl
}
@Override
public void execute(ButtonTaskContext context) {
public void execute(TaskContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
Member member = context.getGuildUser();
@@ -5,8 +5,8 @@ package me.bobert.netneutrality.tasks.music;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.tasks.ButtonTaskContext;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.tasks.ButtonTask;
@@ -39,7 +39,7 @@ public class CancelLoadTask extends ButtonTask implements WithParent<LoadPlaylis
}
@Override
public void execute(ButtonTaskContext context) {
public void execute(TaskContext context) {
// Interaction will get removed after execution
}
}
@@ -7,32 +7,32 @@ package me.bobert.netneutrality.tasks.music;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
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.context.TaskContext;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.core.tasks.trait.Deletable;
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
import me.bobert.netneutrality.db.GSettings;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.SourceFinder;
import me.bobert.netneutrality.tasks.ButtonTask;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.ItemComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import java.util.ArrayList;
import java.util.List;
import static me.bobert.netneutrality.music.SourceFinder.AudioSourceType.Youtube;
public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deletable {
private final List<Task> childrens = new ArrayList<>();
private final AudioPlaylist playlist;
private final SourceFinder.AudioSourceType sourceType;
public LoadPlaylistTask(AudioPlaylist playlist) {
public LoadPlaylistTask(AudioPlaylist playlist, SourceFinder.AudioSourceType sourceType) {
this.playlist = playlist;
this.sourceType = sourceType;
}
public AudioPlaylist getPlaylist() {
@@ -56,9 +56,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
return;
}
int searchResultsLimit = Integer.parseInt(context.getGuildManager()
.getGuildConfigManager()
.getSetting(GSettings.MUSIC_SEARCHRESULTS_LIMIT));
int searchResultsLimit = 5; // Max number of buttons per row
int maxPlaylistSizeBeforeAsking = Integer.parseInt(context.getGuildManager()
.getGuildConfigManager()
.getSetting(GSettings.MUSIC_MAXPLAYLISTSIZE));
@@ -72,8 +70,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
AudioTrack track = searchResults.get(i);
Task buttonTask = new LoadTrackFromPlaylistTask(this, track);
Button button = Button.primary(buttonTask.getId(), String.valueOf(i + 1));
buttonList.add(button);
buttonList.add(buttonTask.createButton().primary(String.valueOf(i + 1)));
childrens.add(buttonTask);
}
@@ -98,25 +95,26 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
if (MusicUtils.connect(context)) {
manager.getQueueManager().queue(tracks, context.getGuildUser());
context.edit(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), context.getGuildUser(), Youtube));
context.edit(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), context.getGuildUser(), sourceType));
}
}
@Override
public void execute(ButtonTaskContext context) {
public void execute(TaskContext context) {
// Only the buttons will get executed, this task is only used to insert them
}
private ActionRow commonButtons(List<AudioTrack> tracks, String addAllText, String cancelText) {
Task addAllTracksTask = new AddAllTracksTask(this, tracks);
Button addAllButton = Button.success(addAllTracksTask.getId(), addAllText).withEmoji(Emoji.fromFormatted("U+1F60E"));
childrens.add(addAllTracksTask);
Task cancelTask = new CancelLoadTask(this);
Button cancelButton = Button.danger(cancelTask.getId(), cancelText).withEmoji(Emoji.fromFormatted("U+1F92E"));
childrens.add(cancelTask);
return ActionRow.of(addAllButton, cancelButton);
return ActionRow.of(
addAllTracksTask.createButton().success(addAllText).withEmoji(Emoji.fromFormatted("U+1F60E")),
cancelTask.createButton().danger(cancelText).withEmoji(Emoji.fromFormatted("U+1F92E"))
);
}
@Override
@@ -6,8 +6,8 @@ package me.bobert.netneutrality.tasks.music;
*/
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.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.SourceFinder;
@@ -36,7 +36,7 @@ public class LoadTrackFromPlaylistTask extends ButtonTask implements WithParent<
}
@Override
public void execute(ButtonTaskContext context) {
public void execute(TaskContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
Member member = context.getGuildUser();
@@ -4,21 +4,35 @@ package me.bobert.netneutrality.thread;/*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.BuiltInTask;
import me.bobert.netneutrality.core.tasks.PersistableTask;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.core.tasks.TaskManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
public class CleanupTaskThread implements Runnable {
private static final Logger logger = LogManager.getLogger(CleanupTaskThread.class);
@Override
public void run() {
List<Task> tasks = TaskManager.getInstance().getTasks()
.stream()
.filter(task -> !(task instanceof PersistableTask))
.filter(task -> !(task instanceof BuiltInTask))
.filter(Task::isExpired)
.toList();
if (!tasks.isEmpty()) {
logger.info("Cleaning up tasks..");
TaskManager.getInstance().getTasks()
.stream()
.filter(Task::isExpired)
.forEach(task -> TaskManager.getInstance().removeTask(task));
for (Task task : tasks) {
TaskManager.getInstance().removeTask(task);
}
}
}
}
@@ -200,39 +200,54 @@ public class MessageFunction {
return embed.build();
}
public static List<MessageEmbed> buildHelpMessage() {
Map<CommandType, EmbedBuilder> help = new HashMap<>();
public static String buildHelpMessage() {
Map<CommandType, String> help = new HashMap<>();
for (Command command : CommandManager.commands.values()) {
if (command.getType() == null)
if (command.getType() == null) {
continue;
String description = "No description yet";
}
if (command.isEnabled() && command.getType() != CommandType.UNLISTED) {
if (command.getDescription() != null)
description = command.getDescription();
String description = "No description yet";
EmbedBuilder message;
if (command.getDescription() != null) {
description = command.getDescription();
}
String message;
if (help.containsKey(command.getType())) {
message = help.get(command.getType());
} else {
help.put(command.getType(), message = new EmbedBuilder());
help.put(command.getType(), message = "");
}
if (message.isEmpty()) {
message.setTitle(command.getType().getDescription() + " commands for NetNeutrality");
message.setColor(Color.BLUE);
message = "## " + command.getType() + " commands";
}
message.addField(String.format("%s", command.getCommandName()), description, false);
message += "\n- /" + command.getCommandName() + " -> " + description;
help.put(command.getType(), message);
}
}
List<MessageEmbed> messages = new ArrayList<>();
help.values().forEach(embedBuilder -> messages.add(embedBuilder.build()));
String subtext = "\n\n" +
"-# The bot supports both types of commands: slash (/) and message ($), " +
"however not all slash commands might have a corresponding message command." +
"-# To get help for a specific command, please use /help <command> ";
return messages;
return String.join("\n", help.values()) + subtext;
}
public static String buildCommandHelpMessage(Command command) {
String message = "## Command info for /" + command.getCommandName();
if (command.getDescription() != null) {
message += "\n- Description: " + command.getDescription();
}
message += "\n\n-# *: required argument";
return message;
}
public static MessageEmbed buildSentryErrorMessage(Exception ex) {
@@ -72,6 +72,10 @@ public class Settings {
return property;
}
public String getPrefix() {
return get("NETNEUTRALITY_BOT_PREFIX", "$");
}
public String getMavenVersion() {
String version = null;