[AB-264] fixing profanity filter not considering message edits

This commit is contained in:
Sheldan
2021-05-23 17:10:54 +02:00
parent 23af59ab9d
commit 9dc1d73507
5 changed files with 171 additions and 124 deletions

View File

@@ -1,107 +0,0 @@
package dev.sheldan.abstracto.profanityfilter.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener;
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.metric.service.MetricTag;
import dev.sheldan.abstracto.core.models.database.ProfanityRegex;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.ProfanityService;
import dev.sheldan.abstracto.core.service.RoleImmunityService;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterFeatureDefinition;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterMode;
import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Optional;
import static dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterService.PROFANITY_FILTER_EFFECT_KEY;
@Component
@Slf4j
public class ProfanityDetectionListener implements AsyncMessageReceivedListener {
@Autowired
private ProfanityService profanityService;
@Autowired
private ProfanityFilterService profanityFilterService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private MessageService messageService;
@Autowired
private MetricService metricService;
@Autowired
private RoleImmunityService roleImmunityService;
public static final String MODERATION_PURGE_METRIC = "profanity.filter";
public static final String STEP = "step";
private static final CounterMetric PROFANITIES_DETECTED_METRIC =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(STEP, "detection")))
.name(MODERATION_PURGE_METRIC)
.build();
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
Message message = model.getMessage();
if(message.isWebhookMessage() || message.getType().isSystem() || !message.isFromGuild()) {
return DefaultListenerResult.IGNORED;
}
if(roleImmunityService.isImmune(message.getMember(), PROFANITY_FILTER_EFFECT_KEY)) {
log.info("Not checking for profanities in message, because author {} in channel {} in guild {} is immune against profanity filter.",
message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong());
return DefaultListenerResult.IGNORED;
}
Long serverId = model.getServerId();
Optional<ProfanityRegex> potentialProfanityGroup = profanityService.getProfanityRegex(message.getContentRaw(), serverId);
if(potentialProfanityGroup.isPresent()) {
metricService.incrementCounter(PROFANITIES_DETECTED_METRIC);
if(featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, serverId, ProfanityFilterMode.PROFANITY_REPORT)) {
ProfanityRegex foundProfanityGroup = potentialProfanityGroup.get();
profanityFilterService.createProfanityReport(message, foundProfanityGroup).exceptionally(throwable -> {
log.error("Failed to report or persist profanities in server {} for message {} in channel {}.",
serverId, message.getChannel().getIdLong(), message.getIdLong(), throwable);
return null;
});
}
if(featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, serverId, ProfanityFilterMode.AUTO_DELETE_PROFANITIES)) {
messageService.deleteMessage(message).exceptionally(throwable -> {
log.error("Failed to delete profanity message with id {} in channel {} in server {}.",
message.getIdLong(), message.getChannel().getIdLong(), message.getGuild().getIdLong(), throwable);
return null;
});
}
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return ProfanityFilterFeatureDefinition.PROFANITY_FILTER;
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(PROFANITIES_DETECTED_METRIC, "Amount of profanities detected");
}
}

View File

@@ -0,0 +1,59 @@
package dev.sheldan.abstracto.profanityfilter.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener;
import dev.sheldan.abstracto.core.models.database.ProfanityRegex;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.service.ProfanityService;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterFeatureDefinition;
import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterService;
import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class ProfanityMessageReceivedListener implements AsyncMessageReceivedListener {
@Autowired
private ProfanityService profanityService;
@Autowired
private ProfanityFilterService profanityFilterService;
@Autowired
private ProfanityFilterServiceBean profanityFilterServiceBean;
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
Message message = model.getMessage();
if(message.isWebhookMessage() || message.getType().isSystem() || !message.isFromGuild()) {
return DefaultListenerResult.IGNORED;
}
if(profanityFilterService.isImmuneAgainstProfanityFilter(message.getMember())) {
log.debug("Not checking for profanities in message, because author {} in channel {} in guild {} is immune against profanity filter.",
message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong());
return DefaultListenerResult.IGNORED;
}
Long serverId = model.getServerId();
Optional<ProfanityRegex> potentialProfanityGroup = profanityService.getProfanityRegex(message.getContentRaw(), serverId);
if(potentialProfanityGroup.isPresent()) {
ProfanityRegex foundProfanityGroup = potentialProfanityGroup.get();
profanityFilterServiceBean.handleProfaneMessage(message, foundProfanityGroup);
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return ProfanityFilterFeatureDefinition.PROFANITY_FILTER;
}
}

View File

