Skip to content

Commit 74e1c73

Browse files
authored
Merge pull request #493 from danthe1st/fix-purge
fix purges deleting too much, purge cancellation
2 parents 85d8f7e + d1848e2 commit 74e1c73

File tree

1 file changed

+71
-22
lines changed

1 file changed

+71
-22
lines changed

src/main/java/net/discordjug/javabot/systems/moderation/PurgeCommand.java

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@
2929
import java.time.LocalDateTime;
3030
import java.time.OffsetDateTime;
3131
import java.time.format.DateTimeFormatter;
32+
import java.util.Collections;
33+
import java.util.HashMap;
34+
import java.util.HashSet;
3235
import java.util.List;
3336
import java.util.Map;
3437
import java.util.Map.Entry;
38+
import java.util.Set;
39+
import java.util.concurrent.CompletableFuture;
3540
import java.util.concurrent.ExecutorService;
41+
import java.util.concurrent.atomic.AtomicBoolean;
3642
import java.util.stream.Collectors;
3743

3844
/**
@@ -42,6 +48,8 @@
4248
public class PurgeCommand extends ModerateCommand {
4349
private static final Path ARCHIVE_DIR = Path.of("purgeArchives");
4450
private final ExecutorService asyncPool;
51+
52+
private final Map<Long, Set<RunningPurge>> currentPurges = Collections.synchronizedMap(new HashMap<>());
4553

4654
/**
4755
* The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}.
@@ -51,8 +59,8 @@ public class PurgeCommand extends ModerateCommand {
5159
public PurgeCommand(BotConfig botConfig, ExecutorService asyncPool) {
5260
super(botConfig);
5361
this.asyncPool = asyncPool;
54-
setModerationSlashCommandData(Commands.slash("purge", "Deletes messages from a channel.")
55-
.addOption(OptionType.INTEGER, "amount", "Number of messages to remove.", true)
62+
setModerationSlashCommandData(Commands.slash("purge", "Bulk-deletes messages from a channel. Use /purge 0 to stop all purges.")
63+
.addOption(OptionType.INTEGER, "amount", "Number of messages to remove. Set this to 0 in order to stop all running purges.", true)
5664
.addOption(OptionType.USER, "user", "The user whose messages to remove. If left blank, messages from any user are removed.", false)
5765
.addOption(OptionType.BOOLEAN, "archive", "Whether the removed messages should be saved in an archive. This defaults to true, if left blank.", false)
5866
);
@@ -69,10 +77,34 @@ protected ReplyCallbackAction handleModerationCommand(@NotNull SlashCommandInter
6977
Long amount = (amountOption == null) ? 1 : amountOption.getAsLong();
7078
User user = (userOption == null) ? null : userOption.getAsUser();
7179
int maxAmount = config.getPurgeMaxMessageCount();
72-
if (amount == null || amount < 1 || amount > maxAmount) {
80+
if (amount == null || amount > maxAmount) {
7381
return Responses.warning(event, "Invalid amount. Should be between 1 and " + maxAmount + ", inclusive.");
7482
}
75-
asyncPool.submit(() -> this.purge(amount, user, event.getUser(), archive, event.getChannel(), config.getLogChannel()));
83+
if (amount == 0) {
84+
Set<RunningPurge> purges = currentPurges.get(event.getGuild().getIdLong());
85+
if (purges == null) {
86+
return Responses.warning(event, "Cannot stop purge as no purge is currently running.");
87+
} else {
88+
int count = 0;
89+
for (RunningPurge purge : purges) {
90+
if (purge.cancelled().compareAndSet(false, true)) {
91+
count++;
92+
}
93+
}
94+
return Responses.success(event, "Purge stopped", count + " purge(s) have been stopped.");
95+
}
96+
}
97+
RunningPurge runningPurge = new RunningPurge(event.getIdLong(), new AtomicBoolean());
98+
CompletableFuture<Void> future = CompletableFuture.runAsync(
99+
() -> this.purge(amount, user, event.getUser(), archive, event.getChannel(), config.getLogChannel(), runningPurge.cancelled()),
100+
asyncPool);
101+
currentPurges
102+
.computeIfAbsent(event.getGuild().getIdLong(), l -> Collections.synchronizedSet(new HashSet<>()))
103+
.add(runningPurge);
104+
future.whenComplete((success, failure) ->
105+
currentPurges.get(event.getGuild().getIdLong())
106+
.remove(runningPurge)
107+
);
76108
StringBuilder sb = new StringBuilder();
77109
sb.append(amount > 1 ? "Up to " + amount + " messages " : "1 message ");
78110
if (user != null) {
@@ -92,20 +124,42 @@ protected ReplyCallbackAction handleModerationCommand(@NotNull SlashCommandInter
92124
* @param archive Whether to create an archive file for the purge.
93125
* @param channel The channel to remove messages from.
94126
* @param logChannel The channel to write log messages to during the purge.
127+
* @param cancelled {@code true} indicates the purge is cancelled, else {@code false}
95128
*/
96-
private void purge(long amount, @Nullable User user, User initiatedBy, boolean archive, MessageChannel channel, TextChannel logChannel) {
129+
private void purge(long amount, @Nullable User user, User initiatedBy, boolean archive, MessageChannel channel, TextChannel logChannel, AtomicBoolean cancelled) {
97130
MessageHistory history = channel.getHistory();
98131
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
99132
String file = String.format("purge_%s_%s.txt", channel.getName(), timestamp);
100133
PrintWriter archiveWriter = archive ? createArchiveWriter(channel, logChannel, file) : null;
101-
List<Message> messages;
102134
OffsetDateTime startTime = OffsetDateTime.now();
103135
long count = 0;
104136
logChannel.sendMessageFormat("Starting purge of channel %s, initiated by %s", channel.getAsMention(), initiatedBy.getAsMention())
105137
.queue();
138+
count = performDeletion(amount, user, channel, logChannel, history, archiveWriter, count, cancelled);
139+
if (archiveWriter != null) {
140+
archiveWriter.close();
141+
}
142+
MessageCreateAction action = logChannel.sendMessage(String.format(
143+
"Purge of channel %s has completed. %d messages have been removed, and the purge took %s.",
144+
channel.getAsMention(),
145+
count,
146+
new TimeUtils().formatDurationToNow(startTime)
147+
));
148+
if (archive) {
149+
action.addFiles(FileUpload.fromData(ARCHIVE_DIR.resolve(file).toFile()));
150+
}
151+
action.queue();
152+
}
153+
154+
private long performDeletion(long amount, User user, MessageChannel channel, TextChannel logChannel,
155+
MessageHistory history, PrintWriter archiveWriter, long count, AtomicBoolean cancelled) {
106156
int lastEmptyIterations = 0;
157+
List<Message> messages;
107158
do {
108159
messages = history.retrievePast((int) Math.min(100, user==null ? amount : Math.max(amount, 10))).complete();
160+
if(cancelled.get()) {
161+
return count;
162+
}
109163
if (!messages.isEmpty()) {
110164
int messagesRemoved = removeMessages(messages, user, archiveWriter, amount - count);
111165
count += messagesRemoved;
@@ -114,27 +168,15 @@ private void purge(long amount, @Nullable User user, User initiatedBy, boolean a
114168
messagesRemoved,
115169
channel.getAsMention(),
116170
count
117-
)).queue();
171+
)).complete();
118172
if (messagesRemoved == 0) {
119173
lastEmptyIterations++;
120174
}else {
121175
lastEmptyIterations = 0;
122176
}
123177
}
124-
} while (!messages.isEmpty() && amount > count && lastEmptyIterations <= 20);
125-
if (archiveWriter != null) {
126-
archiveWriter.close();
127-
}
128-
MessageCreateAction action = logChannel.sendMessage(String.format(
129-
"Purge of channel %s has completed. %d messages have been removed, and the purge took %s.",
130-
channel.getAsMention(),
131-
count,
132-
new TimeUtils().formatDurationToNow(startTime)
133-
));
134-
if (archive) {
135-
action.addFiles(FileUpload.fromData(ARCHIVE_DIR.resolve(file).toFile()));
136-
}
137-
action.queue();
178+
} while (!cancelled.get() && !messages.isEmpty() && amount > count && lastEmptyIterations <= 20);
179+
return count;
138180
}
139181

140182
/**
@@ -162,7 +204,7 @@ private int removeMessages(List<Message> messages, @Nullable User user, @Nullabl
162204
for (Message msg : msgs) {
163205
archiveMessage(archiveWriter, msg);
164206
}
165-
entry.getKey().purgeMessages(messages);
207+
entry.getKey().purgeMessages(msgs);
166208
}
167209
}
168210
return count;
@@ -208,4 +250,11 @@ private void archiveMessage(PrintWriter writer, Message message) {
208250
message.getContentRaw()
209251
);
210252
}
253+
254+
private record RunningPurge(long id, AtomicBoolean cancelled) {
255+
@Override
256+
public final int hashCode() {
257+
return (int) id;
258+
}
259+
}
211260
}

0 commit comments

Comments
 (0)