mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-03-27 14:23:56 +00:00
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
This commit is contained in:
@@ -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<Void> 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<Parameter> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> purgeMessagesInChannel(Integer amountToDelete, TextChannel channel, Long startId, Member purgedMember) {
|
||||
return purgeMessages(amountToDelete, channel, startId, purgedMember, amountToDelete, 0, 0L);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<Void> purgeMessages(Integer amountToDelete, TextChannel channel, Long startId, Member purgedMember, Integer totalCount, Integer currentCount, Long statusMessageId) {
|
||||
CompletableFuture<Void> deletionFuture = new CompletableFuture<>();
|
||||
int toDeleteInThisIteration;
|
||||
int messageLimit = 100;
|
||||
if(amountToDelete >= messageLimit){
|
||||
toDeleteInThisIteration = messageLimit;
|
||||
} else {
|
||||
toDeleteInThisIteration = amountToDelete % messageLimit;
|
||||
}
|
||||
|
||||
CompletableFuture<MessageHistory> historyFuture = channel.getHistoryBefore(startId, toDeleteInThisIteration).submit();
|
||||
CompletableFuture<Long> statusMessageFuture = getOrCreatedStatusMessage(channel, totalCount, statusMessageId);
|
||||
|
||||
CompletableFuture<Void> coreFuture = CompletableFuture.allOf(historyFuture, statusMessageFuture);
|
||||
coreFuture.thenAccept(voidParam -> {
|
||||
try {
|
||||
List<Message> retrievedHistory = historyFuture.get().getRetrievedHistory();
|
||||
List<Message> 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<Void> 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<Void> deletionFuture, List<Message> messagesToDeleteNow, Consumer<Void> 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<Long> getOrCreatedStatusMessage(TextChannel channel, Integer totalCount, Long statusMessageId) {
|
||||
CompletableFuture<Long> 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<Message> filterMessagesToDelete(List<Message> retrievedHistory, Member purgedMember) {
|
||||
long twoWeeksAgo = TimeUtil.getDiscordTimestamp((System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000)));
|
||||
List<Message> 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<Void> deletionConsumer(Integer amountToDelete, TextChannel channel, Member purgedMember, Integer totalCount, Integer currentCount, CompletableFuture<Void> 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<Void> purgeMessagesInChannel(Integer count, TextChannel channel, Message origin, Member purgingRestriction) {
|
||||
return purgeMessagesInChannel(count, channel, origin.getIdLong(), purgingRestriction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<#include "purge_status_update_message">
|
||||
@@ -0,0 +1 @@
|
||||
<#include "no_message_found_exception_message">
|
||||
Reference in New Issue
Block a user