diff --git a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java index 58b964a..8a65b49 100644 --- a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java +++ b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java @@ -116,6 +116,7 @@ public class HidekoBot MessageCommandListener messageCommandListener = new MessageCommandListener(); messageCommandListener.registerCommand(new HelloCommand()); messageCommandListener.registerCommand(new CommandsCommand()); + messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.ClearCommand()); Cache.setMessageCommandListener(messageCommandListener); // register listeners diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/base/ClearChat.java b/src/main/java/wtf/beatrice/hidekobot/commands/base/ClearChat.java new file mode 100644 index 0000000..1bfe884 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/ClearChat.java @@ -0,0 +1,192 @@ +package wtf.beatrice.hidekobot.commands.base; + +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageHistory; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import wtf.beatrice.hidekobot.Cache; + +import java.util.ArrayList; +import java.util.List; + +public class ClearChat +{ + + public static String getLabel() { + return "clear"; + } + + public static String getDescription() { + return "Clear the current channel's chat."; + } + + public static Permission getPermission() { + return Permission.MESSAGE_MANAGE; + } + + public static String checkDMs(Channel channel) + { + if(!(channel instanceof TextChannel)) + { return "\uD83D\uDE22 Sorry! I can't delete messages here."; } + + return null; + } + + public static String checkDeleteAmount(int toDeleteAmount) + { + if(toDeleteAmount <= 0) + { return "\uD83D\uDE22 Sorry, I can't delete that amount of messages!"; } + + return null; + } + + public static int delete(int toDeleteAmount, long startingMessageId, MessageChannel channel) + { + // int to keep track of how many messages we actually deleted. + int deleted = 0; + + int limit = 95; //discord limits this method to range 2-100. we set it to 95 to be safe. + + // increase the count by 1, because we technically aren't clearing the first ID ever + // which is actually the slash command's ID and not a message. + toDeleteAmount++; + + // count how many times we have to iterate this to delete the full messages. + int iterations = toDeleteAmount / limit; + + //if there are some messages left, but less than , we need one more iterations. + int remainder = toDeleteAmount % limit; + if(remainder != 0) iterations++; + + // set the starting point. + long messageId = startingMessageId; + + // boolean to see if we're trying to delete more messages than possible. + boolean outOfBounds = false; + + // do iterate. + for(int iteration = 0; iteration < iterations; iteration++) + { + if(outOfBounds) break; + + // set how many messages to delete for this iteration (usually unless there's a remainder) + int iterationSize = limit; + + // if we are at the last iteration... + if(iteration+1 == iterations) + { + // check if we have or fewer messages to delete + if(remainder != 0) iterationSize = remainder; + } + + if(iterationSize == 1) + { + // grab the message + Message toDelete = channel.retrieveMessageById(messageId).complete(); + //only delete one message + if(toDelete != null) toDelete.delete().queue(); + else outOfBounds = true; + // increase deleted counter by 1 + deleted++; + } else { + // get the last messages. + MessageHistory.MessageRetrieveAction action = channel.getHistoryBefore(messageId, iterationSize - 1); + // note: first one is the most recent, last one is the oldest message. + List messages = new ArrayList<>(); + // (we are skipping first iteration since it would return an error, given that the id is the slash command and not a message) + if(iteration!=0) messages.add(channel.retrieveMessageById(messageId).complete()); + messages.addAll(action.complete().getRetrievedHistory()); + + // check if we only have one or zero messages left (trying to delete more than possible) + if(messages.size() <= 1) + { + outOfBounds = true; + } else { + // before deleting, we need to grab the message's id for next iteration. + action = channel.getHistoryBefore(messages.get(messages.size() - 1).getIdLong(), 1); + + List previousMessage = action.complete().getRetrievedHistory(); + + // if that message exists (we are not out of bounds)... store it + if(!previousMessage.isEmpty()) messageId = previousMessage.get(0).getIdLong(); + else outOfBounds = true; + } + + // queue messages for deletion + if(messages.size() == 1) + { + messages.get(0).delete().queue(); + } + else if(!messages.isEmpty()) + { + try { + ((TextChannel) channel).deleteMessages(messages).complete(); + /* alternatively, we could use purgeMessages, which is smarter... + however, it also tries to delete messages older than 2 weeks + which are restricted by discord, and thus has to use + a less efficient way that triggers rate-limiting very quickly. */ + + } catch (Exception e) + { + + return -1; + } + } + + // increase deleted counter by + deleted += messages.size(); + } + } + + return deleted; + } + + public static Button getDismissButton() + { + return Button.primary("clear_dismiss", "Dismiss") + .withEmoji(Emoji.fromUnicode("❌")); + } + + public static String parseAmount(int deleted) + { + if(deleted < 1) + { + return "\uD83D\uDE22 Couldn't clear any message!"; + } else if(deleted == 1) + { + return "✂ Cleared 1 message!"; + } else { + return "✂ Cleared " + deleted + " messages!"; + } + } + + private void respond(Object responseFlowObj, String content) + { + if(responseFlowObj instanceof InteractionHook) { + ((InteractionHook) responseFlowObj).editOriginal(content).queue(); + } else if (responseFlowObj instanceof Message) { + ((Message) responseFlowObj).reply(content).queue(); + } + } + + + + public static void dismissMessage(ButtonInteractionEvent event) + { + + if(!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) + { + event.reply("❌ You did not run this command!").setEphemeral(true).queue(); + } else + { + event.getInteraction().getMessage().delete().queue(); + } + } + +} diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java new file mode 100644 index 0000000..af28097 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java @@ -0,0 +1,79 @@ +package wtf.beatrice.hidekobot.commands.message; + +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import wtf.beatrice.hidekobot.Cache; +import wtf.beatrice.hidekobot.commands.base.ClearChat; +import wtf.beatrice.hidekobot.objects.commands.MessageCommand; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class ClearCommand implements MessageCommand +{ + + @Override + public LinkedList getCommandLabels() { + return new LinkedList<>(Collections.singletonList(ClearChat.getLabel())); + } + + @Override + public List getPermissions() { return Collections.singletonList(ClearChat.getPermission()); } + + @Override + public boolean passRawArgs() { + return false; + } + + @Override + public void runCommand(MessageReceivedEvent event, String label, String[] args) + { + // start a new thread, because we are doing synchronous, thread-blocking operations! + new Thread(() -> + { + String senderId = event.getMessage().getAuthor().getId(); + + // check if user is trying to run command in dms. + String error = ClearChat.checkDMs(event.getChannel()); + if (error != null) { + event.getMessage().reply(error).queue(); + return; + } + + // get the amount from the command args. + Integer toDeleteAmount; + if (args.length == 0) toDeleteAmount = 1; + else toDeleteAmount = Integer.parseInt(args[0]); + + error = ClearChat.checkDeleteAmount(toDeleteAmount); + if (error != null) { + event.getMessage().reply(error).queue(); + return; + } + + // answer by saying that the operation has begun. + String content = "\uD83D\uDEA7 Clearing..."; + Message botMessage = event.getMessage().reply(content).complete(); + + int deleted = ClearChat.delete(toDeleteAmount, + event.getMessageIdLong(), + event.getChannel()); + + // get a nicely formatted message that logs the deletion of messages. + content = ClearChat.parseAmount(deleted); + + // edit the message text and attach a button. + Button dismiss = ClearChat.getDismissButton(); + botMessage = botMessage.editMessage(content).setActionRow(dismiss).complete(); + + // add the message to database. + Cache.getDatabaseSource().queueDisabling(botMessage); + Cache.getDatabaseSource().trackRanCommandReply(botMessage, event.getAuthor()); + }).start(); + + } + +} diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java index b319bfe..17e188f 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java @@ -1,52 +1,43 @@ package wtf.beatrice.hidekobot.commands.slash; -import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageHistory; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; import org.jetbrains.annotations.NotNull; import wtf.beatrice.hidekobot.Cache; +import wtf.beatrice.hidekobot.commands.base.ClearChat; import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl; -import java.util.ArrayList; -import java.util.List; - public class ClearCommand extends SlashCommandImpl { @Override public CommandData getSlashCommandData() { - return Commands.slash("clear", "Clear the current channel's chat.") + return Commands.slash(ClearChat.getLabel(), + ClearChat.getDescription()) .addOption(OptionType.INTEGER, "amount", "The amount of messages to delete.") - .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE)); + .setDefaultPermissions(DefaultMemberPermissions.enabledFor(ClearChat.getPermission())); } @Override public void runSlashCommand(@NotNull SlashCommandInteractionEvent event) { - - // run in a new thread, so we don't block the main one + // start a new thread, because we are doing synchronous, thread-blocking operations! new Thread(() -> { + event.deferReply().complete(); - MessageChannel channel = event.getChannel(); - - if(!(channel instanceof TextChannel)) + // check if user is trying to run command in dms. + String error = ClearChat.checkDMs(event.getChannel()); + if(error != null) { - event.reply("\uD83D\uDE22 Sorry! I can't delete messages here.").queue(); + event.getHook().editOriginal(error).queue(); return; } @@ -56,147 +47,34 @@ public class ClearCommand extends SlashCommandImpl OptionMapping amountOption = event.getOption("amount"); int toDeleteAmount = amountOption == null ? 1 : amountOption.getAsInt(); - if(toDeleteAmount <= 0) + error = ClearChat.checkDeleteAmount(toDeleteAmount); + if(error != null) { - event.reply("\uD83D\uDE22 Sorry, I can't delete that amount of messages!").queue(); + event.getHook().editOriginal(error).queue(); + return; } - else { - // answer by saying that the operation has begun. - InteractionHook replyInteraction = event.reply("\uD83D\uDEA7 Clearing...").complete(); - // int to keep track of how many messages we actually deleted. - int deleted = 0; + // answer by saying that the operation has begun. + String content = "\uD83D\uDEA7 Clearing..."; + Message botMessage = event.getHook().editOriginal(content).complete(); - int limit = 95; //discord limits this method to range 2-100. we set it to 95 to be safe. + // actually delete the messages. + int deleted = ClearChat.delete(toDeleteAmount, + event.getInteraction().getIdLong(), + event.getChannel()); - // increase the count by 1, because we technically aren't clearing the first ID ever - // which is actually the slash command's ID and not a message. - toDeleteAmount++; + // get a nicely formatted message that logs the deletion of messages. + content = ClearChat.parseAmount(deleted); - // count how many times we have to iterate this to delete the full messages. - int iterations = toDeleteAmount / limit; + // edit the message text and attach a button. + Button dismiss = ClearChat.getDismissButton(); + botMessage = botMessage.editMessage(content).setActionRow(dismiss).complete(); - //if there are some messages left, but less than , we need one more iterations. - int remainder = toDeleteAmount % limit; - if(remainder != 0) iterations++; + // add the message to database. + Cache.getDatabaseSource().queueDisabling(botMessage); + Cache.getDatabaseSource().trackRanCommandReply(botMessage, event.getUser()); - // set the starting point. - long messageId = event.getInteraction().getIdLong(); - - // boolean to see if we're trying to delete more messages than possible. - boolean outOfBounds = false; - - // do iterate. - for(int iteration = 0; iteration < iterations; iteration++) - { - if(outOfBounds) break; - - // set how many messages to delete for this iteration (usually unless there's a remainder) - int iterationSize = limit; - - // if we are at the last iteration... - if(iteration+1 == iterations) - { - // check if we have or fewer messages to delete - if(remainder != 0) iterationSize = remainder; - } - - if(iterationSize == 1) - { - // grab the message - Message toDelete = ((TextChannel)channel).retrieveMessageById(messageId).complete(); - //only delete one message - if(toDelete != null) toDelete.delete().queue(); - else outOfBounds = true; - // increase deleted counter by 1 - deleted++; - } else { - // get the last messages. - MessageHistory.MessageRetrieveAction action = channel.getHistoryBefore(messageId, iterationSize - 1); - // note: first one is the most recent, last one is the oldest message. - List messages = new ArrayList<>(); - // (we are skipping first iteration since it would return an error, given that the id is the slash command and not a message) - if(iteration!=0) messages.add(((TextChannel)channel).retrieveMessageById(messageId).complete()); - messages.addAll(action.complete().getRetrievedHistory()); - - // check if we only have one or zero messages left (trying to delete more than possible) - if(messages.size() <= 1) - { - outOfBounds = true; - } else { - // before deleting, we need to grab the message's id for next iteration. - action = channel.getHistoryBefore(messages.get(messages.size() - 1).getIdLong(), 1); - - List previousMessage = action.complete().getRetrievedHistory(); - - // if that message exists (we are not out of bounds)... store it - if(!previousMessage.isEmpty()) messageId = previousMessage.get(0).getIdLong(); - else outOfBounds = true; - } - - // queue messages for deletion - if(messages.size() == 1) - { - messages.get(0).delete().queue(); - } - else if(!messages.isEmpty()) - { - try { - ((TextChannel) channel).deleteMessages(messages).complete(); - /* alternatively, we could use purgeMessages, which is smarter... - however, it also tries to delete messages older than 2 weeks - which are restricted by discord, and thus has to use - a less efficient way that triggers rate-limiting very quickly. */ - } catch (Exception e) - { - replyInteraction.editOriginal("\uD83D\uDE22 Sorry, I ran into an error! " + e.getMessage()).queue(); - return; // warning: this quits everything. - } - } - - // increase deleted counter by - deleted += messages.size(); - } - } - - - Button dismissButton = Button.primary("clear_dismiss", "Dismiss") - .withEmoji(Emoji.fromUnicode("❌")); - - WebhookMessageEditAction webhookMessageEditAction; - - // log having deleted the messages (or not). - if(deleted < 1) - { - webhookMessageEditAction = replyInteraction.editOriginal("\uD83D\uDE22 Couldn't clear any message!"); - } else if(deleted == 1) - { - webhookMessageEditAction = replyInteraction.editOriginal("✂ Cleared 1 message!"); - } else { - webhookMessageEditAction = replyInteraction.editOriginal("✂ Cleared " + deleted + " messages!"); - } - - Message message = webhookMessageEditAction - .setActionRow(dismissButton) - .complete(); - - Cache.getDatabaseSource().queueDisabling(message); - Cache.getDatabaseSource().trackRanCommandReply(message, event.getUser()); - - } }).start(); } - - public void dismissMessage(ButtonInteractionEvent event) - { - - if(!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) - { - event.reply("❌ You did not run this command!").setEphemeral(true).queue(); - } else - { - event.getInteraction().getMessage().delete().queue(); - } - } } diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java index 41be8c7..17606e6 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java @@ -2,7 +2,7 @@ package wtf.beatrice.hidekobot.listeners; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; -import wtf.beatrice.hidekobot.commands.slash.ClearCommand; +import wtf.beatrice.hidekobot.commands.base.ClearChat; import wtf.beatrice.hidekobot.commands.slash.CoinFlipCommand; public class ButtonInteractionListener extends ListenerAdapter @@ -18,7 +18,7 @@ public class ButtonInteractionListener extends ListenerAdapter case "coinflip_reflip" -> new CoinFlipCommand().buttonReFlip(event); // clearchat command - case "clear_dismiss" -> new ClearCommand().dismissMessage(event); + case "clear_dismiss" -> ClearChat.dismissMessage(event); }