@@ -0,0 +1,59 @@
package dev.sheldan.abstracto.profanityfilter.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageUpdatedListener;
import dev.sheldan.abstracto.core.models.database.ProfanityRegex;
import dev.sheldan.abstracto.core.models.listener.MessageUpdatedModel;
import dev.sheldan.abstracto.core.service.ProfanityService;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterFeatureDefinition;
import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterService;
import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class ProfanityMessageUpdatedListener implements AsyncMessageUpdatedListener {
@Autowired
private ProfanityService profanityService;
@Autowired
private ProfanityFilterService profanityFilterService;
@Autowired
private ProfanityFilterServiceBean profanityFilterServiceBean;
@Override
public DefaultListenerResult execute(MessageUpdatedModel model) {
Message message = model.getAfter();
if(message.isWebhookMessage() || message.getType().isSystem() || !message.isFromGuild()) {
return DefaultListenerResult.IGNORED;
}
if(profanityFilterService.isImmuneAgainstProfanityFilter(message.getMember())) {
log.debug("Not checking for profanities in message, because author {} in channel {} in guild {} is immune against profanity filter.",
message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong());
return DefaultListenerResult.IGNORED;
}
Long serverId = model.getServerId();
Optional<ProfanityRegex> potentialProfanityGroup = profanityService.getProfanityRegex(message.getContentRaw(), serverId);
if(potentialProfanityGroup.isPresent()) {
ProfanityRegex foundProfanityGroup = potentialProfanityGroup.get();
profanityFilterServiceBean.handleProfaneMessage(message, foundProfanityGroup);
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return ProfanityFilterFeatureDefinition.PROFANITY_FILTER;
}
}

View File

@@ -6,10 +6,7 @@ import dev.sheldan.abstracto.core.metric.service.MetricTag;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.database.ProfanityRegex;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.ReactionService;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ProfanityRegexManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
@@ -18,13 +15,13 @@ import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterFeatureDefinition;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterMode;
import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterPostTarget;
import dev.sheldan.abstracto.profanityfilter.listener.ProfanityDetectionListener;
import dev.sheldan.abstracto.profanityfilter.model.database.ProfanityUse;
import dev.sheldan.abstracto.profanityfilter.model.database.ProfanityUserInAServer;
import dev.sheldan.abstracto.profanityfilter.model.template.ProfanityReportModel;
import dev.sheldan.abstracto.profanityfilter.service.management.ProfanityUseManagementService;
import dev.sheldan.abstracto.profanityfilter.service.management.ProfanityUserInServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -70,23 +67,37 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService {
@Autowired
private MetricService metricService;
@Autowired
private RoleImmunityService roleImmunityService;
@Autowired
private ProfanityFilterServiceBean self;
private static final String PROFANITY_REPORT_TEMPLATE_KEY = "profanityDetection_listener_report";
public static final String MODERATION_PURGE_METRIC = "profanity.filter";
public static final String STEP = "step";
private static final CounterMetric PROFANITIES_DETECTED_METRIC =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(STEP, "detection")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric PROFANITIES_AGREEMENT =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(ProfanityDetectionListener.STEP, "agreement")))
.name(ProfanityDetectionListener.MODERATION_PURGE_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(STEP, "agreement")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric PROFANITIES_DISAGREEMENT =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(ProfanityDetectionListener.STEP, "disagreement")))
.name(ProfanityDetectionListener.MODERATION_PURGE_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(STEP, "disagreement")))
.name(MODERATION_PURGE_METRIC)
.build();
@Override
@@ -202,9 +213,33 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService {
profanityUseManagementService.createProfanityUse(profaneMessageObj, reportMessageObj, profaneUser, profanityRegex.getGroup());
}
@Override
public boolean isImmuneAgainstProfanityFilter(Member member) {
return roleImmunityService.isImmune(member, PROFANITY_FILTER_EFFECT_KEY);
}
public void handleProfaneMessage(Message message, ProfanityRegex foundProfanityGroup) {
metricService.incrementCounter(PROFANITIES_DETECTED_METRIC);
if(featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, message.getGuild().getIdLong(), ProfanityFilterMode.PROFANITY_REPORT)) {
createProfanityReport(message, foundProfanityGroup).exceptionally(throwable -> {
log.error("Failed to report or persist profanities in server {} for message {} in channel {}.",
message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong(), throwable);
return null;
});
}
if(featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, message.getGuild().getIdLong(), ProfanityFilterMode.AUTO_DELETE_PROFANITIES)) {
messageService.deleteMessage(message).exceptionally(throwable -> {
log.error("Failed to delete profanity message with id {} in channel {} in server {}.",
message.getIdLong(), message.getChannel().getIdLong(), message.getGuild().getIdLong(), throwable);
return null;
});
}
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(PROFANITIES_AGREEMENT, "Amount of profanity votes resulting in agreement");
metricService.registerCounter(PROFANITIES_DISAGREEMENT, "Amount of profanity votes resulting in disagreement");
metricService.registerCounter(PROFANITIES_DETECTED_METRIC, "Amount of profanities detected");
}
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.database.ProfanityRegex;
import dev.sheldan.abstracto.profanityfilter.model.database.ProfanityUse;
import dev.sheldan.abstracto.profanityfilter.model.database.ProfanityUserInAServer;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.util.List;
@@ -15,14 +16,7 @@ public interface ProfanityFilterService {
String PROFANITY_VOTES_CONFIG_KEY = "profanityVotes";
String REPORT_DISAGREE_EMOTE = "profanityFilterDisagreeEmote";
String PROFANITY_FILTER_EFFECT_KEY = "profanityFilter";
enum VoteResult {
AGREEMENT, DISAGREEMENT, BELOW_THRESHOLD;
public static boolean isFinal(VoteResult result) {
return result.equals(AGREEMENT) || result.equals(DISAGREEMENT);
}
}
boolean isImmuneAgainstProfanityFilter(Member member);
CompletableFuture<Void> createProfanityReport(Message message, ProfanityRegex profanityRegex);
boolean isMessageProfanityReport(Long messageId);
void verifyProfanityUse(ProfanityUse profanityUse, VoteResult result);
@@ -32,4 +26,11 @@ public interface ProfanityFilterService {
Long getFalseProfanityReportCountForUser(ProfanityUserInAServer aUserInAServer);
List<ServerChannelMessage> getRecentPositiveReports(AUserInAServer aUserInAServer, int count);
List<ServerChannelMessage> getRecentPositiveReports(ProfanityUserInAServer aUserInAServer, int count);
enum VoteResult {
AGREEMENT, DISAGREEMENT, BELOW_THRESHOLD;
public static boolean isFinal(VoteResult result) {
return result.equals(AGREEMENT) || result.equals(DISAGREEMENT);
}
}
}