Skip to content

Commit

Permalink
Prevent concurrent moderation
Browse files Browse the repository at this point in the history
  • Loading branch information
Matyrobbrt committed Apr 11, 2024
1 parent 4f3943c commit 137ac72
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.google.common.base.Preconditions;
import com.jagrosh.jdautilities.command.SlashCommand;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
Expand All @@ -13,13 +16,13 @@
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.neoforged.camelot.BotMain;
import net.neoforged.camelot.Database;
import net.neoforged.camelot.db.schemas.ModLogEntry;
import net.neoforged.camelot.db.transactionals.ModLogsDAO;
import net.neoforged.camelot.log.ModerationActionRecorder;
import net.neoforged.camelot.util.Utils;
import org.jetbrains.annotations.Nullable;
import net.neoforged.camelot.BotMain;
import net.neoforged.camelot.db.schemas.ModLogEntry;
import net.neoforged.camelot.log.ModerationActionRecorder;

import javax.annotation.ParametersAreNullableByDefault;
import java.util.concurrent.CompletableFuture;
Expand All @@ -31,6 +34,11 @@
*/
public abstract class ModerationCommand<T> extends SlashCommand {

/**
* The list of user having in-progress actions against them.
*/
public static final LongSet IN_PROGRESS = LongSets.synchronize(new LongArraySet());

protected ModerationCommand() {
this.guildOnly = true;
}
Expand All @@ -50,6 +58,18 @@ protected ModerationCommand() {
@Nullable
protected abstract ModerationAction<T> createEntry(SlashCommandEvent event);

/**
* Checks if the given {@code action} can be executed.
* <p>
* If impossible, it is up to the implementor to reply.
*
* @param action the moderation action
* @return a CF returning the result of this action.
*/
protected CompletableFuture<Boolean> canExecute(SlashCommandEvent event, ModerationAction<T> action) {
return CompletableFuture.completedFuture(true);
}

@Override
protected final void execute(SlashCommandEvent event) {
final ModerationAction<T> action;
Expand All @@ -63,26 +83,39 @@ protected final void execute(SlashCommandEvent event) {

if (action == null) return;
final ModLogEntry entry = action.entry;
if (!IN_PROGRESS.add(entry.user())) {
event.reply("User is already being moderated. Please wait...").setEphemeral(true).queue();
return;
}

entry.setId(Database.main().withExtension(ModLogsDAO.class, dao -> dao.insert(entry)));
event.deferReply().queue();
event.getJDA().retrieveUserById(entry.user())
.submit()
.thenCompose(usr -> {
if (shouldDMUser) {
return dmUser(entry, usr).submit();
}
return CompletableFuture.completedFuture(null);
})
.whenComplete((msg, t) -> {
if (t == null) {
logAndExecute(action, event.getHook(), true);
} else {
logAndExecute(action, event.getHook(), false);
if (t instanceof ErrorResponseException ex && ex.getErrorResponse() != ErrorResponse.CANNOT_SEND_TO_USER) {
BotMain.LOGGER.error("Encountered exception DMing user {}: ", entry.user(), ex);
}
canExecute(event, action)
.thenAccept(pos -> {
if (!pos) {
IN_PROGRESS.remove(entry.user());
return;
}

entry.setId(Database.main().withExtension(ModLogsDAO.class, dao -> dao.insert(entry)));
event.getJDA().retrieveUserById(entry.user())
.submit()
.thenCompose(usr -> {
if (shouldDMUser) {
return dmUser(entry, usr).submit();
}
return CompletableFuture.completedFuture(null);
})
.whenComplete((_, t) -> {
if (t == null) {
logAndExecute(action, event.getHook(), true);
} else {
logAndExecute(action, event.getHook(), false);
if (t instanceof ErrorResponseException ex && ex.getErrorResponse() != ErrorResponse.CANNOT_SEND_TO_USER) {
BotMain.LOGGER.error("Encountered exception DMing user {}: ", entry.user(), ex);
}
}
IN_PROGRESS.remove(entry.user());
});
});
}

Expand Down Expand Up @@ -167,7 +200,10 @@ protected void logAndExecute(ModerationAction<T> action, InteractionHook interac
}
return handle.flatMap(_ -> edit);
})
.queue();
.queue(_ -> IN_PROGRESS.remove(action.entry().user()), err -> {
IN_PROGRESS.remove(action.entry().user());
RestAction.getDefaultFailure().accept(err);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.requests.RestAction;
import org.jetbrains.annotations.Nullable;
import net.neoforged.camelot.db.schemas.ModLogEntry;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
* The command used to unban a user.
Expand Down Expand Up @@ -42,6 +43,15 @@ protected ModerationAction<Void> createEntry(SlashCommandEvent event) {
);
}

@Override
protected CompletableFuture<Boolean> canExecute(SlashCommandEvent event, ModerationAction<Void> action) {
return event.getGuild().retrieveBan(UserSnowflake.fromId(action.entry().user()))
.submit()
.thenApply(_ -> true)
.exceptionallyCompose(_ -> event.getHook().editOriginal("User is not banned.")
.submit().thenApply(_ -> false));
}

@Override
@SuppressWarnings("DataFlowIssue")
protected RestAction<?> handle(User user, ModerationAction<Void> action) {
Expand Down

0 comments on commit 137ac72

Please sign in to comment.