From 1091e66013d320d035781e6a4f91eaaaa13285ce Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Wed, 3 Jun 2020 19:22:31 +0200 Subject: [PATCH] added purge command to delete messages via bulk delete adapted api of status message service added concept of self destruct command results, these will cause the command message to be deleted --- .../abstracto/moderation/commands/Purge.java | 67 ++++++++ .../moderation/service/PurgeServiceBean.java | 148 ++++++++++++++++++ .../purge/purge_status_update_en_US.ftl | 1 + .../no_message_found_exception_en_US.ftl | 1 + .../exception/NoMessageFoundException.java | 20 +++ .../commands/PurgeStatusUpdateModel.java | 13 ++ .../moderation/service/PurgeService.java | 12 ++ .../post/SelfDestructPostExecution.java | 18 +++ .../core/service/MessageServiceBean.java | 15 ++ .../core/command/execution/CommandResult.java | 4 + .../core/command/execution/ResultState.java | 2 +- .../core/service/MessageService.java | 4 + .../abstracto/core/utils/ExceptionUtils.java | 27 ++++ .../docs/asciidoc/features/moderation.adoc | 5 + .../purge/help/purge_description_en_US.ftl | 1 + .../purge/help/purge_long_help_en_US.ftl | 6 + .../help/purge_parameter_amount_en_US.ftl | 1 + .../help/purge_parameter_member_en_US.ftl | 1 + .../commands/purge/help/purge_usage_en_US.ftl | 1 + .../purge_status_update_message_en_US.ftl | 1 + ..._message_found_exception_message_en_US.ftl | 1 + .../service/TemplateServiceBean.java | 5 + .../templating/service/TemplateService.java | 8 + 23 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/commands/Purge.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/command/purge/purge_status_update_en_US.ftl create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/exception/no_message_found_exception_en_US.ftl create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/exception/NoMessageFoundException.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/models/template/commands/PurgeStatusUpdateModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeService.java create mode 100644 abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/post/SelfDestructPostExecution.java create mode 100644 abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/utils/ExceptionUtils.java create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_description_en_US.ftl create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_long_help_en_US.ftl create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_amount_en_US.ftl create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_member_en_US.ftl create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_usage_en_US.ftl create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/purge_status_update_message_en_US.ftl create mode 100644 abstracto-application/template-config/src/main/resources/templates/en_US/moderation/exception/no_message_found_exception_message_en_US.ftl diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/commands/Purge.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/commands/Purge.java new file mode 100644 index 000000000..a163f99a4 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/commands/Purge.java @@ -0,0 +1,67 @@ +package dev.sheldan.abstracto.moderation.commands; + +import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; +import dev.sheldan.abstracto.core.command.config.CommandConfiguration; +import dev.sheldan.abstracto.core.command.config.HelpInfo; +import dev.sheldan.abstracto.core.command.config.Parameter; +import dev.sheldan.abstracto.core.command.execution.CommandContext; +import dev.sheldan.abstracto.core.command.execution.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureEnum; +import dev.sheldan.abstracto.core.utils.ExceptionUtils; +import dev.sheldan.abstracto.moderation.config.ModerationModule; +import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures; +import dev.sheldan.abstracto.moderation.service.PurgeService; +import dev.sheldan.abstracto.templating.service.TemplateService; +import net.dv8tion.jda.api.entities.Member; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Component +public class Purge extends AbstractConditionableCommand { + + @Autowired + private PurgeService purgeService; + + @Autowired + private TemplateService templateService; + + @Autowired + private ExceptionUtils exceptionUtils; + + @Override + public CommandResult execute(CommandContext commandContext) { + Integer amountOfMessages = (Integer) commandContext.getParameters().getParameters().get(0); + Member memberToPurgeMessagesOf = null; + if(commandContext.getParameters().getParameters().size() == 2) { + memberToPurgeMessagesOf = (Member) commandContext.getParameters().getParameters().get(1); + } + CompletableFuture future = purgeService.purgeMessagesInChannel(amountOfMessages, commandContext.getChannel(), commandContext.getMessage(), memberToPurgeMessagesOf); + future.whenComplete((aVoid, throwable) -> exceptionUtils.handleExceptionIfTemplatable(throwable, commandContext.getChannel())); + return CommandResult.fromSelfDestruct(); + } + + @Override + public CommandConfiguration getConfiguration() { + List parameters = new ArrayList<>(); + parameters.add(Parameter.builder().name("amount").type(Integer.class).templated(true).build()); + parameters.add(Parameter.builder().name("member").type(Member.class).optional(true).templated(true).build()); + HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + return CommandConfiguration.builder() + .name("purge") + .module(ModerationModule.MODERATION) + .templated(true) + .causesReaction(true) + .parameters(parameters) + .help(helpInfo) + .build(); + } + + @Override + public FeatureEnum getFeature() { + return ModerationFeatures.MODERATION; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java new file mode 100644 index 000000000..616b1dd88 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java @@ -0,0 +1,148 @@ +package dev.sheldan.abstracto.moderation.service; + +import dev.sheldan.abstracto.core.service.MessageService; +import dev.sheldan.abstracto.moderation.exception.NoMessageFoundException; +import dev.sheldan.abstracto.moderation.models.template.commands.PurgeStatusUpdateModel; +import dev.sheldan.abstracto.templating.model.MessageToSend; +import dev.sheldan.abstracto.templating.service.TemplateService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageHistory; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.utils.MiscUtil; +import net.dv8tion.jda.api.utils.TimeUtil; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@Component +@Slf4j +public class PurgeServiceBean implements PurgeService { + + @Autowired + private MessageService messageService; + + @Autowired + private TemplateService templateService; + + @Override + public CompletableFuture purgeMessagesInChannel(Integer amountToDelete, TextChannel channel, Long startId, Member purgedMember) { + return purgeMessages(amountToDelete, channel, startId, purgedMember, amountToDelete, 0, 0L); + } + + @NotNull + private CompletableFuture purgeMessages(Integer amountToDelete, TextChannel channel, Long startId, Member purgedMember, Integer totalCount, Integer currentCount, Long statusMessageId) { + CompletableFuture deletionFuture = new CompletableFuture<>(); + int toDeleteInThisIteration; + int messageLimit = 100; + if(amountToDelete >= messageLimit){ + toDeleteInThisIteration = messageLimit; + } else { + toDeleteInThisIteration = amountToDelete % messageLimit; + } + + CompletableFuture historyFuture = channel.getHistoryBefore(startId, toDeleteInThisIteration).submit(); + CompletableFuture statusMessageFuture = getOrCreatedStatusMessage(channel, totalCount, statusMessageId); + + CompletableFuture coreFuture = CompletableFuture.allOf(historyFuture, statusMessageFuture); + coreFuture.thenAccept(voidParam -> { + try { + List retrievedHistory = historyFuture.get().getRetrievedHistory(); + List messagesToDeleteNow = filterMessagesToDelete(retrievedHistory, purgedMember); + Long currentStatusMessageId = statusMessageFuture.get(); + if(messagesToDeleteNow.size() == 0) { + deletionFuture.completeExceptionally(new NoMessageFoundException()); + channel.deleteMessageById(currentStatusMessageId).queueAfter(5, TimeUnit.SECONDS); + return; + } + Message latestMessage = messagesToDeleteNow.get(messagesToDeleteNow.size() - 1); + log.trace("Deleting {} messages directly", messagesToDeleteNow.size()); + int newCurrentCount = currentCount + messagesToDeleteNow.size(); + int newAmountToDelete = amountToDelete - messageLimit; + Consumer consumer = deletionConsumer(newAmountToDelete, channel, purgedMember, totalCount, newCurrentCount, deletionFuture, currentStatusMessageId, latestMessage); + if (messagesToDeleteNow.size() > 1) { + bulkDeleteMessages(channel, deletionFuture, messagesToDeleteNow, consumer); + } else if (messagesToDeleteNow.size() == 1) { + messagesToDeleteNow.get(0).delete().queue(consumer, deletionFuture::completeExceptionally); + } + + } catch (Exception e) { + log.warn("Failed to purge messages.", e); + deletionFuture.completeExceptionally(e); + } + }).exceptionally(throwable -> { + log.warn("Failed to fetch messages.", throwable); + return null; + }); + + return CompletableFuture.allOf(coreFuture, deletionFuture); + } + + private void bulkDeleteMessages(TextChannel channel, CompletableFuture deletionFuture, List messagesToDeleteNow, Consumer consumer) { + try { + channel.deleteMessages(messagesToDeleteNow).queue(consumer, deletionFuture::completeExceptionally); + } catch (IllegalArgumentException e) { + channel.sendMessage(e.getMessage()).queue(); + log.warn("Failed to bulk delete, message was most likely too old to delete by bulk.", e); + deletionFuture.complete(null); + } + } + + private CompletableFuture getOrCreatedStatusMessage(TextChannel channel, Integer totalCount, Long statusMessageId) { + CompletableFuture statusMessageFuture; + if(statusMessageId == 0) { + PurgeStatusUpdateModel model = PurgeStatusUpdateModel.builder().currentlyDeleted(0).totalToDelete(totalCount).build(); + MessageToSend messageToSend = templateService.renderTemplateToMessageToSend("purge_status_update", model); + statusMessageFuture = messageService.createStatusMessageId(messageToSend, channel); + } else { + statusMessageFuture = CompletableFuture.completedFuture(statusMessageId); + } + return statusMessageFuture; + } + + private List filterMessagesToDelete(List retrievedHistory, Member purgedMember) { + long twoWeeksAgo = TimeUtil.getDiscordTimestamp((System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000))); + List messagesToDeleteNow = new ArrayList<>(); + for (Message messageObj : retrievedHistory) { + if (MiscUtil.parseSnowflake(messageObj.getId()) > twoWeeksAgo) { + if(purgedMember != null) { + if(messageObj.getAuthor().getIdLong() == purgedMember.getIdLong()) { + messagesToDeleteNow.add(messageObj); + } + } else { + messagesToDeleteNow.add(messageObj); + } + } + } + return messagesToDeleteNow; + } + + private Consumer deletionConsumer(Integer amountToDelete, TextChannel channel, Member purgedMember, Integer totalCount, Integer currentCount, CompletableFuture deletionFuture, Long currentStatusMessageId, Message earliestMessage) { + return aVoid -> { + if (amountToDelete > 1) { + purgeMessages(amountToDelete, channel, earliestMessage.getIdLong(), purgedMember, totalCount, currentCount, currentStatusMessageId).thenAccept(aVoid1 -> + deletionFuture.complete(null) + ); + } else { + channel.deleteMessageById(currentStatusMessageId).queueAfter(5, TimeUnit.SECONDS); + deletionFuture.complete(null); + } + log.info("Setting status for {} out of {}", currentCount, totalCount); + PurgeStatusUpdateModel finalUpdateModel = PurgeStatusUpdateModel.builder().currentlyDeleted(currentCount).totalToDelete(totalCount).build(); + MessageToSend finalUpdateMessage = templateService.renderTemplateToMessageToSend("purge_status_update", finalUpdateModel); + messageService.updateStatusMessage(channel, currentStatusMessageId, finalUpdateMessage); + }; + } + + @Override + public CompletableFuture purgeMessagesInChannel(Integer count, TextChannel channel, Message origin, Member purgingRestriction) { + return purgeMessagesInChannel(count, channel, origin.getIdLong(), purgingRestriction); + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/command/purge/purge_status_update_en_US.ftl b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/command/purge/purge_status_update_en_US.ftl new file mode 100644 index 000000000..36202062b --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/command/purge/purge_status_update_en_US.ftl @@ -0,0 +1 @@ +<#include "purge_status_update_message"> \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/exception/no_message_found_exception_en_US.ftl b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/exception/no_message_found_exception_en_US.ftl new file mode 100644 index 000000000..b3ac0014f --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/templates/exception/no_message_found_exception_en_US.ftl @@ -0,0 +1 @@ +<#include "no_message_found_exception_message"> \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/exception/NoMessageFoundException.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/exception/NoMessageFoundException.java new file mode 100644 index 000000000..0a378c83a --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/exception/NoMessageFoundException.java @@ -0,0 +1,20 @@ +package dev.sheldan.abstracto.moderation.exception; + +import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; +import dev.sheldan.abstracto.templating.Templatable; + +public class NoMessageFoundException extends AbstractoRunTimeException implements Templatable { + public NoMessageFoundException() { + super(""); + } + + @Override + public String getTemplateName() { + return "no_message_found_exception"; + } + + @Override + public Object getTemplateModel() { + return new Object(); + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/models/template/commands/PurgeStatusUpdateModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/models/template/commands/PurgeStatusUpdateModel.java new file mode 100644 index 000000000..91c349179 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/models/template/commands/PurgeStatusUpdateModel.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.moderation.models.template.commands; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class PurgeStatusUpdateModel { + private Integer currentlyDeleted; + private Integer totalToDelete; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeService.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeService.java new file mode 100644 index 000000000..a78072bbe --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeService.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.moderation.service; + +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.TextChannel; + +import java.util.concurrent.CompletableFuture; + +public interface PurgeService { + CompletableFuture purgeMessagesInChannel(Integer count, TextChannel channel, Long messageId, Member purgingRestriction); + CompletableFuture purgeMessagesInChannel(Integer count, TextChannel channel, Message origin, Member purgingRestriction); +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/post/SelfDestructPostExecution.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/post/SelfDestructPostExecution.java new file mode 100644 index 000000000..a4dd68110 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/post/SelfDestructPostExecution.java @@ -0,0 +1,18 @@ +package dev.sheldan.abstracto.core.command.post; + +import dev.sheldan.abstracto.core.command.Command; +import dev.sheldan.abstracto.core.command.execution.CommandContext; +import dev.sheldan.abstracto.core.command.execution.CommandResult; +import dev.sheldan.abstracto.core.command.execution.ResultState; +import dev.sheldan.abstracto.core.command.service.PostCommandExecution; +import org.springframework.stereotype.Component; + +@Component +public class SelfDestructPostExecution implements PostCommandExecution { + @Override + public void execute(CommandContext commandContext, CommandResult commandResult, Command command) { + if(commandResult.getResult().equals(ResultState.SELF_DESTRUCT)) { + commandContext.getMessage().delete().queue(); + } + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java index e9ed59e93..4ee584c33 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java @@ -83,11 +83,26 @@ public class MessageServiceBean implements MessageService { return channelService.sendMessageToSendToAChannel(messageToSend, channel).get(0); } + @Override + public CompletableFuture createStatusMessage(MessageToSend messageToSend, MessageChannel channel) { + return channelService.sendMessageToSendToChannel(messageToSend, channel).get(0); + } + + @Override + public CompletableFuture createStatusMessageId(MessageToSend messageToSend, MessageChannel channel) { + return channelService.sendMessageToSendToChannel(messageToSend, channel).get(0).thenApply(ISnowflake::getIdLong); + } + @Override public void updateStatusMessage(AChannel channel, Long messageId, MessageToSend messageToSend) { channelService.editMessageInAChannel(messageToSend, channel, messageId); } + @Override + public void updateStatusMessage(MessageChannel channel, Long messageId, MessageToSend messageToSend) { + channelService.editMessageInAChannel(messageToSend, channel, messageId); + } + @Override public void sendMessageToUser(AUserInAServer userInAServer, String text, TextChannel feedbackChannel) { Member memberInServer = botService.getMemberInServer(userInAServer); diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/CommandResult.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/CommandResult.java index 687ee3845..df4b82595 100644 --- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/CommandResult.java +++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/CommandResult.java @@ -19,6 +19,10 @@ public class CommandResult { return CommandResult.builder().result(ResultState.SUCCESSFUL).build(); } + public static CommandResult fromSelfDestruct() { + return CommandResult.builder().result(ResultState.SELF_DESTRUCT).build(); + } + public static CommandResult fromError(String message){ return CommandResult.builder().result(ResultState.ERROR).message(message).build(); } diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/ResultState.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/ResultState.java index 76ac552d5..6089fa8bc 100644 --- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/ResultState.java +++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/execution/ResultState.java @@ -1,5 +1,5 @@ package dev.sheldan.abstracto.core.command.execution; public enum ResultState { - ERROR, SUCCESSFUL, IGNORED, CONDITION + ERROR, SUCCESSFUL, IGNORED, CONDITION, SELF_DESTRUCT } diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java index 099dd860f..e14373e98 100644 --- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java +++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java @@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.templating.model.MessageToSend; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; @@ -16,7 +17,10 @@ public interface MessageService { List> addReactionsToMessageWithFuture(List emoteKeys, Long serverId, Message message); CompletableFuture deleteMessageInChannelInServer(Long serverId, Long channelId, Long messageId); CompletableFuture createStatusMessage(MessageToSend messageToSend, AChannel channel); + CompletableFuture createStatusMessage(MessageToSend messageToSend, MessageChannel channel); + CompletableFuture createStatusMessageId(MessageToSend messageToSend, MessageChannel channel); void updateStatusMessage(AChannel channel, Long messageId, MessageToSend messageToSend); + void updateStatusMessage(MessageChannel channel, Long messageId, MessageToSend messageToSend); void sendMessageToUser(AUserInAServer userInAServer, String text, TextChannel feedbackChannel); void sendMessageToUser(User user, String text, TextChannel feedbackChannel); } diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/utils/ExceptionUtils.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/utils/ExceptionUtils.java new file mode 100644 index 000000000..bdf3a5a06 --- /dev/null +++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/utils/ExceptionUtils.java @@ -0,0 +1,27 @@ +package dev.sheldan.abstracto.core.utils; + +import dev.sheldan.abstracto.templating.Templatable; +import dev.sheldan.abstracto.templating.service.TemplateService; +import net.dv8tion.jda.api.entities.MessageChannel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class ExceptionUtils { + + @Autowired + private TemplateService templateService; + + @Transactional + public void handleExceptionIfTemplatable(Throwable throwable, MessageChannel channel) { + if(throwable != null) { + if(throwable.getCause() instanceof Templatable) { + String exceptionText = templateService.renderTemplatable((Templatable) throwable.getCause()); + channel.sendMessage(exceptionText).queue(); + } else { + channel.sendMessage(throwable.getCause().getMessage()).queue(); + } + } + } +} diff --git a/abstracto-application/documentation/src/main/docs/asciidoc/features/moderation.adoc b/abstracto-application/documentation/src/main/docs/asciidoc/features/moderation.adoc index 23c76132a..28265b35a 100644 --- a/abstracto-application/documentation/src/main/docs/asciidoc/features/moderation.adoc +++ b/abstracto-application/documentation/src/main/docs/asciidoc/features/moderation.adoc @@ -29,6 +29,11 @@ Change the slow mode in a channel:: * Usage: `slowmode [channel]` * Description: This command sets the slow mode in the `channel` to the given `duration`. This command uses duration parsing. The `channel` is optional and if none is provided, the current channel is used. * Example: `slowMode 1h2m3s #general` in order to set the slow mode in channel `general` to 1 hour 2 minutes and 3 seconds (the #general is a user mention) +Purging messages in a channel:: +* Usage: `purge [member]` +* Description: Deletes the last `messageCount` messages in the current channel. If a `member` is provided as parameter, only the messages by this member +will be deleted. The deletion of this messages will *not* be logged by the logging mechanism. The messages to be deleted need to be from within the last 2 weeks, but there is no limit on how much messages can be deleted besides that. +While the command is ongoing, a status update message will be shown indicating how far the command is. This message will be deleted after the command is done. === Warning diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_description_en_US.ftl new file mode 100644 index 000000000..7be1b6cb7 --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_description_en_US.ftl @@ -0,0 +1 @@ +Deletes the last n messages in the channel \ No newline at end of file diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_long_help_en_US.ftl new file mode 100644 index 000000000..2247dd8cc --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_long_help_en_US.ftl @@ -0,0 +1,6 @@ +Deletes the last n messages in the channel. The messages are allowed to be at most 2 weeks old. +If a member is provided as a parameter, only messages by this member are deleted, but at most n messages are considered. +For example, if you execute the command with 200 messages and specify member User#1234, and User#1234 does not have any messages +in the last 200 message, no message will be deleted. +While the command is going on a status message indicating how many messages are currently being deleted is shown. +If messages older than two weeks are found, the command will stop and post an error message. \ No newline at end of file diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_amount_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_amount_en_US.ftl new file mode 100644 index 000000000..1a254096f --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_amount_en_US.ftl @@ -0,0 +1 @@ +Amount of messages do delete. \ No newline at end of file diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_member_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_member_en_US.ftl new file mode 100644 index 000000000..536f26b99 --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_parameter_member_en_US.ftl @@ -0,0 +1 @@ +The member to delete messages of. \ No newline at end of file diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_usage_en_US.ftl new file mode 100644 index 000000000..3ad3aba6a --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/help/purge_usage_en_US.ftl @@ -0,0 +1 @@ +purge [member] \ No newline at end of file diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/purge_status_update_message_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/purge_status_update_message_en_US.ftl new file mode 100644 index 000000000..88f8bcff0 --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/commands/purge/purge_status_update_message_en_US.ftl @@ -0,0 +1 @@ +${currentlyDeleted} out of ${totalToDelete} messages deleted. \ No newline at end of file diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/exception/no_message_found_exception_message_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/exception/no_message_found_exception_message_en_US.ftl new file mode 100644 index 000000000..3539f17d6 --- /dev/null +++ b/abstracto-application/template-config/src/main/resources/templates/en_US/moderation/exception/no_message_found_exception_message_en_US.ftl @@ -0,0 +1 @@ +No more messages found to delete. \ No newline at end of file diff --git a/abstracto-application/templating/templating-impl/src/main/java/dev/sheldan/abstracto/templating/service/TemplateServiceBean.java b/abstracto-application/templating/templating-impl/src/main/java/dev/sheldan/abstracto/templating/service/TemplateServiceBean.java index b6ff0419b..11c1233e7 100644 --- a/abstracto-application/templating/templating-impl/src/main/java/dev/sheldan/abstracto/templating/service/TemplateServiceBean.java +++ b/abstracto-application/templating/templating-impl/src/main/java/dev/sheldan/abstracto/templating/service/TemplateServiceBean.java @@ -114,6 +114,11 @@ public class TemplateServiceBean implements TemplateService { .build(); } + @Override + public MessageToSend renderTemplateToMessageToSend(String key, Object model) { + return MessageToSend.builder().message(renderTemplate(key, model)).build(); + } + private void createFieldsForEmbed(List embedBuilders, EmbedConfiguration configuration) { for (int i = 0; i < configuration.getFields().size(); i++) { EmbedField field = configuration.getFields().get(i); diff --git a/abstracto-application/templating/templating-interface/src/main/java/dev/sheldan/abstracto/templating/service/TemplateService.java b/abstracto-application/templating/templating-interface/src/main/java/dev/sheldan/abstracto/templating/service/TemplateService.java index e16151a49..f5c6aa695 100644 --- a/abstracto-application/templating/templating-interface/src/main/java/dev/sheldan/abstracto/templating/service/TemplateService.java +++ b/abstracto-application/templating/templating-interface/src/main/java/dev/sheldan/abstracto/templating/service/TemplateService.java @@ -18,6 +18,14 @@ public interface TemplateService { */ MessageToSend renderEmbedTemplate(String key, Object model); + /** + * Renders the given template directly into a {@link MessageToSend} but the template only represents a text message. + * @param key The key of the string template to be rendered. + * @param model The model used to render the template + * @return A {@link MessageToSend} instance only containing the string property. + */ + MessageToSend renderTemplateToMessageToSend(String key, Object model); + /** * Renders the template identified by the key with the given {@link HashMap} used as model and returns the value as a string * @param key The key of the template to be rendered.