Skip to content

Commit fb23042

Browse files
committed
fix purges deleting too much, purge cancellation
1 parent e59239f commit fb23042

File tree

1 file changed

+67
-22
lines changed

1 file changed

+67
-22
lines changed

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

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@
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;
3234
import java.util.List;
3335
import java.util.Map;
3436
import java.util.Map.Entry;
37+
import java.util.concurrent.CompletableFuture;
38+
import java.util.concurrent.CopyOnWriteArrayList;
3539
import java.util.concurrent.ExecutorService;
40+
import java.util.concurrent.atomic.AtomicBoolean;
3641
import java.util.stream.Collectors;
3742

3843
/**
@@ -42,6 +47,8 @@
4247
public class PurgeCommand extends ModerateCommand {
4348
private static final Path ARCHIVE_DIR = Path.of("purgeArchives");
4449
private final ExecutorService asyncPool;
50+
51+
private final Map<Long, List<RunningPurge>> currentPurges = Collections.synchronizedMap(new HashMap<>());
4552

4653
/**
4754
* The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}.
@@ -51,8 +58,8 @@ public class PurgeCommand extends ModerateCommand {
5158
public PurgeCommand(BotConfig botConfig, ExecutorService asyncPool) {
5259
super(botConfig);
5360
this.asyncPool = asyncPool;
54-
setModerationSlashCommandData(Commands.slash("purge", "Deletes messages from a channel.")
55-
.addOption(OptionType.INTEGER, "amount", "Number of messages to remove.", true)
61+
setModerationSlashCommandData(Commands.slash("purge", "Bulk-deletes messages from a channel. Use /purge 0 to stop all purges.")
62+
.addOption(OptionType.INTEGER, "amount", "Number of messages to remove. Set this to 0 in order to stop all running purges.", true)
5663
.addOption(OptionType.USER, "user", "The user whose messages to remove. If left blank, messages from any user are removed.", false)
5764
.addOption(OptionType.BOOLEAN, "archive", "Whether the removed messages should be saved in an archive. This defaults to true, if left blank.", false)
5865
);
@@ -69,10 +76,34 @@ protected ReplyCallbackAction handleModerationCommand(@NotNull SlashCommandInter
6976
Long amount = (amountOption == null) ? 1 : amountOption.getAsLong();
7077
User user = (userOption == null) ? null : userOption.getAsUser();
7178
int maxAmount = config.getPurgeMaxMessageCount();
72-
if (amount == null || amount < 1 || amount > maxAmount) {
79+
if (amount == null || amount > maxAmount) {
7380
return Responses.warning(event, "Invalid amount. Should be between 1 and " + maxAmount + ", inclusive.");
7481
}
75-
asyncPool.submit(() -> this.purge(amount, user, event.getUser(), archive, event.getChannel(), config.getLogChannel()));
82+
if (amount == 0) {
83+
List<RunningPurge> purges = currentPurges.get(event.getGuild().getIdLong());
84+
if (purges == null) {
85+
return Responses.warning(event, "Cannot stop purge as no purge is currently running.");
86+
} else {
87+
int count = 0;
88+
for (RunningPurge purge : purges) {
89+
if (purge.cancelled().compareAndSet(false, true)) {
90+
count++;
91+
}
92+
}
93+
return Responses.success(event, "Purge stopped", count + " purge(s) have been stopped.");
94+
}
95+
}
96+
RunningPurge runningPurge = new RunningPurge(event.getIdLong(), new AtomicBoolean());
97+
CompletableFuture<Void> future = CompletableFuture.runAsync(
98+
() -> this.purge(amount, user, event.getUser(), archive, event.getChannel(), config.getLogChannel(), runningPurge.cancelled()),
99+
asyncPool);
100+
currentPurges
101+
.computeIfAbsent(event.getGuild().getIdLong(), l -> new CopyOnWriteArrayList<>())
102+
.add(runningPurge);
103+
future.whenComplete((success, failure) ->
104+
currentPurges.get(event.getGuild().getIdLong())
105+
.remove(runningPurge)
106+
);
76107
StringBuilder sb = new StringBuilder();
77108
sb.append(amount > 1 ? "Up to " + amount + " messages " : "1 message ");
78109
if (user != null) {
@@ -92,20 +123,42 @@ protected ReplyCallbackAction handleModerationCommand(@NotNull SlashCommandInter
92123
* @param archive Whether to create an archive file for the purge.
93124
* @param channel The channel to remove messages from.
94125
* @param logChannel The channel to write log messages to during the purge.
126+
* @param cancelled {@code true} indicates the purge is cancelled, else {@code false}
95127
*/
96-
private void purge(long amount, @Nullable User user, User initiatedBy, boolean archive, MessageChannel channel, TextChannel logChannel) {
128+
private void purge(long amount, @Nullable User user, User initiatedBy, boolean archive, MessageChannel channel, TextChannel logChannel, AtomicBoolean cancelled) {
97129
MessageHistory history = channel.getHistory();
98130
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
99131
String file = String.format("purge_%s_%s.txt", channel.getName(), timestamp);
100132
PrintWriter archiveWriter = archive ? createArchiveWriter(channel, logChannel, file) : null;
101-
List<Message> messages;
102133
OffsetDateTime startTime = OffsetDateTime.now();
103134
long count = 0;
104135
logChannel.sendMessageFormat("Starting purge of channel %s, initiated by %s", channel.getAsMention(), initiatedBy.getAsMention())
105136
.queue();
137+
count = performDeletion(amount, user, channel, logChannel, history, archiveWriter, count, cancelled);
138+
if (archiveWriter != null) {
139+
archiveWriter.close();
140+
}
141+
MessageCreateAction action = logChannel.sendMessage(String.format(
142+
"Purge of channel %s has completed. %d messages have been removed, and the purge took %s.",
143+
channel.getAsMention(),
144+
count,
145+
new TimeUtils().formatDurationToNow(startTime)
146+
));
147+
if (archive) {
148+
action.addFiles(FileUpload.fromData(ARCHIVE_DIR.resolve(file).toFile()));
149+
}
150+
action.queue();
151+
}
152+
153+
private long performDeletion(long amount, User user, MessageChannel channel, TextChannel logChannel,
154+
MessageHistory history, PrintWriter archiveWriter, long count, AtomicBoolean cancelled) {
106155
int lastEmptyIterations = 0;
156+
List<Message> messages;
107157
do {
108158
messages = history.retrievePast((int) Math.min(100, user==null ? amount : Math.max(amount, 10))).complete();
159+
if(cancelled.get()) {
160+
return count;
161+
}
109162
if (!messages.isEmpty()) {
110163
int messagesRemoved = removeMessages(messages, user, archiveWriter, amount - count);
111164
count += messagesRemoved;
@@ -114,27 +167,15 @@ private void purge(long amount, @Nullable User user, User initiatedBy, boolean a
114167
messagesRemoved,
115168
channel.getAsMention(),
116169
count
117-
)).queue();
170+
)).complete();
118171
if (messagesRemoved == 0) {
119172
lastEmptyIterations++;
120173
}else {
121174
lastEmptyIterations = 0;
122175
}
123176
}
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();
177+
} while (!cancelled.get() && !messages.isEmpty() && amount > count && lastEmptyIterations <= 20);
178+
return count;
138179
}
139180

140181
/**
@@ -162,7 +203,7 @@ private int removeMessages(List<Message> messages, @Nullable User user, @Nullabl
162203
for (Message msg : msgs) {
163204
archiveMessage(archiveWriter, msg);
164205
}
165-
entry.getKey().purgeMessages(messages);
206+
entry.getKey().purgeMessages(msgs);
166207
}
167208
}
168209
return count;
@@ -208,4 +249,8 @@ private void archiveMessage(PrintWriter writer, Message message) {
208249
message.getContentRaw()
209250
);
210251
}
252+
253+
private record RunningPurge(long id, AtomicBoolean cancelled) {
254+
255+
}
211256
}

0 commit comments

Comments
 (0)