diff --git a/src/main/java/me/bobert/netneutrality/NetNeutrality.java b/src/main/java/me/bobert/netneutrality/NetNeutrality.java index b4c2d26..a29b212 100644 --- a/src/main/java/me/bobert/netneutrality/NetNeutrality.java +++ b/src/main/java/me/bobert/netneutrality/NetNeutrality.java @@ -45,7 +45,7 @@ public class NetNeutrality { options.setEnvironment(env); options.setTracesSampleRate(1.0); options.setDebug(false); - options.setRelease(VERSION); + options.setRelease(Settings.getVersion()); }); } diff --git a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java index cc0a7cb..1040340 100644 --- a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java @@ -11,7 +11,8 @@ import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.options.CommandOptionsBuilder; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.db.entities.OBlacklist; -import me.bobert.netneutrality.db.repository.BlacklistRepository; +import me.bobert.netneutrality.db.service.BlacklistService; +import me.bobert.netneutrality.db.service.GuildService; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; @@ -54,14 +55,14 @@ public class BlacklistCommand extends AbstractAdminCommand { public CommandExecutionResult execute(CommandContext context) { String sub = context.getSubcommand(); String word = context.getOption("word", "", CommandOption::getAsString); - BlacklistRepository repository = new BlacklistRepository(); + BlacklistService service = GuildService.get(context.getGuild()).getBlacklistService(); switch (sub) { case "get" -> context.async("Retrieving list, please wait...", asyncMessage -> { - List blacklists = repository.getBlacklistForGuild(context.getGuild()); + List blacklists = service.getBlacklistForGuild(); StringBuilder sb = new StringBuilder(); - sb.append("List of blacklisted words for this server [").append(context.getGuild().getName()).append("]: \n"); + sb.append("List of blacklisted words for this server: \n"); for (OBlacklist bl : blacklists) { sb.append(bl.getWord()).append(" -> ").append(bl.getEnabled() ? "enabled" : "disabled").append("\n"); } @@ -73,10 +74,10 @@ public class BlacklistCommand extends AbstractAdminCommand { asyncMessage.edit(sb.toString()); }); case "add" -> { - return repository.add(context.getGuild(), context.getGuildUser(), word); + return service.add(context.getGuildUser(), word); } case "remove" -> { - return repository.delete(context.getGuild(), word); + return service.delete(word); } } diff --git a/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java index 2d1a5d0..e16612b 100644 --- a/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java @@ -17,6 +17,9 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; +import java.util.List; +import java.util.Map; + public class ReactionCommand extends AbstractAdminCommand { @Override @@ -43,6 +46,7 @@ public class ReactionCommand extends AbstractAdminCommand { public void registerCommandOptions(CommandOptionsBuilder builder) { builder.addSubcommandGroups( new SubcommandGroupData("emoji", "Give roles to users using emojis").addSubcommands( + new SubcommandData("list", "Lists all registered tasks for this guild"), new SubcommandData("setup", "Add a role to a message").addOptions( new OptionData(OptionType.STRING, "messageid", "The ID of the message", true) .setRequiredLength(19, 19), @@ -77,65 +81,78 @@ public class ReactionCommand extends AbstractAdminCommand { String group = context.getSubcommandGroup(); String sub = context.getSubcommand(); - switch (group) { - case "emoji" -> { - if ("setup".equals(sub)) { - String emoji = context.getOption("emoji", CommandOption::getAsString); - String role = context.getOption("role", CommandOption::getAsString); + return switch (group) { + case "emoji" -> executeEmoji(context, guildId, messageId, sub); + default -> CommandExecutionResult.none(); + }; + } - EmojiReactionRoleTask task = new EmojiReactionRoleTask(guildId, messageId); - if (TaskManager.getInstance().exists(task.getId())) { - return CommandExecutionResult.error("The message ID is already registered, use the add subcommand instead."); - } - task.addEmoji(emoji, role); + private CommandExecutionResult executeEmoji(CommandContext context, String guildId, String messageId, String sub) { + String id = String.format("%s:%s", guildId, messageId); + EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); - TaskManager.getInstance().insert(context, task); - return CommandExecutionResult.success("Added emoji to message successfully!"); + switch (sub) { + case "list" -> { + List tasks = TaskManager.getInstance().findTasksByPartialId(EmojiReactionRoleTask.class, guildId); + if (tasks.isEmpty()) { + return CommandExecutionResult.info("No tasks found for this guild"); } - if ("add".equals(sub)) { - String emoji = context.getOption("emoji", CommandOption::getAsString); - String role = context.getOption("role", CommandOption::getAsString); + StringBuilder builder = new StringBuilder(); + builder.append("I have found the following tasks for this guild:"); - String id = String.format("%s:%s", guildId, messageId); - EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); - if (task == null) { - return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); - } - - task.addEmoji(emoji, role); - TaskManager.getInstance().getTaskService().persistTask(task); - return CommandExecutionResult.success("Added emoji to message successfully!"); + for (EmojiReactionRoleTask item : tasks) { + builder.append("\n- Task ID: ").append(item.getId()).append("\n") + .append(" Message ID: ").append(item.getMessageId()).append("\n") + .append(" Emoji Mapping: ").append(item.getEmojiMapping()).append("\n"); } - if ("remove".equals(sub)) { - String emoji = context.getOption("emoji", CommandOption::getAsString); - - String id = String.format("%s:%s", guildId, messageId); - EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); - if (task == null) { - return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); - } - - task.removeEmoji(emoji); - TaskManager.getInstance().getTaskService().persistTask(task); - return CommandExecutionResult.success("Removed emoji from message successfully!"); + return CommandExecutionResult.success(builder.toString()); + } + case "setup" -> { + if (task != null) { + return CommandExecutionResult.error("The message ID is already registered, use the add subcommand instead."); } - if ("delete".equals(sub)) { - String id = String.format("%s:%s", guildId, messageId); - EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); - if (task == null) { - return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); - } + String emoji = context.getOption("emoji", CommandOption::getAsString); + String role = context.getOption("role", CommandOption::getAsString); + task = new EmojiReactionRoleTask(guildId, messageId, Map.of(emoji, role)); + task.insert(context); - TaskManager.getInstance().getTaskService().unpersistTask(task); - return CommandExecutionResult.success("Unregistered the message ID successfully!"); + return CommandExecutionResult.success("Added emoji to message successfully!"); + } + case "add" -> { + if (task == null) { + return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); } + + String emoji = context.getOption("emoji", CommandOption::getAsString); + String role = context.getOption("role", CommandOption::getAsString); + task.addEmoji(emoji, role); + task.save(); + return CommandExecutionResult.success("Added emoji to message successfully!"); + } + case "remove" -> { + String emoji = context.getOption("emoji", CommandOption::getAsString); + + if (task == null) { + return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); + } + + task.removeEmoji(emoji); + task.save(); + return CommandExecutionResult.success("Removed emoji from message successfully!"); + } + case "delete" -> { + if (task == null) { + return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); + } + + task.delete(); + return CommandExecutionResult.success("Unregistered the message ID successfully!"); } } - return CommandExecutionResult.none(); } } diff --git a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java index bfd6081..5e3280f 100644 --- a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java @@ -61,9 +61,9 @@ public class HelpCommand extends AbstractCommand { public CommandExecutionResult execute(CommandContext context) { String command = context.getOption("command", null, CommandOption::getAsString); if (command == 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"); + Button commands = BuiltInTask.createButton(CommandHelp.class); + Button info = BuiltInTask.createButton(BotInfo.class); + Button reportbug = BuiltInTask.createButton(ReportBug.class); ActionRow row = ActionRow.of(commands, info, reportbug); context.replyEphemeral("Thanks for using the Net Neutrality Bot! What do you need help with?", List.of(row)); return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java index 10831e4..e9c9652 100644 --- a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java @@ -5,13 +5,13 @@ package me.bobert.netneutrality.command.bot; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.core.command.AbstractAdminCommand; import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.options.CommandOptionsBuilder; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.db.GSettings; +import me.bobert.netneutrality.db.service.GuildService; import me.bobert.netneutrality.music.MusicManager; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; @@ -63,7 +63,7 @@ public class ConfigureCommand extends AbstractAdminCommand { @Override public CommandExecutionResult execute(CommandContext context) { String sub = context.getSubcommand(); - GuildManager guildManager = GuildManager.getManager(context.getGuild()); + GuildService guildService = GuildService.get(context.getGuild().getId()); context.ephemeral(); if ("music-role".equals(sub)) { @@ -71,13 +71,13 @@ public class ConfigureCommand extends AbstractAdminCommand { assert role != null; if (role.getPosition() == -1) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null); + guildService.getGuildSettingService().setSettingValue(GSettings.MUSIC_ROLE, null); return CommandExecutionResult.success("Everyone can now use music commands"); } if (role.getGuild().getId().equals(context.getGuild().getId())) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, role.getId()); + guildService.getGuildSettingService().setSettingValue(GSettings.MUSIC_ROLE, role.getId()); return CommandExecutionResult.success(String.format("Successfully set %s as the role needed for music command", role.getAsMention())); } @@ -87,14 +87,14 @@ public class ConfigureCommand extends AbstractAdminCommand { GuildChannelUnion channel = context.getOption("channel", CommandOption::getAsChannel); if (channel == null) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null); + guildService.getGuildSettingService().setSettingValue(GSettings.MUSIC_CHANNEL, null); return CommandExecutionResult.success("No channel restrictions for music commands"); } if (channel instanceof TextChannel) { if (channel.getGuild().getId().equals(context.getGuild().getId())) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId()); + guildService.getGuildSettingService().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId()); return CommandExecutionResult.success(String.format("Successfully set %s as the channel required to execute music command", channel.getAsMention())); } @@ -104,7 +104,7 @@ public class ConfigureCommand extends AbstractAdminCommand { if ("music-volume".equals(sub)) { int volume = context.getOption("volume", 30, CommandOption::getAsInt); - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume)); + guildService.getGuildSettingService().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume)); MusicManager.get(context.getGuild()).setVolume(volume); return CommandExecutionResult.success(String.format("Default volume for music player is now %d", volume)); @@ -112,13 +112,13 @@ public class ConfigureCommand extends AbstractAdminCommand { if ("error-logging".equals(sub)) { boolean debug = context.getOption("debug", true, CommandOption::getAsBoolean); - guildManager.setDebug(debug); + guildService.updateDebugMode(debug); return CommandExecutionResult.success("Error logging is now %s for this guild", (debug ? "enabled" : "disabled")); } if ("show-all".equals(sub)) { - Map settings = guildManager.getGuildConfigManager().getSettings(); + Map settings = guildService.getGuildSettingService().getSettings(); MessageCreateBuilder builder = new MessageCreateBuilder(); for (GSettings o : GSettings.values()) { diff --git a/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java b/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java index e65f342..94a6670 100644 --- a/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java @@ -11,10 +11,12 @@ import me.bobert.netneutrality.core.command.CommandCategory; import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.core.command.options.CommandOptionsBuilder; import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.http.WebModule; import me.bobert.netneutrality.tasks.git.ReportIssueTask; import me.bobert.netneutrality.utils.Settings; +import java.util.concurrent.TimeUnit; + public class ReportIssueCommand extends AbstractCommand { @Override public boolean needAdmin() { @@ -66,8 +68,10 @@ public class ReportIssueCommand extends AbstractCommand { return CommandExecutionResult.error("Command is not configured, failed to create issue."); } - TaskManager.getInstance().insert(context, new ReportIssueTask(context instanceof SlashCommandContext)); + if (new WebModule.Builder(gitlabHost).ping().timeout(2, TimeUnit.SECONDS).build().request().isSuccess()) { + return CommandExecutionResult.task(new ReportIssueTask(context)); + } - return CommandExecutionResult.none(); + return CommandExecutionResult.error("Unable to create bug reports at this time, please try again later."); } } diff --git a/src/main/java/me/bobert/netneutrality/context/AsyncContext.java b/src/main/java/me/bobert/netneutrality/context/AsyncContext.java index b58a627..a4ca65e 100644 --- a/src/main/java/me/bobert/netneutrality/context/AsyncContext.java +++ b/src/main/java/me/bobert/netneutrality/context/AsyncContext.java @@ -34,6 +34,14 @@ public class AsyncContext extends AbstractContext { this.message = message; } + public String getMessageId() { + if (hook != null) { + return hook.getId(); + } + + return message.getId(); + } + @Override public Event getEvent() { return null; @@ -60,6 +68,10 @@ public class AsyncContext extends AbstractContext { @Override public boolean isAcknowledged() { + if (hook != null) { + return hook.getInteraction().isAcknowledged(); + } + return true; } diff --git a/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java b/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java index 7144fd0..7dd80f3 100644 --- a/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java +++ b/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java @@ -6,19 +6,17 @@ package me.bobert.netneutrality.context; */ import me.bobert.netneutrality.core.context.AbstractContext; +import me.bobert.netneutrality.core.context.NoMessageContext; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; 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 java.util.List; import java.util.function.Consumer; -public class MusicChannelContext extends AbstractContext { +public class MusicChannelContext extends AbstractContext implements NoMessageContext { public MusicChannelContext(GuildMessageChannel channel, Member member) { super(channel.getJDA(), channel.getGuild(), channel, member, member.getUser()); @@ -34,21 +32,6 @@ public class MusicChannelContext extends AbstractContext { return String.format("music:%s", getChannel().getId()); } - @Override - public ChannelType getType() { - return getChannel().getType(); - } - - @Override - public InteractionHook getInteractionHook() { - return null; - } - - @Override - public boolean isAcknowledged() { - return true; - } - @Override public boolean isFromGuild() { return true; @@ -59,25 +42,10 @@ public class MusicChannelContext extends AbstractContext { } - @Override - public void reply(Modal modal) { - - } - @Override public void reply(MessageCreateData messageCreateData, boolean ephemeral, List components, Consumer consumer) { if (getChannel().canTalk()) { getChannel().sendMessage(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message))); } } - - @Override - public void defer(boolean ephemeral, Consumer consumer) { - reply("Bot is thinking", consumer); - } - - @Override - public void ack(boolean ephemeral) { - - } } diff --git a/src/main/java/me/bobert/netneutrality/context/command/MessageCommandContext.java b/src/main/java/me/bobert/netneutrality/context/command/MessageCommandContext.java index 510d0d7..7864318 100644 --- a/src/main/java/me/bobert/netneutrality/context/command/MessageCommandContext.java +++ b/src/main/java/me/bobert/netneutrality/context/command/MessageCommandContext.java @@ -5,31 +5,25 @@ package me.bobert.netneutrality.context.command; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.context.AsyncContext; import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.options.MessageCommandOption; import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.context.MessageContext; import me.bobert.netneutrality.utils.Settings; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -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.ArrayList; import java.util.List; import java.util.Set; import java.util.StringJoiner; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -public class MessageCommandContext extends CommandContext { +public class MessageCommandContext extends CommandContext implements MessageContext { private final MessageReceivedEvent event; private final Message message; @@ -54,63 +48,10 @@ public class MessageCommandContext extends CommandContext { } @Override - public ChannelType getType() { - return event.getChannelType(); - } - public Message getMessage() { return message; } - @Override - public boolean isAcknowledged() { - return true; - } - - @Override - public boolean isFromGuild() { - return event.isFromGuild(); - } - - @Override - public InteractionHook getInteractionHook() { - return null; - } - - @Override - public void defer(boolean ephemeral, Consumer consumer) { - reply("Bot is thinking...", consumer); - } - - @Override - public void reply(MessageCreateData messageCreateData, boolean ephemeral, List components, Consumer consumer) { - if (ephemeral) { - sendMessageInPrivate(messageCreateData, components); - return; - } - - if (getChannel() != null && getChannel().canTalk()) { - message.reply(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message))); - } - } - - @Override - public void reply(Modal modal) { - - } - - @Override - public void ack(boolean ephemeral) { - - } - - @Override - public void deleteMessage() { - if (message != null && getType() != ChannelType.PRIVATE) { - message.delete().queueAfter(1, TimeUnit.SECONDS); - } - } - @Override public List parseOptions(String[] args, List commandOptions) { List options = new ArrayList<>(); diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java index 2bc4911..3a93ede 100644 --- a/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java +++ b/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java @@ -6,6 +6,7 @@ package me.bobert.netneutrality.context.tasks; */ import me.bobert.netneutrality.context.AsyncContext; +import me.bobert.netneutrality.core.context.NoMessageContext; import me.bobert.netneutrality.core.context.TaskContext; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; @@ -17,7 +18,8 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData; import java.util.List; import java.util.function.Consumer; -public class EmojiTaskContext extends TaskContext { +public class EmojiTaskContext extends TaskContext implements NoMessageContext { + public static final String ID = "et:%s:%s"; private final MessageReactionAddEvent event; public EmojiTaskContext(MessageReactionAddEvent event) { @@ -32,29 +34,9 @@ public class EmojiTaskContext extends TaskContext { return event; } - @Override - public ChannelType getType() { - return event.getChannelType(); - } - @Override public String getId() { - return String.format("%s:%s", getGuild().getId(), event.getMessageId()); - } - - @Override - public InteractionHook getInteractionHook() { - return null; - } - - @Override - public void defer(boolean ephemeral, Consumer consumer) { - reply("Bot is thinking...", consumer); - } - - @Override - public boolean isAcknowledged() { - return true; + return String.format(ID, getGuild().getId(), event.getMessageId()); } @Override @@ -62,29 +44,10 @@ public class EmojiTaskContext extends TaskContext { return event.isFromGuild(); } - @Override - public void reply(Modal modal) { - - } - @Override public void reply(MessageCreateData messageCreateData, boolean ephemeral, List components, Consumer consumer) { if (getChannel() != null && getChannel().canTalk()) { getChannel().sendMessage(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message))); } } - - @Override - public void replyEphemeral(MessageCreateData messageCreateData) { - if (getType() == ChannelType.PRIVATE) { - return; - } - - sendMessageInPrivate(messageCreateData); - } - - @Override - public void ack(boolean ephemeral) { - - } } diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/MessageTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/MessageTaskContext.java new file mode 100644 index 0000000..ae17857 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/tasks/MessageTaskContext.java @@ -0,0 +1,55 @@ +package me.bobert.netneutrality.context.tasks; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.MessageContext; +import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageReference; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.events.Event; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +public class MessageTaskContext extends TaskContext implements MessageContext { + private final MessageReceivedEvent event; + private final Message message; + private final String id; + + public MessageTaskContext(MessageReceivedEvent event) { + super(event.getJDA(), event.isFromGuild() ? event.getGuild() : null, event.getChannel(), event.getMember(), event.getAuthor()); + this.event = event; + this.message = event.getMessage(); + + MessageReference reference = message.getMessageReference(); + this.id = reference == null ? event.getMessageId() : reference.getMessageId(); + } + + @Override + public Event getEvent() { + return event; + } + + @Override + public String getId() { + return id; + } + + @Override + public void replyEphemeral(MessageCreateData messageCreateData) { + if (getType() == ChannelType.PRIVATE) { + return; + } + + sendMessageInPrivate(messageCreateData); + } + + @Override + public Message getMessage() { + return message; + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/BotState.java b/src/main/java/me/bobert/netneutrality/core/BotState.java index 0422632..fbd0793 100644 --- a/src/main/java/me/bobert/netneutrality/core/BotState.java +++ b/src/main/java/me/bobert/netneutrality/core/BotState.java @@ -5,9 +5,9 @@ package me.bobert.netneutrality.core; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.db.service.GuildService; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.music.TrackScheduler; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import org.json.JSONArray; import org.json.JSONObject; @@ -21,18 +21,21 @@ public class BotState { //TODO: is it needed? private static void saveGuildsState() { - Map managers = GuildManager.getManagers(); + Map services = GuildService.getServices(); JSONObject main = new JSONObject(); JSONArray guildsArray = new JSONArray(); - for (GuildManager manager : managers.values()) { - MusicManager musicManager = MusicManager.get(manager.getGuild()); - TrackScheduler trackScheduler = musicManager.getScheduler(); + for (MusicManager manager : MusicManager.getManagers()) { + TrackScheduler trackScheduler = manager.getScheduler(); + } + + for (GuildService service : services.values()) { + JSONObject guild = new JSONObject(); JSONObject music = new JSONObject(); - guild.put("id", manager.getGuild().getId()); + guild.put("id", service.getEntity().getGuildId()); // MessageChannel currentChannel = trackScheduler.getTextChannel(); // if (currentChannel == null) { diff --git a/src/main/java/me/bobert/netneutrality/core/GuildManager.java b/src/main/java/me/bobert/netneutrality/core/GuildManager.java deleted file mode 100644 index 120289c..0000000 --- a/src/main/java/me/bobert/netneutrality/core/GuildManager.java +++ /dev/null @@ -1,157 +0,0 @@ -package me.bobert.netneutrality.core; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.NetNeutrality; -import me.bobert.netneutrality.db.entities.OBlacklist; -import me.bobert.netneutrality.db.entities.OGuild; -import me.bobert.netneutrality.db.repository.GuildRepository; -import me.bobert.netneutrality.utils.JpaUtils; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Role; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class GuildManager { - - private static final Logger logger = LogManager.getLogger(GuildManager.class); - private static final Map managers = new ConcurrentHashMap<>(); - private final GuildRepository guildRepository = new GuildRepository(); - private final Set blacklistCache = new HashSet<>(); - private final GuildConfigManager guildConfigManager; - private final Guild guild; - private OGuild guildEntity; - - private GuildManager(Guild guild) { - this.guild = guild; - loadGuildFromDb(guild.getId()); - this.guildConfigManager = new GuildConfigManager(guild, this.guildEntity); - - reloadBlacklistCache(); - } - - public static GuildManager getManager(Guild guild) { - if (managers.containsKey(guild.getId())) { - return managers.get(guild.getId()); - } - - GuildManager manager = new GuildManager(guild); - managers.put(guild.getId(), manager); - - return manager; - } - - public static GuildManager getManager(String guildId) { - if (managers.containsKey(guildId)) { - return managers.get(guildId); - } - - Guild temp = NetNeutrality.getInstance().getJDA().getGuildById(guildId); - if (temp == null) { - throw new IllegalArgumentException("Guild does not exist in current cache"); - } - - return getManager(temp); - } - - // Getter - public static Map getManagers() { - return managers; - } - - public Guild getGuild() { - return guild; - } - - public OGuild getGuildEntity() { - return guildEntity; - } - - public Set getBlacklistCache() { - return blacklistCache; - } - - public GuildConfigManager getGuildConfigManager() { - return guildConfigManager; - } - - // Methods - public boolean isLoggingEnabled() { - return guildEntity.getLogging(); - } - - public boolean isDebugEnabled() { - return guildEntity.getDebug(); - } - - public void addRolesToMember(Member member, Role role) { - Member self = guild.getSelfMember(); - if (!self.canInteract(role)) { - logger.error("Missing permissions to interact with role {} in guild {}", role.getId(), guild.getId()); - } - guild.addRoleToMember(member, role).queue(); - } - - public void setDebug(boolean toggle) { - if (toggle == isDebugEnabled()) - return; - - JpaUtils.updateWithTransaction(guildEntity, () -> guildEntity.setDebug(toggle)); - } - - public void reloadBlacklistCache() { - logger.debug("Reloading blacklist cache for guild: {}", guild.getId()); - blacklistCache.clear(); - - JpaUtils.getWithSession(guildEntity, guildEntity.getGuildId(), session -> { - Set oBlacklistSet = guildEntity.getBlacklists(); - - for (OBlacklist bl : oBlacklistSet) { - if (bl.getEnabled()) { - blacklistCache.add(bl.getWord()); - } - } - }); - } - - private void loadGuildFromDb(String guildId) { - JpaUtils.runWithTransaction(session -> { - OGuild entity = guildRepository.findById(guildId); - - if (entity == null) { - logger.debug("Guild[{}] doesn't exist in DB, creating entity..", guildId); - - entity = new OGuild(); - entity.setGuildId(guildId); - session.persist(entity); - - guild.getTextChannels() - .stream() - .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log")) - .forEach(textChannel -> textChannel.sendMessage("Error logging enabled on channel " + textChannel.getAsMention()).queue()); - } - - guildEntity = entity; - }); - } - - public void reloadEntity() { - loadGuildFromDb(guild.getId()); - } - - public void removeFromDB() { - guildRepository.deleteBy(guild.getId()); - - managers.remove(guild.getId()); - } - -} diff --git a/src/main/java/me/bobert/netneutrality/core/LogHandler.java b/src/main/java/me/bobert/netneutrality/core/LogHandler.java index 305b10f..790036a 100644 --- a/src/main/java/me/bobert/netneutrality/core/LogHandler.java +++ b/src/main/java/me/bobert/netneutrality/core/LogHandler.java @@ -9,6 +9,7 @@ import io.sentry.Sentry; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.context.Context; import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.db.service.GuildService; import me.bobert.netneutrality.exceptions.ContextException; import me.bobert.netneutrality.utils.MessageFunction; import net.dv8tion.jda.api.entities.Guild; @@ -49,13 +50,12 @@ public class LogHandler { } public static void sendMessageToDebugChannel(Guild guild, String message) { - if (GuildManager.getManager(guild).isDebugEnabled()) { - guild.getTextChannels() - .stream() - .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log")) - .filter(TextChannel::canTalk) - .forEach(textChannel -> textChannel.sendMessage(message).queue()); - } + guild.getTextChannels() + .stream() + .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log")) + .filter(TextChannel::canTalk) + .forEach(textChannel -> textChannel.sendMessage(message).queue()); + } private static void logInternally(ContextException exception) { @@ -80,7 +80,7 @@ public class LogHandler { Context context = exception.getContext(); if (context.getGuild() != null) { - if (context.getGuildManager().isDebugEnabled()) { + if (GuildService.get(context.getGuild()).isDebugEnabled()) { context.getGuild() .getTextChannels() .stream() diff --git a/src/main/java/me/bobert/netneutrality/core/command/AbstractMessageMenuCommand.java b/src/main/java/me/bobert/netneutrality/core/command/AbstractMessageMenuCommand.java index 3ea7a84..1dfc4cd 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/AbstractMessageMenuCommand.java +++ b/src/main/java/me/bobert/netneutrality/core/command/AbstractMessageMenuCommand.java @@ -17,9 +17,14 @@ public abstract class AbstractMessageMenuCommand extends AbstractCommand { return null; } + @Override + public boolean isGuildOnly() { + return true; + } + @Override public boolean isSlashCommandOnly() { - return false; + return true; } @Override @@ -27,6 +32,11 @@ public abstract class AbstractMessageMenuCommand extends AbstractCommand { // no options for context commands } + @Override + public CommandCategory getCategory() { + return CommandCategory.UNLISTED; + } + @Override public Type getType() { return Type.MESSAGE; diff --git a/src/main/java/me/bobert/netneutrality/core/command/AbstractMusicCommand.java b/src/main/java/me/bobert/netneutrality/core/command/AbstractMusicCommand.java index 61ea77b..a4e9c8d 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/AbstractMusicCommand.java +++ b/src/main/java/me/bobert/netneutrality/core/command/AbstractMusicCommand.java @@ -5,8 +5,8 @@ package me.bobert.netneutrality.core.command; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.db.GSettings; +import me.bobert.netneutrality.db.service.GuildService; import me.bobert.netneutrality.utils.UserFunction; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; @@ -39,7 +39,8 @@ public abstract class AbstractMusicCommand extends AbstractCommand { } Guild guild = member.getGuild(); - String roleDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE); + + String roleDb = GuildService.get(guild.getId()).getGuildSettingService().getSetting(GSettings.MUSIC_ROLE); if (roleDb != null && !roleDb.isEmpty()) { if (UserFunction.isAdmin(member)) { @@ -49,7 +50,7 @@ public abstract class AbstractMusicCommand extends AbstractCommand { Role neededRole = guild.getRoleById(roleDb); if (neededRole == null) { // Role doesn't exist on guild anymore, remove from db - GuildManager.getManager(guild).getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null); + GuildService.get(guild.getId()).getGuildSettingService().setSettingValue(GSettings.MUSIC_ROLE, null); return true; } @@ -65,7 +66,7 @@ public abstract class AbstractMusicCommand extends AbstractCommand { if (channel.getType() != ChannelType.TEXT) return false; - String channelDb = GuildManager.getManager(guildMessageChannel.getGuild()).getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL); + String channelDb = GuildService.get(guildMessageChannel.getGuild()).getGuildSettingService().getSetting(GSettings.MUSIC_CHANNEL); if (channelDb != null && !channelDb.isEmpty()) { return channelDb.equals(channel.getId()); } diff --git a/src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java b/src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java index d2206ee..df73543 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java @@ -5,26 +5,38 @@ package me.bobert.netneutrality.core.command; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.tasks.Task; + public class CommandExecutionResult { public enum Result { - SUCCESS, INFO, WARN, ERROR, NOACTION + SUCCESS, INFO, WARN, ERROR, NOACTION, TASK } private final Result result; private final String message; + private final Object data; private final Exception exception; - private CommandExecutionResult(Result result, String message, Exception exception) { + private CommandExecutionResult(Result result, String message, Object data, Exception exception) { this.result = result; this.message = message; + this.data = data; this.exception = exception; } + private CommandExecutionResult(Result result, String message, Exception exception) { + this(result, message, null, exception); + } + private CommandExecutionResult(Result result, String message) { this(result, message, null); } + private CommandExecutionResult(Result result, Object data) { + this(result, null, data, null); + } + public Result getResult() { return result; } @@ -33,6 +45,10 @@ public class CommandExecutionResult { return message; } + public Object getData() { + return data; + } + public Exception getException() { return exception; } @@ -65,4 +81,7 @@ public class CommandExecutionResult { return new CommandExecutionResult(Result.NOACTION, null); } + public static CommandExecutionResult task(Task task) { + return new CommandExecutionResult(Result.TASK, null, task, null); + } } diff --git a/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java b/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java index b0e2627..2d1723d 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java @@ -14,15 +14,13 @@ import org.apache.logging.log4j.Logger; import org.reflections.Reflections; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public class CommandManager { private static final Logger logger = LogManager.getLogger(CommandManager.class); - public static final HashMap commands = new HashMap<>(); + public static final Map commands = new ConcurrentHashMap<>(); public static void init() { logger.info("Loading commands.."); diff --git a/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java b/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java index c0c5615..0b506b5 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java @@ -11,7 +11,10 @@ import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.core.command.options.CommandOptions; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.context.InteractionContext; +import me.bobert.netneutrality.core.tasks.Task; +import me.bobert.netneutrality.core.tasks.TaskManager; import me.bobert.netneutrality.exceptions.CommandExecutionException; +import me.bobert.netneutrality.exceptions.GracefulContextError; import me.bobert.netneutrality.utils.UserFunction; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; @@ -26,8 +29,7 @@ import java.util.List; import java.util.Map; import static java.util.function.Predicate.not; -import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.ERROR; -import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.NOACTION; +import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.*; public class CommandProcessor { @@ -56,13 +58,13 @@ public class CommandProcessor { return; } - if (command.isSlashCommandOnly() && !(context instanceof SlashCommandContext)) { + if (command.isSlashCommandOnly() && !(context instanceof InteractionContext)) { context.replyEphemeral("This command is only available as a slash command"); return; } try { - logger.info("{} ID: {}", context.getClass().getSimpleName(), context.getId()); + logger.info("Context ID: {}", context.getId()); logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild() == null ? "PrivateMessage" : context.getGuild().getId()); configureSentry(command); @@ -95,17 +97,28 @@ public class CommandProcessor { return; } + if (result == TASK) { + TaskManager.getInstance().insert(context, (Task) commandExecutionResult.getData()); + } + String message = commandExecutionResult.getMessage(); if (result == ERROR) { logger.error("Context[{}]: Error from command: {}", context.getId(), message, commandExecutionResult.getException()); } - context.reply(message); - } catch (Exception exception) { - if (exception instanceof CommandExecutionException) { - LogHandler.log(exception); + if (result == WARN) { + logger.warn("Context[{}]: Warning from command: {}", context.getId(), message); } + if (message != null) { + context.reply(message); + } + } catch (GracefulContextError exception) { + logger.error(exception.getMessage()); + context.replyEphemeral(exception.getMessage()); + } catch (CommandExecutionException exception) { + LogHandler.log(exception); + } catch (Exception exception) { LogHandler.log(new CommandExecutionException("Error during command execution", context, exception)); } } diff --git a/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java b/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java index 7d45998..ca55682 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java +++ b/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java @@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.context; */ import io.sentry.Sentry; +import me.bobert.netneutrality.context.command.SlashCommandContext; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; diff --git a/src/main/java/me/bobert/netneutrality/core/context/Context.java b/src/main/java/me/bobert/netneutrality/core/context/Context.java index 5f44ab7..bcf7ebd 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/Context.java +++ b/src/main/java/me/bobert/netneutrality/core/context/Context.java @@ -6,7 +6,6 @@ package me.bobert.netneutrality.core.context; */ import me.bobert.netneutrality.context.AsyncContext; -import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.utils.ContextUtils; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.*; @@ -55,10 +54,6 @@ public interface Context { void ack(boolean ephemeral); - default GuildManager getGuildManager() { - return GuildManager.getManager(getGuild()); - } - default void ack() { ack(false); } @@ -160,33 +155,39 @@ public interface Context { replyEphemeral(MessageCreateData.fromEmbeds(embeds)); } - default void sendMessageInPrivate(MessageCreateData messageCreateData, List components) { + default void sendMessageInPrivate(MessageCreateData messageCreateData, List components, Consumer consumer) { User user = getDiscordUser(); if (user != null) { - user.openPrivateChannel().flatMap(channel -> channel.sendMessage(messageCreateData).setComponents(components)).queue(); + user.openPrivateChannel() + .flatMap(channel -> channel.sendMessage(messageCreateData).setComponents(components)) + .queue(message -> consumer.accept(new AsyncContext(message))); } } + default void sendMessageInPrivate(String message, List components, Consumer consumer) { + sendMessageInPrivate(MessageCreateData.fromContent(message), components, consumer); + } + + default void sendMessageInPrivate(MessageCreateData messageCreateData, List components) { + sendMessageInPrivate(messageCreateData, components, asyncContext -> {}); + } + + default void sendMessageInPrivate(String message, Consumer consumer) { + sendMessageInPrivate(message, List.of(), consumer); + } + default void sendMessageInPrivate(MessageCreateData messageCreateData) { sendMessageInPrivate(messageCreateData, List.of()); } - default void sendMessageInPrivate(String message) { - sendMessageInPrivateWithLimit(message); - } - - default void sendMessageInPrivate(String message, List components) { - sendMessageInPrivate(MessageCreateData.fromContent(message), components); - } - - default void sendMessageInPrivate(MessageEmbed embed) { - sendMessageInPrivate(List.of(embed)); - } - default void sendMessageInPrivate(List embeds) { sendMessageInPrivate(MessageCreateData.fromEmbeds(embeds)); } + default void sendMessageInPrivate(String message) { + sendMessageInPrivateWithLimit(message); + } + private void replyWithLimit(String message) { for (String chunk : ContextUtils.splitMessage(message, Message.MAX_CONTENT_LENGTH)) { reply(MessageCreateData.fromContent(chunk)); diff --git a/src/main/java/me/bobert/netneutrality/core/context/MessageContext.java b/src/main/java/me/bobert/netneutrality/core/context/MessageContext.java new file mode 100644 index 0000000..cf33c24 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/context/MessageContext.java @@ -0,0 +1,45 @@ +package me.bobert.netneutrality.core.context; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.AsyncContext; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public interface MessageContext extends NoMessageContext { + Message getMessage(); + + @Override + default boolean isFromGuild() { + return getMessage().isFromGuild(); + } + + @Override + default void deleteMessage() { + if (getMessage() != null && getType() != ChannelType.PRIVATE) { + getMessage().delete().queueAfter(1, TimeUnit.SECONDS); + } + } + + @Override + default void reply(MessageCreateData messageCreateData, boolean ephemeral, List components, Consumer consumer) { + if (ephemeral && getType() != ChannelType.PRIVATE) { + sendMessageInPrivate(messageCreateData, components); + return; + } + + if (getChannel() != null && getChannel().canTalk()) { + getMessage().reply(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message))); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/context/NoMessageContext.java b/src/main/java/me/bobert/netneutrality/core/context/NoMessageContext.java new file mode 100644 index 0000000..1aeea14 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/context/NoMessageContext.java @@ -0,0 +1,47 @@ +package me.bobert.netneutrality.core.context; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.AsyncContext; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.modals.Modal; + +import java.util.function.Consumer; + +public interface NoMessageContext extends Context { + + @Override + default InteractionHook getInteractionHook() { + return null; + } + + @Override + default boolean isAcknowledged() { + return true; + } + + @Override + default ChannelType getType() { + return getChannel().getType(); + } + + @Override + default void reply(Modal modal) { + // only available for interactions + } + + @Override + default void defer(boolean ephemeral, Consumer consumer) { + reply("Bot is thinking...", consumer); + } + + @Override + default void ack(boolean ephemeral) { + + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java b/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java index 86a8044..185ea57 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java +++ b/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java @@ -11,15 +11,15 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public abstract class TaskContext extends AbstractContext { private final Map contextData; public TaskContext(JDA jda, Guild guild, MessageChannel channel, Member member, User user) { super(jda, guild, channel, member, user); - this.contextData = new HashMap<>(); + this.contextData = new ConcurrentHashMap<>(); } @Override diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractBuiltInTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractBuiltInTask.java index ae3e4eb..f334bf4 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractBuiltInTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractBuiltInTask.java @@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.tasks; */ import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.context.TaskContext; public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltInTask { @@ -19,11 +20,6 @@ public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltI return true; } - @Override - public boolean oneshot() { - return false; - } - @Override public boolean isExpired() { return false; @@ -33,4 +29,12 @@ public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltI public void replyToInteraction(CommandContext context) { // Task is loaded at runtime, this will never be called } + + @Override + public Lifecycle execute(TaskContext context) { + executeTask(context); + return Lifecycle.BUILTIN; + } + + public abstract void executeTask(TaskContext context); } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java index dc71c77..4b03f13 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java @@ -15,11 +15,6 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe return true; } - @Override - public boolean oneshot() { - return false; - } - @Override public boolean isExpired() { return false; @@ -30,6 +25,30 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe // Task loaded from database, this will never be called } + @Override + public void insert(CommandContext context) { + if (TaskManager.getInstance().hasTask(getId())) { + throw new RuntimeException("Cannot insert a task that already exists"); + } + + TaskManager.getInstance().insert(context, this); + } + + @Override + public void save() { + if (!TaskManager.getInstance().hasTask(getId())) { + throw new RuntimeException("Cannot save a task that doesn't exist yet"); + } + + TaskManager.getInstance().getTaskService().persistTask(this); + } + + @Override + public void delete() { + TaskManager.getInstance().getTaskService().unpersistTask(this); + TaskManager.getInstance().removeTask(this); + } + @Override public String toPayload() { JSONObject payload = new JSONObject(this); diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java index 56e1bfd..a6b3820 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java @@ -5,6 +5,8 @@ package me.bobert.netneutrality.core.tasks; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.context.CommandContext; + import java.time.OffsetDateTime; import java.util.Objects; @@ -26,8 +28,8 @@ public abstract class AbstractTask implements Task { } @Override - public TaskButtonCreator createButton() { - return new TaskButtonCreator(getId()); + public void insert(CommandContext context) { + TaskManager.getInstance().insert(context, this); } @Override @@ -46,6 +48,6 @@ public abstract class AbstractTask implements Task { @Override public String toString() { - return String.format("Task{id=%s, class=%s, name=%s, oneshot=%b, creationTime=%s}", getId(), getClass().getSimpleName(), getName(), oneshot(), getCreationTime()); + return String.format("Task{id=%s, lockId=%s, class=%s, name=%s, creationTime=%s}", getId(), getLockId(), getClass().getSimpleName(), getName(), getCreationTime()); } } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java index 2803bc7..a64fa2d 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java @@ -5,18 +5,21 @@ package me.bobert.netneutrality.core.tasks; * https://discord.gg/qUa6zNFZsd */ +import net.dv8tion.jda.api.interactions.components.buttons.Button; + /** * 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 clazz) { - BuiltInTask builtInTask = TaskManager.getInstance().getTaskMap().values().stream().filter(clazz::isInstance).map(task -> (BuiltInTask) task).findFirst().orElse(null); + static Button createButton(Class clazz) { + BuiltInTask builtInTask = TaskManager.getInstance().findBuiltInTaskByClass(clazz); + if (builtInTask == null) { throw new IllegalArgumentException("Task class " + clazz.getName() + " was not registered by the task manager."); } - return new TaskButtonCreator(builtInTask.getId()); + return builtInTask.createButton(); } } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/GenericCustomIdTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/GenericCustomIdTask.java new file mode 100644 index 0000000..8ab28a2 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/GenericCustomIdTask.java @@ -0,0 +1,27 @@ +package me.bobert.netneutrality.core.tasks; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +public abstract class GenericCustomIdTask extends AbstractTask { + private final String customId; + + public GenericCustomIdTask(String id) { + this.customId = id; + } + + @Override + public String getId() { + return customId; + } + + @Override + public Button createButton() { + return null; + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/GenericRandomIdTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/GenericRandomIdTask.java new file mode 100644 index 0000000..23cde6e --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/GenericRandomIdTask.java @@ -0,0 +1,15 @@ +package me.bobert.netneutrality.core.tasks; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import java.util.UUID; + +public abstract class GenericRandomIdTask extends GenericCustomIdTask { + public GenericRandomIdTask() { + super(UUID.randomUUID().toString()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java index 3d0179d..a5a1cc7 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java @@ -5,10 +5,19 @@ package me.bobert.netneutrality.core.tasks; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.context.CommandContext; + /** - * Represents a task that will be saved in the database and loaded when the bot is started + * Represents a task that can be persisted and restored later. + * The persistable task provides functionality to serialize its state + * so that it can be stored in a database. */ public interface PersistableTask extends Task { + void insert(CommandContext context); + + void save(); + + void delete(); String toPayload(); } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/Task.java b/src/main/java/me/bobert/netneutrality/core/tasks/Task.java index 15f6b08..bc2a084 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/Task.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/Task.java @@ -13,17 +13,35 @@ import net.dv8tion.jda.api.interactions.components.buttons.Button; import java.time.OffsetDateTime; public interface Task { + enum Lifecycle { + BUILTIN, + PARENT, + KEEP, + REMOVE_SELF, + REMOVE_SELF_AND_CHILDREN + } /** - *

This method should generate a unique ID to differentiate it from other tasks.

+ * This method should generate a unique ID to differentiate it from other tasks. *

* This determines what triggers a task to be executed. * For example, a modal or button would use the component id. * For an emoji reaction, the guild and message id would be combined. *

+ * * @return a unique ID for this task */ String getId(); + /** + * Retrieves the lock ID that is used to synchronize tasks + * that require a locking mechanism. + * + * @return the lock ID associated with this task + */ + default String getLockId() { + return getId(); + } + /** * This is only used for debugging purpose. * @return Name of the task @@ -36,9 +54,14 @@ public interface Task { boolean isInsertable(); /** - * This determines if the task should be cancelled after execution. + * When this returns true, the task will get removed by the cleanup thread. + * @return If the task is expired */ - boolean oneshot(); + boolean isExpired(); + + OffsetDateTime getCreationTime(); + + Button createButton(); /** *

This method is used to reply to the interaction that triggered this task.

@@ -48,41 +71,9 @@ public interface Task { /** * Executes the task. - * @param context The context of a task containing the event that triggererd it. + * @param context The context for this task. */ - void execute(TaskContext context); + Lifecycle execute(TaskContext context); - /** - * When this returns true, the task will get removed by the cleanup thread. - * @return If the task is expired - */ - 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); - } - } + void insert(CommandContext context); } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java b/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java index b56efd8..9de3c61 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java @@ -5,17 +5,18 @@ package me.bobert.netneutrality.core.tasks; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.context.tasks.ButtonTaskContext; -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.InteractionContext; import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.impl.StepTask; import me.bobert.netneutrality.core.tasks.trait.Deletable; import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable; import me.bobert.netneutrality.core.tasks.trait.WithChildren; import me.bobert.netneutrality.core.tasks.trait.WithParent; import me.bobert.netneutrality.core.thread.ThreadManager; import me.bobert.netneutrality.db.service.TaskService; +import me.bobert.netneutrality.exceptions.GracefulContextError; import me.bobert.netneutrality.exceptions.TaskExecutionException; import me.bobert.netneutrality.thread.CleanupTaskThread; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -26,19 +27,27 @@ import org.reflections.Reflections; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; public class TaskManager { private static final Logger logger = LogManager.getLogger(TaskManager.class); private static TaskManager instance; private final TaskService taskService; private final Map tasks; + private final List pendingTasks; private final Map deletableTasksContext; + private final Map tasksLock; private TaskManager() { - this.tasks = new HashMap<>(); - this.deletableTasksContext = new HashMap<>(); - this.taskService = new TaskService(this); + this.tasks = new ConcurrentHashMap<>(); + this.pendingTasks = new CopyOnWriteArrayList<>(); + this.deletableTasksContext = new ConcurrentHashMap<>(); + this.tasksLock = new ConcurrentHashMap<>(); + this.taskService = new TaskService(); } public static TaskManager getInstance() { @@ -56,7 +65,7 @@ public class TaskManager { ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new CleanupTaskThread(), 1, 5, TimeUnit.MINUTES); } - public TaskService getTaskService() { + protected TaskService getTaskService() { return taskService; } @@ -64,22 +73,22 @@ public class TaskManager { return tasks.get(id); } - public Collection getTasks() { + public boolean hasTask(String id) { + return tasks.containsKey(id) || pendingTasks.stream().anyMatch(task -> task.getId().equals(id)); + } + + public synchronized Collection getTasks() { return tasks.values(); } - public Map getTaskMap() { - return tasks; - } - - public boolean exists(String id) { - return tasks.containsKey(id); + public synchronized Collection getPendingTasks() { + return pendingTasks; } public void insert(CommandContext context, Task task) { String id = task.getId(); - if (tasks.containsKey(id)) { + if (hasTask(id)) { context.reply("A task with the same ID already exists!"); return; } @@ -93,7 +102,11 @@ public class TaskManager { taskService.persistTask(persistableTask); } - tasks.put(id, task); + if (task instanceof StepTask) { + pendingTasks.add(task); + } else { + tasks.put(id, task); + } } if (task instanceof Deletable) { @@ -102,6 +115,10 @@ public class TaskManager { if (task instanceof WithChildren taskWithChildren) { List childrens = taskWithChildren.getChildTasks(); + if (childrens == null || childrens.isEmpty()) { + logger.warn("Task [{}]: parent task doesn't create any childs", task.getId()); + return; + } for (Task child : childrens) { insert(context, child); @@ -109,51 +126,107 @@ public class TaskManager { } } - public void removeTask(Task task) { + public synchronized void activateTask(Task task) { + if (pendingTasks.remove(task)) { + tasks.put(task.getId(), task); + logger.info("Updating task {} with new id: {}", task.getName(), task.getId()); + } + } + + public synchronized void removeTask(Task task) { String id = task.getId(); if (tasks.containsKey(id)) { logger.debug("Removing task: {}", task); tasks.remove(id); } + + tasksLock.remove(task.getLockId()); } public void handle(TaskContext context) { String taskId = context.getId(); - Task task = tasks.get(taskId); + Task task = getTask(taskId); - if (task == null && (context instanceof ModalTaskContext || context instanceof ButtonTaskContext)) { + if (task == null && context instanceof InteractionContext) { context.replyEphemeral("This task is no longer available, someone might have already used it or it might have expired"); } if (task != null) { - logger.debug("Executing task: {}", task); + String lockId = task.getLockId(); + ReentrantLock lock = tasksLock.computeIfAbsent(lockId, k -> { + if (task instanceof PersistableTask || task instanceof BuiltInTask) { + return null; + } + + return new ReentrantLock(); + }); try { - // Cancel task during execution to prevent spam - removeTaskAndChildTasks(task); + if (lock != null) { + logger.debug("Aquiring lock for task: {}", taskId); + lock.lock(); + logger.debug("Aquired lock for: {}", lockId); - task.execute(context); - - if (task.isInsertable() && !task.oneshot()) { - reinsertTaskAndChildTasks(task); + if (!tasks.containsKey(taskId)) { + logger.debug("Task {} is no longer available after aquiring lock", taskId); + return; + } } + logger.debug("Executing task: {}", task); + Task.Lifecycle status = task.execute(context); + handleTaskLifecycle(task, status); + deleteInteraction(task); // If interaction is still not acknowledged after execution, send a placeholder response. if (!context.isAcknowledged() && !(task instanceof NotAcknowledgeable)) { + logger.warn("Interaction not acknowledged"); context.replyEphemeral("Task executed successfully but interaction wasn't acknowledged, report this bug by using the command /report-bug"); } - + } catch (GracefulContextError exception) { + logger.error(exception.getMessage()); + context.replyEphemeral(exception.getMessage()); } catch (TaskExecutionException exception) { LogHandler.log(exception); } catch (Exception exception) { LogHandler.log(new TaskExecutionException("Error during task execution", context, exception)); + } finally { + if (lock != null) { + logger.debug("Releasing lock for task: {}", taskId); + lock.unlock(); + } } } } + public List findTasksByPartialId(Class clazz, String partialId) { + return tasks.values().stream() + .filter(clazz::isInstance) + .map(clazz::cast) + .filter(task -> task.getId().contains(partialId)) + .collect(Collectors.toList()); + } + + public BuiltInTask findBuiltInTaskByClass(Class clazz) { + return tasks.values().stream() + .filter(clazz::isInstance) + .map(task -> (BuiltInTask) task) + .findFirst() + .orElse(null); + } + + public void insertPersistableTask(PersistableTask task) { + if (tasks.containsKey(task.getId())) { + logger.error("Trying to load persistable task but ID already exists: {}", task.getId()); + return; + } + + logger.info("Loaded persistable task: {}", task); + tasks.put(task.getId(), task); + } + private void loadBuiltInTasks() { logger.info("Loading built-in tasks..."); @@ -177,7 +250,7 @@ public class TaskManager { private void deleteInteraction(Task task) { if (task instanceof WithParent taskWithParent) { - Task parent = (Task) taskWithParent.getParentTask(); + Task parent = taskWithParent.getParentTask(); if (parent instanceof Deletable deletable && task.isInsertable()) { deleteInteraction(parent.getId(), deletable); @@ -212,37 +285,28 @@ public class TaskManager { deletable.onInteractionDelete(); } - private void removeTaskAndChildTasks(Task task) { - if (task instanceof BuiltInTask) { - return; + private void handleTaskLifecycle(Task task, Task.Lifecycle status) { + if (status == null) { + throw new IllegalArgumentException("Task lifecycle status cannot be null"); } - tasks.remove(task.getId()); + switch (status) { + case REMOVE_SELF: + removeTask(task); + break; - if (task instanceof WithParent taskWithParent && !taskWithParent.shouldKeepChilds()) { - List childrens = taskWithParent.getParentTask().getChildTasks(); - for (Task child : childrens) { - if (!task.equals(child)) { - tasks.remove(child.getId()); + case REMOVE_SELF_AND_CHILDREN: + if (task instanceof WithChildren taskWithChildren) { + for (Task child : taskWithChildren.getChildTasks()) { + removeTask(child); + } } - } - } - } + removeTask(task); + break; - private void reinsertTaskAndChildTasks(Task task) { - if (task instanceof BuiltInTask) { - return; - } - - tasks.put(task.getId(), task); - - if (task instanceof WithParent taskWithParent && taskWithParent.shouldKeepChilds()) { - List childrens = taskWithParent.getParentTask().getChildTasks(); - for (Task child : childrens) { - if (!task.equals(child)) { - tasks.put(child.getId(), child); - } - } + case KEEP: + default: + break; } } } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/impl/ButtonTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/impl/ButtonTask.java new file mode 100644 index 0000000..fc76810 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/impl/ButtonTask.java @@ -0,0 +1,36 @@ +package me.bobert.netneutrality.core.tasks.impl; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.tasks.GenericRandomIdTask; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; + +import java.time.OffsetDateTime; + +public abstract class ButtonTask extends GenericRandomIdTask { + private final ButtonStyle buttonStyle; + private final String buttonLabel; + + public ButtonTask(ButtonStyle buttonStyle, String buttonLabel) { + this.buttonStyle = buttonStyle; + this.buttonLabel = buttonLabel; + } + + @Override + public Button createButton() { + if (buttonStyle == null || buttonLabel == null) { + return null; + } + + return Button.of(buttonStyle, getId(), buttonLabel); + } + + @Override + public boolean isExpired() { + return getCreationTime().plusMinutes(15).isBefore(OffsetDateTime.now()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java new file mode 100644 index 0000000..26bc71a --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java @@ -0,0 +1,54 @@ +package me.bobert.netneutrality.core.tasks.impl; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.EmojiTaskContext; +import me.bobert.netneutrality.core.tasks.AbstractPersistableTask; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class EmojiTask extends AbstractPersistableTask { + private final Map emojiMapping = new ConcurrentHashMap<>(); + 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; + } + + public void addEmoji(String emoji, String snowflake) { + emojiMapping.put(emoji, snowflake); + } + + public void removeEmoji(String emoji) { + emojiMapping.remove(emoji); + } + + public Map getEmojiMapping() { + return emojiMapping; + } + + @Override + public String getId() { + return String.format(EmojiTaskContext.ID, getGuildId(), getMessageId()); + } + + @Override + public Button createButton() { + return null; + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/ModalTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/impl/ModalTask.java similarity index 67% rename from src/main/java/me/bobert/netneutrality/tasks/ModalTask.java rename to src/main/java/me/bobert/netneutrality/core/tasks/impl/ModalTask.java index b55fc10..94db7a9 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/ModalTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/impl/ModalTask.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.tasks; +package me.bobert.netneutrality.core.tasks.impl; /* * Copyright (c) anthorob * @@ -6,25 +6,13 @@ package me.bobert.netneutrality.tasks; */ import me.bobert.netneutrality.core.context.TaskContext; -import me.bobert.netneutrality.core.tasks.AbstractTask; +import me.bobert.netneutrality.core.tasks.GenericRandomIdTask; 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 { - private final UUID id = UUID.randomUUID(); - - @Override - public String getId() { - return id.toString(); - } - - @Override - public boolean oneshot() { - return true; - } +public abstract class ModalTask extends GenericRandomIdTask { @Override public boolean isExpired() { diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/impl/StepTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/impl/StepTask.java new file mode 100644 index 0000000..a17e558 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/impl/StepTask.java @@ -0,0 +1,110 @@ +package me.bobert.netneutrality.core.tasks.impl; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.context.MessageContext; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.AbstractTask; +import me.bobert.netneutrality.core.tasks.TaskManager; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public abstract class StepTask extends AbstractTask { + private static final Logger logger = LoggerFactory.getLogger(StepTask.class); + + public enum StepType { + STRING, ROLE, EMOJI + } + + private Guild guild; + private String id = UUID.randomUUID().toString(); + + @Override + public String getId() { + return id; + } + + @Override + public void replyToInteraction(CommandContext context) { + if (context.isFromGuild()) { + guild = context.getGuild(); + } + + if (guild == null && getStepType() == StepType.ROLE) { + throw new IllegalArgumentException("Step task if of type role but it was inserted in a non-guild context"); + } + + if (inDM()) { + context.replyEphemeral("Please follow the instructions sent in DM."); + context.sendMessageInPrivate(getStepDescription() + "\n" + getStepQuestion(), asyncContext -> assignId(asyncContext.getMessageId())); + } else { + context.reply(getStepQuestion(), asyncContext -> id = asyncContext.getMessageId()); + } + + System.out.println("step id : " + id); + } + + @Override + public Lifecycle execute(TaskContext context) { + if (context instanceof MessageContext messageContext) { + Message message = messageContext.getMessage(); + String answer = message.getContentRaw(); + + if (validateAnswer(answer)) { + StepTask nextStep = executeStep(answer); + + //TODO: to finish + } + } + + return Lifecycle.REMOVE_SELF; + } + + @Override + public boolean isExpired() { + return getCreationTime().plusMinutes(30).isBefore(OffsetDateTime.now()); + } + + @Override + public Button createButton() { + return null; + } + + private boolean validateAnswer(String answer) { + + } + + private void assignId(String newId) { + if (newId == null || newId.isBlank()) { + throw new IllegalArgumentException("Task ID cannot be null or empty."); + } + + synchronized (this) { + this.id = newId; + TaskManager.getInstance().activateTask(this); + } + + this.id = newId; + } + + public abstract boolean inDM(); + + public abstract StepType getStepType(); + + public abstract String getStepDescription(); + + public abstract String getStepQuestion(); + + public abstract StepTask executeStep(String answer); +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java index 7126c1e..768eb4a 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java @@ -6,64 +6,76 @@ package me.bobert.netneutrality.core.tasks.persistence; */ import me.bobert.netneutrality.core.tasks.PersistableTask; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.json.JSONArray; import org.json.JSONObject; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import java.util.Map; public class TaskFactory { - private static final Logger logger = LogManager.getLogger(TaskFactory.class); + public enum ParamType { + STRING(String.class), + INT(Integer.class), + MAP(Map.class); + + private final Class typeClass; + + ParamType(Class typeClass) { + this.typeClass = typeClass; + } + + public Class getTypeClass() { + return typeClass; + } + + public static ParamType fromString(String type) { + return switch (type.toLowerCase()) { + case "string" -> STRING; + case "int" -> INT; + case "map" -> MAP; + default -> throw new IllegalArgumentException("Unsupported type: " + type); + }; + } + } public static PersistableTask createTaskInstance(String className, JSONArray params, JSONArray types, JSONObject payload) throws Exception { if (params.length() != types.length()) { - throw new RuntimeException("Wrong number of parameters and types for " + className); + throw new RuntimeException("Wrong number of parameters and types."); } Class clazz = Class.forName(className); + if (!PersistableTask.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Class does not implement PersistableTask."); + } Class[] classTypes = new Class[types.length()]; Object[] classParams = new Object[params.length()]; for (int i = 0; i < params.length(); i++) { - String type = types.getString(i); - String param = params.getString(i); + ParamType paramType = ParamType.fromString(types.getString(i)); + String paramName = params.getString(i); - Class classType = parseClassTypeFromString(type); - Object classParam = castParamToType(classType, param, type, payload); - - classTypes[i] = classType; - classParams[i] = classParam; + classTypes[i] = paramType.getTypeClass(); + classParams[i] = extractValue(paramType, paramName, payload); } - return (PersistableTask) clazz.getConstructor(classTypes).newInstance(classParams); + try { + return (PersistableTask) clazz.getConstructor(classTypes).newInstance(classParams); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodException("No matching constructor found with parameters: " + Arrays.toString(classTypes)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid arguments for constructor. Expected: " + Arrays.toString(classParams), e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Constructor threw an exception", e.getCause()); + } } - private static Class parseClassTypeFromString(String param) { - return switch (param) { - case "string" -> String.class; - case "int" -> Integer.class; - case "map" -> Map.class; - default -> throw new IllegalArgumentException("Param " + param + " is not implemented yet."); + private static Object extractValue(ParamType type, String paramName, JSONObject payload) { + return switch (type) { + case STRING -> payload.getString(paramName); + case INT -> payload.getInt(paramName); + case MAP -> payload.getJSONObject(paramName).toMap(); }; } - - private static Object castParamToType(Class type, String paramName, String typeString, JSONObject payload) { - if (type == String.class) { - return payload.getString(paramName); - } - - if (type == Integer.class) { - return payload.getInt(paramName); - } - - if (type == Map.class) { - JSONObject mapJson = payload.getJSONObject(paramName); - - return mapJson.toMap(); - } - - throw new IllegalArgumentException("Type " + typeString + " is not implemented yet."); - } } \ No newline at end of file diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java index 1e0362f..e0dc7b3 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java @@ -26,43 +26,67 @@ public class TaskRegistry { private static final Logger logger = LogManager.getLogger(TaskRegistry.class); private static final Map taskCreators = new HashMap<>(); + private static final String FILE_NAME = "persistable-tasks.json"; - public TaskRegistry() { - loadTaskConfig(); - } + // JSON keys as constants + private static final String KEY_TASKS = "tasks"; + private static final String KEY_CLASS = "class"; + private static final String KEY_PARAMS = "params"; + private static final String KEY_TYPES = "types"; public static TaskCreator getTaskCreator(String className) { return taskCreators.get(className); } - private static void loadTaskConfig() { - String fileName = "persistable-tasks.json"; + public static void loadTaskConfig() { + logger.info("Reading task mappings from {}..", FILE_NAME); - try (InputStream is = TaskFactory.class.getClassLoader().getResourceAsStream(fileName)) { + try (InputStream is = TaskFactory.class.getClassLoader().getResourceAsStream(FILE_NAME)) { if (is == null) { - throw new FileNotFoundException("Could not load: " + fileName); + throw new FileNotFoundException("Could not load: " + FILE_NAME); } String jsonString = IOUtils.toString(is, StandardCharsets.UTF_8); JSONObject json = new JSONObject(jsonString); + JSONArray tasksArray = json.optJSONArray(KEY_TASKS); - json.getJSONArray("tasks").forEach(task -> { - JSONObject taskJson = (JSONObject) task; + if (tasksArray == null) { + logger.warn("No tasks found in JSON: {}", FILE_NAME); + return; + } - String className = taskJson.getString("class"); - JSONArray params = taskJson.getJSONArray("params"); - JSONArray types = taskJson.getJSONArray("types"); + for (int i = 0; i < tasksArray.length(); i++) { + JSONObject taskJson = tasksArray.getJSONObject(i); + + String className = taskJson.optString(KEY_CLASS, "").trim(); + if (className.isEmpty()) { + logger.warn("Missing JSON key '{}' at index {}", KEY_CLASS, i); + continue; + } + + JSONArray params = taskJson.optJSONArray(KEY_PARAMS); + if (params == null) { + logger.warn("Missing JSON key '{}' for class: {}", KEY_PARAMS, className); + continue; + } + + JSONArray types = taskJson.optJSONArray(KEY_TYPES); + if (types == null) { + logger.warn("Missing JSON key '{}' for class: {}", KEY_TYPES, className); + continue; + } taskCreators.put(className, payload -> { try { return TaskFactory.createTaskInstance(className, params, types, payload); - } catch (RuntimeException e) { - throw e; } catch (Exception e) { - throw new RuntimeException(e); + logger.error("Failed to create instance of '{}': {}", className, e.getMessage()); + return null; } }); - }); + } + + logger.info("{} mappings registered", taskCreators.size()); } catch (Exception e) { logger.error(e); } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java index bbd9715..661a8cd 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java @@ -9,5 +9,11 @@ package me.bobert.netneutrality.core.tasks.trait; * Represents a task that will remove its interaction from the channel after execution */ public interface Deletable { + + /** + * This method is called after the interaction associated with a deletable task + * has been deleted. Tasks that implement the {@link Deletable} interface can provide + * their own cleanup logic in this method. + */ void onInteractionDelete(); } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java index 786325b..e3c3b75 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java @@ -6,7 +6,7 @@ package me.bobert.netneutrality.core.tasks.trait; */ /** - * If a task implements this interface, it means that the interaction will not get acknowledged during execution. + * Represents a task that will not acknowledge the interaction when executed. */ public interface NotAcknowledgeable { } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java index 32a149b..22f5fec 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java @@ -5,17 +5,21 @@ package me.bobert.netneutrality.core.tasks.trait; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.tasks.Task; -import me.bobert.netneutrality.core.tasks.TaskManager; import java.util.List; -public interface WithChildren { +public interface WithChildren extends Task { + + @Override + default Lifecycle execute(TaskContext context) { + return Lifecycle.PARENT; + } + List getChildTasks(); - default void removeAllChildTasks() { - for (Task child : getChildTasks()) { - TaskManager.getInstance().removeTask(child); - } + default void addChild(Task... tasks) { + getChildTasks().addAll(List.of(tasks)); } } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java index 6fe6074..026dbaf 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java @@ -5,12 +5,30 @@ package me.bobert.netneutrality.core.tasks.trait; * https://discord.gg/qUa6zNFZsd */ -public interface WithParent { +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.tasks.Task; - E getParentTask(); +public interface WithParent extends Task { /** - * Whether execution of this task should remove all childs of its parent task during execution + * Retrieves the lock ID associated with the parent task. + * + * @return the lock ID of the parent task */ - boolean shouldKeepChilds(); + @Override + default String getLockId() { + return getParentTask().getLockId(); + } + + @Override + default void replyToInteraction(CommandContext context) { + // Child tasks shouldn't reply to the interaction, this is the job of the parent task + } + + /** + * Retrieves the parent task associated with this task. + * + * @return the parent task of type E + */ + E getParentTask(); } diff --git a/src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java b/src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java index 02b92f5..a08fb9e 100644 --- a/src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java +++ b/src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java @@ -22,11 +22,12 @@ public class ThreadManager { ); } + public static ExecutorService getExecutor(String name) { + return executors.computeIfAbsent(name, s -> Executors.newCachedThreadPool(new BaseThreadFactory(s))); + } + public static ExecutorService getExecutor() { - return executors.computeIfAbsent( - "general-executor", - s -> Executors.newCachedThreadPool(new BaseThreadFactory(s)) - ); + return getExecutor("general-executor"); } public static void shutdownExecutor() { diff --git a/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java b/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java index a0fde0d..b6bd763 100644 --- a/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java +++ b/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java @@ -5,14 +5,11 @@ package me.bobert.netneutrality.db.repository; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.Repository; import me.bobert.netneutrality.db.entities.OBlacklist; import me.bobert.netneutrality.db.entities.OGuild; import me.bobert.netneutrality.utils.JpaUtils; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import org.hibernate.Session; import org.hibernate.query.Query; @@ -53,15 +50,26 @@ public class BlacklistRepository implements Repository { }); } - public CommandExecutionResult add(Guild guild, Member member, String word) { - GuildManager guildManager = GuildManager.getManager(guild); + public List findByGuildId(String guildId) { + List blacklists = new ArrayList<>(); + JpaUtils.runWithSession(session -> { + Query query = session.createQuery("SELECT o FROM OBlacklist o WHERE o.guild.guildId = :guildId", OBlacklist.class); + query.setParameter("guildId", guildId); + + blacklists.addAll(query.list()); + }); + + return blacklists; + } + + public void add(OGuild oGuild, Member member, String word) { JpaUtils.runWithTransaction(session -> { - OBlacklist bl = getWordFromDb(word, guild.getId()); + OBlacklist bl = getWordFromDb(oGuild.getGuildId(), word); if (bl == null) { bl = new OBlacklist(); - bl.setGuild(guildManager.getGuildEntity()); + bl.setGuild(oGuild); bl.setCreatorId(member.getId()); bl.setWord(word); bl.setEnabled(true); @@ -72,37 +80,18 @@ public class BlacklistRepository implements Repository { session.merge(bl); } }); - - guildManager.reloadBlacklistCache(); - return CommandExecutionResult.success("Added the word [%s] to the blacklist.", word); } - public CommandExecutionResult delete(Guild guild, String word) { - GuildManager guildManager = GuildManager.getManager(guild); - + public void delete(OGuild oGuild, String word) { JpaUtils.runWithTransaction(session -> { - OBlacklist bl = getWordFromDb(word, guild.getId()); + OBlacklist bl = getWordFromDb(oGuild.getGuildId(), word); if (bl != null) { session.remove(bl); } - }); - - guildManager.reloadBlacklistCache(); - return CommandExecutionResult.success("Removed the word [%s] to the blacklist.", word); } - public List getBlacklistForGuild(Guild guild) { - GuildManager manager = GuildManager.getManager(guild); - OGuild guildEntity = manager.getGuildEntity(); - List blacklist = new ArrayList<>(); - - JpaUtils.getWithSession(guildEntity, guildEntity.getGuildId(), session -> blacklist.addAll(guildEntity.getBlacklists())); - - return blacklist; - } - - private OBlacklist getWordFromDb(String word, String guildId) { + private OBlacklist getWordFromDb(String guildId, String word) { try(Session session = SessionManager.getSessionFactory().openSession()) { Query query = session.createQuery("SELECT o FROM OBlacklist o WHERE upper(o.word) = upper(:word) AND o.guild.guildId = :guildId", OBlacklist.class); query.setParameter("guildId", guildId); diff --git a/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java b/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java index 4805464..2294701 100644 --- a/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java +++ b/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java @@ -5,9 +5,9 @@ package me.bobert.netneutrality.db.repository; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.Repository; +import me.bobert.netneutrality.db.entities.OGuild; import me.bobert.netneutrality.db.entities.OLogMessage; import me.bobert.netneutrality.utils.JpaUtils; import net.dv8tion.jda.api.entities.Member; @@ -56,10 +56,11 @@ public class MessageRepository implements Repository { }); } - public void log(GuildManager guildManager, MessageChannel channel, Member author, Message message) { + //TODO: move to a service + public void log(OGuild oGuild, MessageChannel channel, Member author, Message message) { JpaUtils.runWithTransaction(session -> { OLogMessage logMessage = new OLogMessage(); - logMessage.setGuild(guildManager.getGuildEntity()); + logMessage.setGuild(oGuild); logMessage.setMessageId(message.getId()); logMessage.setMessage(message.getContentRaw().trim()); logMessage.setChannelId(channel.getId()); diff --git a/src/main/java/me/bobert/netneutrality/db/service/BlacklistService.java b/src/main/java/me/bobert/netneutrality/db/service/BlacklistService.java new file mode 100644 index 0000000..2a07ac4 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/service/BlacklistService.java @@ -0,0 +1,98 @@ +package me.bobert.netneutrality.db.service; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.db.entities.OBlacklist; +import me.bobert.netneutrality.db.entities.OGuild; +import me.bobert.netneutrality.db.repository.BlacklistRepository; +import me.bobert.netneutrality.utils.JpaUtils; +import net.dv8tion.jda.api.entities.Member; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class BlacklistService { + private static final Logger logger = LoggerFactory.getLogger(BlacklistService.class); + private static final Map> blacklistCache = new ConcurrentHashMap<>(); + private final BlacklistRepository blacklistRepository; + private final GuildService guildService; + + public BlacklistService(GuildService guildService) { + this.blacklistRepository = new BlacklistRepository(); + this.guildService = guildService; + init(); + } + + private void init() { + reloadBlacklistCache(); + } + + public void reloadBlacklistCache() { + OGuild oGuild = guildService.getEntity(); + logger.debug("Reloading blacklist cache for guild: {}", oGuild.getGuildId()); + + JpaUtils.getWithSession(oGuild, oGuild.getGuildId(), session -> { + Set blacklistWords = oGuild.getBlacklists().stream() + .filter(OBlacklist::getEnabled) + .map(OBlacklist::getWord) + .collect(Collectors.toSet()); + + blacklistCache.put(oGuild.getGuildId(), blacklistWords); + }); + } + + public CommandExecutionResult add(Member member, String word) { + try { + blacklistRepository.add(guildService.getEntity(), member, word); + reloadBlacklistCache(); + + return CommandExecutionResult.success("Added the word [%s] to the blacklist.", word); + } catch (Exception e) { + return CommandExecutionResult.warn("Failed to add word to blacklist: " + e.getMessage()); + } + } + + public CommandExecutionResult delete(String word) { + try { + blacklistRepository.delete(guildService.getEntity(), word); + reloadBlacklistCache(); + + return CommandExecutionResult.success("Removed the word [%s] to the blacklist.", word); + } catch (Exception e) { + return CommandExecutionResult.warn("Failed to remove word from blacklist: " + e.getMessage()); + } + } + + public List getBlacklistForGuild() { + try { + return blacklistRepository.findByGuildId(guildService.getEntity().getGuildId()); + } catch (Exception e) { + return List.of(); + } + } + + public void removeGuildBlacklist() { + logger.info("Clearing blacklist cache for guild: {}", guildService.getEntity().getGuildId()); + blacklistCache.remove(guildService.getEntity().getGuildId()); + } + + public static boolean isWordBlacklisted(String guildId, String word) { + Set blacklistWords = blacklistCache.get(guildId); + return blacklistWords != null && blacklistWords.contains(word); + } + + public static Set getBlacklist(String guildId) { + return blacklistCache.getOrDefault(guildId, Collections.emptySet()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/db/service/GuildService.java b/src/main/java/me/bobert/netneutrality/db/service/GuildService.java new file mode 100644 index 0000000..cb60287 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/service/GuildService.java @@ -0,0 +1,85 @@ +package me.bobert.netneutrality.db.service; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.db.entities.OGuild; +import me.bobert.netneutrality.db.repository.GuildRepository; +import me.bobert.netneutrality.utils.JpaUtils; +import net.dv8tion.jda.api.entities.Guild; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class GuildService { + private static final Map services = new ConcurrentHashMap<>(); + private static final Logger logger = LoggerFactory.getLogger(GuildService.class); + private final GuildRepository guildRepository = new GuildRepository(); + private final BlacklistService blacklistService; + private final GuildSettingService guildSettingService; + private OGuild oGuild; + + private GuildService(String guildId) { + this.oGuild = findOrCreateGuild(guildId); + this.blacklistService = new BlacklistService(this); + this.guildSettingService = new GuildSettingService(this); + } + + public static GuildService get(String guildId) { + return services.computeIfAbsent(guildId, GuildService::new); + } + + public static GuildService get(Guild guild) { + return get(guild.getId()); + } + + public static Map getServices() { + return services; + } + + public OGuild getEntity() { + return oGuild; + } + + public BlacklistService getBlacklistService() { + return blacklistService; + } + + public GuildSettingService getGuildSettingService() { + return guildSettingService; + } + + public OGuild findOrCreateGuild(String guildId) { + OGuild guildEntity = guildRepository.findById(guildId); + if (guildEntity == null) { + logger.debug("Guild[{}] doesn't exist in DB, creating entity..", guildId); + + guildEntity = new OGuild(); + guildEntity.setGuildId(guildId); + guildRepository.save(guildEntity); + } + + return guildEntity; + } + + public boolean isDebugEnabled() { + return oGuild.getDebug(); + } + + public void updateDebugMode(boolean debug) { + JpaUtils.updateWithTransaction(oGuild, () -> oGuild.setDebug(debug)); + } + + public void deleteGuild() { + guildRepository.deleteBy(oGuild.getGuildId()); + } + + public void reloadEntity() { + this.oGuild = findOrCreateGuild(oGuild.getGuildId()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java b/src/main/java/me/bobert/netneutrality/db/service/GuildSettingService.java similarity index 60% rename from src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java rename to src/main/java/me/bobert/netneutrality/db/service/GuildSettingService.java index 67a4730..e71683f 100644 --- a/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java +++ b/src/main/java/me/bobert/netneutrality/db/service/GuildSettingService.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.core; +package me.bobert.netneutrality.db.service; /* * Copyright (c) anthorob @@ -7,30 +7,23 @@ package me.bobert.netneutrality.core; */ import me.bobert.netneutrality.db.GSettings; -import me.bobert.netneutrality.db.entities.OGuild; import me.bobert.netneutrality.db.entities.OGuildSetting; import me.bobert.netneutrality.db.repository.GuildSettingRepository; -import net.dv8tion.jda.api.entities.Guild; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; -public class GuildConfigManager { - - private static final Logger logger = LogManager.getLogger(GuildConfigManager.class); - private final Guild guild; - private final OGuild guildEntity; +public class GuildSettingService { + private static final Logger logger = LoggerFactory.getLogger(GuildSettingService.class); private final GuildSettingRepository guildSettingRepository = new GuildSettingRepository(); + private final GuildService guildService; + private final Map settingsCache = new ConcurrentHashMap<>(); - private final Map settingsCache = new HashMap<>(); - - public GuildConfigManager(Guild guild, OGuild guildEntity) { - this.guild = guild; - this.guildEntity = guildEntity; - + public GuildSettingService(GuildService guildService) { + this.guildService = guildService; reloadSettingsCache(); } @@ -49,19 +42,19 @@ public class GuildConfigManager { } public void setSettingValue(GSettings setting, String value) { - guildSettingRepository.setSettingValue(guildEntity, setting, value); + guildSettingRepository.setSettingValue(guildService.getEntity(), setting, value); reloadSettingsCache(); } public void reloadSettingsCache() { - logger.debug("Reloading settings cache for guild: {}", guild.getId()); + logger.debug("Reloading settings cache for guild: {}", guildService.getEntity().getGuildId()); settingsCache.clear(); - List settings = guildSettingRepository.findByGuild(guild.getId()); + + List settings = guildSettingRepository.findByGuild(guildService.getEntity().getGuildId()); for (OGuildSetting setting : settings) { settingsCache.put(setting.getName(), setting.getValue()); } } - } diff --git a/src/main/java/me/bobert/netneutrality/db/service/TaskService.java b/src/main/java/me/bobert/netneutrality/db/service/TaskService.java index 68a6815..7e490f9 100644 --- a/src/main/java/me/bobert/netneutrality/db/service/TaskService.java +++ b/src/main/java/me/bobert/netneutrality/db/service/TaskService.java @@ -15,23 +15,27 @@ import org.apache.logging.log4j.Logger; import org.json.JSONObject; import java.util.List; +import java.util.Objects; public class TaskService { private static final Logger logger = LogManager.getLogger(TaskService.class); - private final TaskManager taskManager; private final TaskRepository taskRepository; - public TaskService(TaskManager taskManager) { - this.taskManager = taskManager; + public TaskService() { this.taskRepository = new TaskRepository(); } public void loadTasks() { + TaskRegistry.loadTaskConfig(); logger.info("Loading persistable tasks..."); + List tasks = taskRepository.findAll(); for (OTask oTask : tasks) { + String taskId = oTask.getTaskId(); String taskClass = oTask.getTaskClass(); + logger.debug("Trying to load task with class: {}", taskClass); + String payload = oTask.getPayload(); try { @@ -42,20 +46,38 @@ 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()); + if (task == null) { continue; } - logger.info("Loaded task: {}", task); - TaskManager.getInstance().getTaskMap().put(task.getId(), task); + validateTask(task, oTask); + TaskManager.getInstance().insertPersistableTask(task); } catch (Exception e) { - logger.error("Error loading task: {}", payload, e); + logger.error("Error loading task with ID {}: {}", taskId, e.getMessage()); } } } + private void validateTask(PersistableTask task, OTask oTask) throws IllegalArgumentException { + String taskId = oTask.getTaskId(); + String payload = oTask.getPayload(); + + if (!Objects.equals(taskId, task.getId())) { + throw new IllegalArgumentException("Task ID mismatch, expected: " + task.getId()); + } + + JSONObject json = new JSONObject(payload); + String payloadId = json.getString("id"); + if (!Objects.equals(payloadId, task.getId())) { + throw new IllegalArgumentException("Task payload ID mismatch, expected: " + task.getId()); + } + + String payloadLockId = json.getString("lockId"); + if (!Objects.equals(payloadLockId, task.getLockId())) { + throw new IllegalArgumentException("Task lock ID mismatch, expected: " + task.getLockId()); + } + } + public void persistTask(PersistableTask task) { String taskClass = task.getClass().getName(); if (TaskRegistry.getTaskCreator(taskClass) == null) { @@ -67,10 +89,10 @@ public class TaskService { OTask oTask = taskRepository.findById(task.getId()); if (oTask == null) { oTask = new OTask(); + oTask.setTaskId(task.getId()); + oTask.setTaskClass(task.getClass().getName()); } - oTask.setTaskId(task.getId()); - oTask.setTaskClass(task.getClass().getName()); oTask.setPayload(task.toPayload()); taskRepository.save(oTask); @@ -81,6 +103,5 @@ public class TaskService { TaskRepository taskRepository = new TaskRepository(); taskRepository.deleteBy(task.getId()); - taskManager.getTaskMap().remove(task.getId()); } } diff --git a/src/main/java/me/bobert/netneutrality/exceptions/GracefulContextError.java b/src/main/java/me/bobert/netneutrality/exceptions/GracefulContextError.java new file mode 100644 index 0000000..0434f72 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/exceptions/GracefulContextError.java @@ -0,0 +1,19 @@ +package me.bobert.netneutrality.exceptions; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.Context; + +/** + * When thrown, this will call {@link Context#replyEphemeral(String)} with the specified message + */ +public class GracefulContextError extends RuntimeException { + + public GracefulContextError(String message) { + super(message); + } +} diff --git a/src/main/java/me/bobert/netneutrality/hooks/EventsHandler.java b/src/main/java/me/bobert/netneutrality/hooks/EventsHandler.java index 97fca8a..f06db8e 100644 --- a/src/main/java/me/bobert/netneutrality/hooks/EventsHandler.java +++ b/src/main/java/me/bobert/netneutrality/hooks/EventsHandler.java @@ -7,22 +7,30 @@ package me.bobert.netneutrality.hooks; */ import me.bobert.netneutrality.context.command.MessageCommandContext; +import me.bobert.netneutrality.context.command.MessageMenuContext; import me.bobert.netneutrality.context.command.SlashCommandContext; +import me.bobert.netneutrality.context.command.UserMenuContext; import me.bobert.netneutrality.context.tasks.ButtonTaskContext; import me.bobert.netneutrality.context.tasks.EmojiTaskContext; +import me.bobert.netneutrality.context.tasks.MessageTaskContext; import me.bobert.netneutrality.context.tasks.ModalTaskContext; -import me.bobert.netneutrality.core.GuildManager; +import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.core.command.CommandProcessor; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.context.Context; import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.db.service.GuildService; import me.bobert.netneutrality.utils.Settings; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.Event; import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.guild.GuildJoinEvent; +import net.dv8tion.jda.api.events.guild.GuildLeaveEvent; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; @@ -45,21 +53,30 @@ public class EventsHandler implements EventListener { if (event.getAuthor().isBot()) return null; if (event.isWebhookMessage()) return null; - String rawMessage = event.getMessage().getContentDisplay().trim(); + Message message = event.getMessage(); + String rawMessage = message.getContentDisplay().trim(); String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$"); - if (rawMessage.startsWith(prefix)) { return new MessageCommandContext(event); } - return null; + return new MessageTaskContext(event); }); + addListener(UserContextInteractionEvent.class, UserMenuContext::new); + addListener(MessageContextInteractionEvent.class, MessageMenuContext::new); addListener(SlashCommandInteractionEvent.class, SlashCommandContext::new); addListener(ModalInteractionEvent.class, ModalTaskContext::new); addListener(ButtonInteractionEvent.class, ButtonTaskContext::new); addListener(MessageReactionAddEvent.class, EmojiTaskContext::new); - addGenericListener(GuildJoinEvent.class, event -> GuildManager.getManager(event.getGuild()).removeFromDB()); + addGenericListener(GuildJoinEvent.class, event -> { + GuildService service = GuildService.get(event.getGuild()); + + if (service.isDebugEnabled()) { + LogHandler.sendMessageToDebugChannel(event.getGuild(), "Error logging enabled on this channel"); + } + }); + addGenericListener(GuildLeaveEvent.class, event -> GuildService.get(event.getGuild()).deleteGuild()); } /** @@ -104,11 +121,6 @@ public class EventsHandler implements EventListener { Function function = (Function) listeners.get(eventClass); Context context = function.apply(event); - if (context == null) { - logger.debug("Handling event {} by generic listener", event); - return; - } - if (context instanceof CommandContext commandContext) { handleCommand(commandContext); return; diff --git a/src/main/java/me/bobert/netneutrality/http/WebModule.java b/src/main/java/me/bobert/netneutrality/http/WebModule.java index 7fbe042..907d836 100644 --- a/src/main/java/me/bobert/netneutrality/http/WebModule.java +++ b/src/main/java/me/bobert/netneutrality/http/WebModule.java @@ -9,8 +9,7 @@ package me.bobert.netneutrality.http; import com.google.gson.Gson; import io.sentry.Breadcrumb; import io.sentry.Sentry; -import me.bobert.netneutrality.NetNeutrality; -import me.bobert.netneutrality.core.LogHandler; +import me.bobert.netneutrality.utils.Settings; import okhttp3.*; import org.json.JSONArray; import org.json.JSONObject; @@ -18,15 +17,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; public class WebModule { private enum Method { - GET, POST + PING, GET, POST } private static final String USER_AGENT = "Net Neutrality/%s (Discord bot, discord.gg/trAqhQ5Wsr)"; @@ -45,7 +41,6 @@ public class WebModule { private final long cacheExpirationMillis; private final OkHttpClient client; private final Map headers; - private final Map params; private final Request request; private CachedResponse cachedResponse; @@ -54,9 +49,8 @@ public class WebModule { this.url = builder.url; this.client = builder.client; this.headers = builder.headers; - this.params = builder.params; this.cacheExpirationMillis = builder.timeUnit.toMillis(builder.timeout); - this.request = createRequest(); + this.request = builder.request; } /** @@ -70,19 +64,6 @@ public class WebModule { cache.keySet().removeIf(s -> s.contains(url)); } - private Request createRequest() { - Request.Builder builder = new Request.Builder().url(url); - builder.addHeader("User-Agent", String.format(USER_AGENT, NetNeutrality.VERSION)); - headers.forEach(builder::addHeader); - - if (method == Method.POST && !params.isEmpty()) { - RequestBody body = RequestBody.create(new Gson().toJson(params), MediaType.parse("application/json; charset=utf-8")); - builder.post(body); - } - - return builder.build(); - } - /** * Sends the HTTP request based on the configuration and retrieves the response. *

@@ -107,24 +88,37 @@ public class WebModule { } } + long startTime = System.currentTimeMillis(); try (Response response = client.newCall(request).execute()) { + long elapsedTime = System.currentTimeMillis() - startTime; Sentry.addBreadcrumb(Breadcrumb.http(url, method.name(), response.code())); String body = response.body() != null ? response.body().string() : null; this.cachedResponse = new CachedResponse( response.code(), response.headers(), + elapsedTime, body, - System.currentTimeMillis() + cacheExpirationMillis + System.currentTimeMillis() + cacheExpirationMillis, + null ); - - if (method == Method.GET && cacheKey != null) { - log.debug("Caching request: {}", cacheKey); - cache.put(cacheKey, this.cachedResponse); - } } catch (IOException e) { + long elapsedTime = System.currentTimeMillis() - startTime; + log.error("Request failed: {}", e.getMessage(), e); Sentry.captureException(e); - throw new RuntimeException("Failed to execute request", e); + + this.cachedResponse = new CachedResponse( + 0, // Status code 0 indicates a failure + new Headers.Builder().build(), + elapsedTime, + null, + System.currentTimeMillis() + cacheExpirationMillis, + e.getMessage() + ); + } + + if (method == Method.GET && cacheKey != null) { + cache.put(cacheKey, this.cachedResponse); } return this; @@ -145,28 +139,27 @@ public class WebModule { } public JSONObject getAsObject() { - try { - return new JSONObject(cachedResponse.getBody()); - } catch (NullPointerException e) { - LogHandler.log(e); - return new JSONObject(); - } + return cachedResponse.getBody() + .map(JSONObject::new) + .orElseThrow(() -> new IllegalStateException("No body available for this response")); } public JSONArray getAsArray() { - try { - return new JSONArray(cachedResponse.getBody()); - } catch (NullPointerException e) { - LogHandler.log(e); - return new JSONArray(); - } + return cachedResponse.getBody() + .map(JSONArray::new) + .orElseThrow(() -> new IllegalStateException("No body available for this response")); } public String getRawData() { - return cachedResponse.getBody(); + return cachedResponse.getBody().orElse(null); + } + + public boolean isSuccess() { + return cachedResponse != null && cachedResponse.getStatusCode() >= 200 && cachedResponse.getStatusCode() < 400; } public static class Builder { + private Request request; private final String url; private Method method = Method.GET; private long timeout = 5; @@ -187,6 +180,11 @@ public class WebModule { this.url = url; } + public Builder ping() { + this.method = Method.PING; + return this; + } + /** * Configures the request to use the HTTP GET method. * @@ -224,7 +222,7 @@ public class WebModule { /** * Configures caching for the request. * - * @param timeout the cache expiration duration + * @param timeout the cache expiration duration * @param timeUnit the unit of time for the cache expiration * @return the current {@code Builder} instance for chaining */ @@ -234,10 +232,17 @@ public class WebModule { return this; } + public Builder timeout(long timeout, TimeUnit timeUnit) { + this.connectTimeout = timeUnit.toMillis(timeout); + this.readTimeout = timeUnit.toMillis(timeout); + this.writeTimeout = timeUnit.toMillis(timeout); + return this; + } + /** * Sets the connection timeout for the HTTP client. * - * @param timeout the timeout duration + * @param timeout the timeout duration * @param timeUnit the unit of time for the timeout * @return the current {@code Builder} instance for chaining */ @@ -249,7 +254,7 @@ public class WebModule { /** * Sets the read timeout for the HTTP client. * - * @param timeout the timeout duration + * @param timeout the timeout duration * @param timeUnit the unit of time for the timeout * @return the current {@code Builder} instance for chaining */ @@ -261,7 +266,7 @@ public class WebModule { /** * Sets the write timeout for the HTTP client. * - * @param timeout the timeout duration + * @param timeout the timeout duration * @param timeUnit the unit of time for the timeout * @return the current {@code Builder} instance for chaining */ @@ -270,6 +275,27 @@ public class WebModule { return this; } + private Request createRequest() { + Request.Builder builder = new Request.Builder().url(url); + builder.addHeader("User-Agent", String.format(USER_AGENT, Settings.getVersion())); + headers.forEach(builder::addHeader); + + if (method == Method.PING) { + builder.head(); + } + + if (method == Method.GET) { + builder.get(); + } + + if (method == Method.POST && !params.isEmpty()) { + RequestBody body = RequestBody.create(new Gson().toJson(params), MediaType.parse("application/json; charset=utf-8")); + builder.post(body); + } + + return builder.build(); + } + /** * Builds and returns a configured {@link WebModule} instance. * @@ -281,6 +307,7 @@ public class WebModule { .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) .readTimeout(readTimeout, TimeUnit.MILLISECONDS) .build(); + this.request = createRequest(); return new WebModule(this); } @@ -289,14 +316,18 @@ public class WebModule { private static class CachedResponse { private final int statusCode; private final Headers headers; + private final long elapsedTime; private final String body; private final long expirationTime; + private final String errorMessage; - public CachedResponse(int statusCode, Headers headers, String body, long expirationTime) { + public CachedResponse(int statusCode, Headers headers, long elapsedTime, String body, long expirationTime, String errorMessage) { this.statusCode = statusCode; this.headers = headers; + this.elapsedTime = elapsedTime; this.body = body; this.expirationTime = expirationTime; + this.errorMessage = errorMessage; } public boolean isExpired() { @@ -311,8 +342,16 @@ public class WebModule { return headers; } - public String getBody() { - return body; + public long getElapsedTime() { + return elapsedTime; + } + + public Optional getBody() { + return Optional.ofNullable(body); + } + + public Optional getErrorMessage() { + return Optional.ofNullable(errorMessage); } } } diff --git a/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java b/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java index ce5b38a..7637ed7 100644 --- a/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java +++ b/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java @@ -9,7 +9,7 @@ package me.bobert.netneutrality.http.server; import com.google.gson.Gson; import com.sun.net.httpserver.HttpExchange; import me.bobert.netneutrality.NetNeutrality; -import me.bobert.netneutrality.core.GuildManager; +import me.bobert.netneutrality.db.service.GuildService; import net.dv8tion.jda.api.entities.Guild; import org.apache.logging.log4j.LogManager; import org.json.JSONObject; @@ -49,8 +49,8 @@ public class ActionHandler extends AbstractHandler { Guild guild = NetNeutrality.getInstance().getJDA().getGuildById(guildId); if (guild != null) { - GuildManager manager = GuildManager.getManager(guild); - Map settings = manager.getGuildConfigManager().getSettings(); + GuildService service = GuildService.get(guild); + Map settings = service.getGuildSettingService().getSettings(); JSONObject setting = new JSONObject(settings); json.put("status", "ok"); @@ -68,13 +68,8 @@ public class ActionHandler extends AbstractHandler { String[] args = exchange.getRequestURI().toString().split("/"); String guildId = args[args.length - 1]; - Guild guild = NetNeutrality.getInstance().getJDA().getGuildById(guildId); boolean ok = true; //TODO: add bool - if (guild == null) - GuildManager.getManager(guildId).getGuildConfigManager().reloadSettingsCache(); - else - GuildManager.getManager(guild).getGuildConfigManager().reloadSettingsCache(); - + GuildService.get(guildId).getGuildSettingService().reloadSettingsCache(); json.put("status", String.valueOf(ok)); sendJsonResponse(exchange, new Gson().toJson(json)); diff --git a/src/main/java/me/bobert/netneutrality/music/MusicManager.java b/src/main/java/me/bobert/netneutrality/music/MusicManager.java index 4f0d165..3a44a69 100644 --- a/src/main/java/me/bobert/netneutrality/music/MusicManager.java +++ b/src/main/java/me/bobert/netneutrality/music/MusicManager.java @@ -13,11 +13,11 @@ import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import dev.lavalink.youtube.YoutubeAudioSourceManager; -import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.thread.ThreadManager; import me.bobert.netneutrality.db.GSettings; +import me.bobert.netneutrality.db.service.GuildService; import me.bobert.netneutrality.music.source.UdioAudioSourceManager; import me.bobert.netneutrality.thread.MusicManagerCleanupThread; import me.bobert.netneutrality.thread.MusicPlayerAutoDisconnectThread; @@ -47,7 +47,7 @@ public class MusicManager { this.guild = guild; this.player = playerManager.createPlayer(); this.scheduler = new TrackScheduler(this); - this.baseVolume = Integer.parseInt(GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_VOLUME)); + this.baseVolume = Integer.parseInt(GuildService.get(guild).getGuildSettingService().getSetting(GSettings.MUSIC_VOLUME)); guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player)); player.addListener(scheduler); @@ -104,8 +104,9 @@ public class MusicManager { return guild; } - public boolean isNotConnectedTo(AudioChannel vChannel) { - return vChannel == null || !vChannel.equals(guild.getAudioManager().getConnectedChannel()); + public synchronized boolean isNotConnectedTo(AudioChannel vChannel) { + AudioChannel current = guild.getAudioManager().getConnectedChannel(); + return vChannel == null || current == null || !vChannel.getId().equals(current.getId()); } public synchronized void connect(AudioChannel audioChannel) { @@ -120,7 +121,7 @@ public class MusicManager { } public synchronized void disconnect() { - if (guild != null && guild.getAudioManager().getConnectedChannel() != null) { + if (guild.getAudioManager().getConnectedChannel() != null) { logger.debug("Guild {}: disconnecting from voice channel {}", guild.getId(), guild.getAudioManager().getConnectedChannel().getId()); player.stopTrack(); guild.getAudioManager().closeAudioConnection(); @@ -129,8 +130,8 @@ public class MusicManager { scheduler.detachContext(); } - public boolean isConnected() { - return guild != null && guild.getAudioManager().getConnectedChannel() != null; + public synchronized boolean isConnected() { + return guild.getAudioManager().getConnectedChannel() != null; } public synchronized AudioTrack getCurrentlyPlaying() { diff --git a/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java b/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java index 3bb61b0..cba619c 100644 --- a/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java +++ b/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java @@ -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, type)); + new LoadPlaylistTask(playlist, type).insert(context); } @Override diff --git a/src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java b/src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java deleted file mode 100644 index 03bf819..0000000 --- a/src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.bobert.netneutrality.tasks; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -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 boolean oneshot() { - return true; - } - - @Override - public boolean isExpired() { - return getCreationTime().plusMinutes(15).isBefore(OffsetDateTime.now()); - } -} diff --git a/src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java b/src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java deleted file mode 100644 index 75a1487..0000000 --- a/src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.bobert.netneutrality.tasks; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -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()); - } -} diff --git a/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java b/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java index 2755c4b..3591730 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java +++ b/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java @@ -6,35 +6,21 @@ package me.bobert.netneutrality.tasks.emoji; */ import me.bobert.netneutrality.core.context.TaskContext; -import me.bobert.netneutrality.tasks.EmojiTask; +import me.bobert.netneutrality.core.tasks.impl.EmojiTask; +import me.bobert.netneutrality.utils.GuildUtils; import net.dv8tion.jda.api.entities.Role; -import java.util.HashMap; import java.util.Map; public class EmojiReactionRoleTask extends EmojiTask { - private final Map emojiMapping; - - public EmojiReactionRoleTask(String guildId, String messageId) { + public EmojiReactionRoleTask(String guildId, String channelId, String messageId) { super(guildId, messageId); - this.emojiMapping = new HashMap<>(); } + // constructor for auto loading public EmojiReactionRoleTask(String guildId, String messageId, Map emojiMapping) { super(guildId, messageId); - this.emojiMapping = emojiMapping; - } - - public void addEmoji(String emoji, String role) { - emojiMapping.put(emoji, role); - } - - public void removeEmoji(String emoji) { - emojiMapping.remove(emoji); - } - - public Map getEmojiMapping() { - return emojiMapping; + getEmojiMapping().putAll(emojiMapping); } @Override @@ -43,15 +29,17 @@ public class EmojiReactionRoleTask extends EmojiTask { } @Override - public void execute(TaskContext context) { + public Lifecycle 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); + GuildUtils.addRolesToMember(context, role); } } + + return Lifecycle.KEEP; } } diff --git a/src/main/java/me/bobert/netneutrality/tasks/generic/AcceptButton.java b/src/main/java/me/bobert/netneutrality/tasks/generic/AcceptButton.java new file mode 100644 index 0000000..2785970 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/generic/AcceptButton.java @@ -0,0 +1,53 @@ +package me.bobert.netneutrality.tasks.generic; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.trait.WithChildren; +import me.bobert.netneutrality.core.tasks.trait.WithParent; +import me.bobert.netneutrality.core.tasks.impl.ButtonTask; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; + +import java.util.function.Function; + +/** + * A generic button task that represents a button to accept a choice. + * @param

the type of the parent task, which must implement WithChildren + */ +public class AcceptButton

extends ButtonTask implements WithParent

{ + private final P parentTask; + private final Function function; + + protected AcceptButton(P parentTask, ButtonStyle buttonStyle, String buttonLabel, Function function) { + super(buttonStyle, buttonLabel); + this.parentTask = parentTask; + this.function = function; + } + + public static

AcceptButton

createPrimaryButton(P parentTask, String buttonLabel, Function function) { + return new AcceptButton<>(parentTask, ButtonStyle.PRIMARY, buttonLabel, function); + } + + public static

AcceptButton

createSuccessButton(P parentTask, String buttonLabel, Function function) { + return new AcceptButton<>(parentTask, ButtonStyle.SUCCESS, buttonLabel, function); + } + + @Override + public P getParentTask() { + return parentTask; + } + + @Override + public String getName() { + return "accept-" + parentTask.getName(); + } + + @Override + public Lifecycle execute(TaskContext context) { + return function.apply(context); + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/generic/CancelButton.java b/src/main/java/me/bobert/netneutrality/tasks/generic/CancelButton.java new file mode 100644 index 0000000..3747ce8 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/generic/CancelButton.java @@ -0,0 +1,46 @@ +package me.bobert.netneutrality.tasks.generic; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable; +import me.bobert.netneutrality.core.tasks.trait.WithChildren; +import me.bobert.netneutrality.core.tasks.trait.WithParent; +import me.bobert.netneutrality.core.tasks.impl.ButtonTask; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; + +import java.util.function.Function; + +public class CancelButton

extends ButtonTask implements WithParent

, NotAcknowledgeable { + private final P parentTask; + private final Function function; + + protected CancelButton(P parentTask, ButtonStyle buttonStyle, String buttonLabel, Function function) { + super(buttonStyle, buttonLabel); + this.parentTask = parentTask; + this.function = function; + } + + public static

CancelButton

create(P parentTask, String buttonLabel, Function function) { + return new CancelButton<>(parentTask, ButtonStyle.DANGER, buttonLabel, function); + } + + @Override + public P getParentTask() { + return parentTask; + } + + @Override + public String getName() { + return "cancel-" + parentTask.getName(); + } + + @Override + public Lifecycle execute(TaskContext context) { + return function.apply(context); + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java b/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java index 87d6080..8e9c0ad 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java +++ b/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java @@ -6,10 +6,11 @@ package me.bobert.netneutrality.tasks.git; */ import io.sentry.Sentry; +import me.bobert.netneutrality.context.command.SlashCommandContext; 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.core.tasks.impl.ModalTask; import me.bobert.netneutrality.utils.Settings; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.interactions.components.ActionRow; @@ -27,8 +28,8 @@ public class ReportIssueTask extends ModalTask { private static final Logger logger = LogManager.getLogger(ReportIssueTask.class); private final boolean insertable; - public ReportIssueTask(boolean insertable) { - this.insertable = insertable; + public ReportIssueTask(CommandContext context) { + this.insertable = context instanceof SlashCommandContext; } @Override @@ -63,7 +64,7 @@ public class ReportIssueTask extends ModalTask { } @Override - public void execute(TaskContext context) { + public Lifecycle execute(TaskContext context) { context.ephemeral(); String gitlabHost = Settings.getInstance().get("GITLAB_HOST"); @@ -73,7 +74,7 @@ public class ReportIssueTask extends ModalTask { if (gitlabHost.isBlank() || gitlabToken.isBlank() || projectId.isBlank()) { logger.error("Gitlab is not configured in the bot settings, can't execute task"); context.reply("Command is not configured, failed to create issue."); - return; + return null; } ModalMapping modalTitle = getValue("title", context); @@ -103,5 +104,7 @@ public class ReportIssueTask extends ModalTask { context.reply("Failed to create issue."); LogHandler.log(e); } + + return Lifecycle.REMOVE_SELF; } } diff --git a/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java b/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java index d9c8238..014b908 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java +++ b/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.tasks.help; import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask; +import net.dv8tion.jda.api.interactions.components.buttons.Button; public class BotInfo extends AbstractBuiltInTask { @@ -16,7 +17,12 @@ public class BotInfo extends AbstractBuiltInTask { } @Override - public void execute(TaskContext context) { + public Button createButton() { + return Button.primary(getId(), "Info"); + } + + @Override + public void executeTask(TaskContext context) { context.sendMessageInPrivate("For information about the bot and to follow the development, " + "you can join the discord here: https://discord.gg/udVJwTgV"); } diff --git a/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java b/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java index 3206ecc..37eeb5b 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java +++ b/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java @@ -9,6 +9,7 @@ import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.Settings; +import net.dv8tion.jda.api.interactions.components.buttons.Button; public class CommandHelp extends AbstractBuiltInTask { @@ -18,7 +19,12 @@ public class CommandHelp extends AbstractBuiltInTask { } @Override - public void execute(TaskContext context) { + public Button createButton() { + return Button.primary(getId(), "Commands"); + } + + @Override + public void executeTask(TaskContext context) { context.replyEphemeral("The prefix for the bot is " + Settings.getInstance().getPrefix()); context.replyEphemeral(MessageFunction.buildHelpMessage()); } diff --git a/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java b/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java index 3c643c7..7555581 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java +++ b/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.tasks.help; import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask; +import net.dv8tion.jda.api.interactions.components.buttons.Button; public class ReportBug extends AbstractBuiltInTask { @@ -16,7 +17,12 @@ public class ReportBug extends AbstractBuiltInTask { } @Override - public void execute(TaskContext context) { + public Button createButton() { + return Button.danger(getId(), "Report bug"); + } + + @Override + public void executeTask(TaskContext context) { context.sendMessageInPrivate("To report a bug with the bot, you can either use the /report-bug command " + "or join the discord here: https://discord.gg/udVJwTgV"); } diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java deleted file mode 100644 index 91114da..0000000 --- a/src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java +++ /dev/null @@ -1,61 +0,0 @@ -package me.bobert.netneutrality.tasks.music; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -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.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 { - private final LoadPlaylistTask parentTask; - List tracks; - - public AddAllTracksTask(LoadPlaylistTask parentTask, List 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(TaskContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - Member member = context.getGuildUser(); - - if (MusicUtils.connect(context)) { - manager.getQueueManager().queue(tracks, 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; - } -} diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java deleted file mode 100644 index 563eedb..0000000 --- a/src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.bobert.netneutrality.tasks.music; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -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; - -public class CancelLoadTask extends ButtonTask implements WithParent, 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(TaskContext context) { - // Interaction will get removed after execution - } -} diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java index 6cdd788..319f568 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java +++ b/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java @@ -8,16 +8,20 @@ package me.bobert.netneutrality.tasks.music; import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; 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.TaskManager; 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.db.service.GuildService; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.music.SourceFinder; -import me.bobert.netneutrality.tasks.ButtonTask; +import me.bobert.netneutrality.core.tasks.impl.ButtonTask; +import me.bobert.netneutrality.tasks.generic.AcceptButton; +import me.bobert.netneutrality.tasks.generic.CancelButton; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; +import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.components.ActionRow; import net.dv8tion.jda.api.interactions.components.ItemComponent; @@ -31,6 +35,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta private final SourceFinder.AudioSourceType sourceType; public LoadPlaylistTask(AudioPlaylist playlist, SourceFinder.AudioSourceType sourceType) { + super(null, null); this.playlist = playlist; this.sourceType = sourceType; } @@ -57,8 +62,8 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta } int searchResultsLimit = 5; // Max number of buttons per row - int maxPlaylistSizeBeforeAsking = Integer.parseInt(context.getGuildManager() - .getGuildConfigManager() + int maxPlaylistSizeBeforeAsking = Integer.parseInt(GuildService.get(context.getGuild()) + .getGuildSettingService() .getSetting(GSettings.MUSIC_MAXPLAYLISTSIZE)); // Search results @@ -68,10 +73,10 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta for (int i = 0; i < searchResults.size(); i++) { AudioTrack track = searchResults.get(i); - Task buttonTask = new LoadTrackFromPlaylistTask(this, track); + Task buttonTask = new LoadTrackFromPlaylistTask(this, String.valueOf(i + 1), track); - buttonList.add(buttonTask.createButton().primary(String.valueOf(i + 1))); - childrens.add(buttonTask); + buttonList.add(buttonTask.createButton()); + addChild(buttonTask); } ActionRow firstRow = ActionRow.of(buttonList); @@ -99,21 +104,27 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta } } - @Override - public void execute(TaskContext context) { - // Only the buttons will get executed, this task is only used to insert them - } - private ActionRow commonButtons(List tracks, String addAllText, String cancelText) { - Task addAllTracksTask = new AddAllTracksTask(this, tracks); - childrens.add(addAllTracksTask); + Task accept = AcceptButton.createSuccessButton(this, addAllText, context -> { + MusicManager manager = MusicManager.get(context.getGuild()); + Member member = context.getGuildUser(); - Task cancelTask = new CancelLoadTask(this); - childrens.add(cancelTask); + if (MusicUtils.connect(context)) { + manager.getQueueManager().queue(tracks, member); + context.reply(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), member, SourceFinder.AudioSourceType.Youtube)); + + return Lifecycle.REMOVE_SELF_AND_CHILDREN; + } + + return Lifecycle.KEEP; + }); + + Task cancel = CancelButton.create(this, cancelText, context -> Lifecycle.REMOVE_SELF_AND_CHILDREN); + addChild(accept, cancel); return ActionRow.of( - addAllTracksTask.createButton().success(addAllText).withEmoji(Emoji.fromFormatted("U+1F60E")), - cancelTask.createButton().danger(cancelText).withEmoji(Emoji.fromFormatted("U+1F92E")) + accept.createButton().withEmoji(Emoji.fromFormatted("U+1F60E")), + cancel.createButton().withEmoji(Emoji.fromFormatted("U+1F92E")) ); } @@ -124,6 +135,8 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta @Override public void onInteractionDelete() { - removeAllChildTasks(); + for (Task child : getChildTasks()) { + TaskManager.getInstance().removeTask(child); + } } } diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java index d073440..f9cd164 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java +++ b/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java @@ -6,22 +6,32 @@ package me.bobert.netneutrality.tasks.music; */ import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -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.tasks.ButtonTask; +import me.bobert.netneutrality.tasks.generic.AcceptButton; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; -public class LoadTrackFromPlaylistTask extends ButtonTask implements WithParent { - private final LoadPlaylistTask parentTask; +public class LoadTrackFromPlaylistTask extends AcceptButton { private final AudioTrack track; - public LoadTrackFromPlaylistTask(LoadPlaylistTask parentTask, AudioTrack track) { - this.parentTask = parentTask; + public LoadTrackFromPlaylistTask(LoadPlaylistTask parentTask, String buttonLabel, AudioTrack track) { + super(parentTask, ButtonStyle.PRIMARY, buttonLabel, context -> { + MusicManager manager = MusicManager.get(context.getGuild()); + Member member = context.getGuildUser(); + + if (MusicUtils.connect(context)) { + manager.getQueueManager().queue(track, member); + context.reply(MessageFunction.buildAddingTrackMessage(track, member, SourceFinder.AudioSourceType.Youtube)); + + return Lifecycle.REMOVE_SELF_AND_CHILDREN; + } + + return Lifecycle.KEEP; + }); + this.track = track; } @@ -29,31 +39,4 @@ public class LoadTrackFromPlaylistTask extends ButtonTask implements WithParent< public String getName() { return String.format("load-track-%s", track.getIdentifier()); } - - @Override - public void replyToInteraction(CommandContext context) { - - } - - @Override - public void execute(TaskContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - Member member = context.getGuildUser(); - - if (MusicUtils.connect(context)) { - manager.getQueueManager().queue(track, member); - - context.reply(MessageFunction.buildAddingTrackMessage(track, member, SourceFinder.AudioSourceType.Youtube)); - } - } - - @Override - public LoadPlaylistTask getParentTask() { - return parentTask; - } - - @Override - public boolean shouldKeepChilds() { - return false; - } } diff --git a/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java b/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java index 697597a..ebf40a0 100644 --- a/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java +++ b/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java @@ -26,13 +26,11 @@ public class CleanupTaskThread implements Runnable { .toList(); if (!tasks.isEmpty()) { - logger.info("Cleaning up tasks.."); + logger.info("Cleaning up {} expired tasks..", tasks.size()); for (Task task : tasks) { TaskManager.getInstance().removeTask(task); } } - - } } diff --git a/src/main/java/me/bobert/netneutrality/utils/GuildUtils.java b/src/main/java/me/bobert/netneutrality/utils/GuildUtils.java new file mode 100644 index 0000000..66e8fcb --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/utils/GuildUtils.java @@ -0,0 +1,26 @@ +package me.bobert.netneutrality.utils; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.Context; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; + +public class GuildUtils { + + public static void addRolesToMember(Context context, Role role) { + addRolesToMember(context.getGuild(), context.getGuildUser(), role); + } + + public static void addRolesToMember(Guild guild, Member member, Role role) { + Member self = guild.getSelfMember(); + if (self.canInteract(role)) { + guild.addRoleToMember(member, role).queue(); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java b/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java index 33f24fb..b6b33fa 100644 --- a/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java +++ b/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java @@ -6,9 +6,9 @@ package me.bobert.netneutrality.utils; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.entities.OGuild; +import me.bobert.netneutrality.db.service.GuildService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.HibernateException; @@ -30,7 +30,6 @@ public class JpaUtils { * If the object is not found in the database and is of type {@link OGuild}, tries to recreate entity and throws * an exception to block execution. *

- * The session is not closed * * @param session The session in which to load the persistent object * @param object The persistent object @@ -45,9 +44,9 @@ public class JpaUtils { String cachedId = oGuild.getGuildId(); logger.error("Trying to load Guild[{}] while entity doesn't exist in database, reloading..", cachedId); - GuildManager guildManager = GuildManager.getManager(cachedId); - guildManager.reloadEntity(); - guildManager.getBlacklistCache().clear(); + GuildService service = GuildService.get(cachedId); + service.reloadEntity(); + service.getBlacklistService().removeGuildBlacklist(); logger.info("Guild[{}] reloaded", cachedId); } } diff --git a/src/main/java/me/bobert/netneutrality/utils/Settings.java b/src/main/java/me/bobert/netneutrality/utils/Settings.java index 938ac50..2a9415b 100644 --- a/src/main/java/me/bobert/netneutrality/utils/Settings.java +++ b/src/main/java/me/bobert/netneutrality/utils/Settings.java @@ -54,6 +54,10 @@ public class Settings { return INSTANCE; } + public static String getVersion() { + return Settings.getInstance().getMavenVersion(); + } + public String get(String key) { String property = prop.getProperty(key);