Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"mode": "AUTO_DELETE_BUT_APPROVE_QUARANTINE",
"reportChannelPattern": "commands",
"botTrapChannelPattern": "bot-trap",
"trustedUserRolePattern": "Top Helpers .+|Moderator|Community Ambassador|Expert",
"suspiciousKeywords": [
"nitro",
"boob",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class ScamBlockerConfig {
private final Mode mode;
private final String reportChannelPattern;
private final String botTrapChannelPattern;
private final String trustedUserRolePattern;
private final Set<String> suspiciousKeywords;
private final Set<String> hostWhitelist;
private final Set<String> hostBlacklist;
Expand All @@ -32,6 +33,8 @@ private ScamBlockerConfig(@JsonProperty(value = "mode", required = true) Mode mo
required = true) String reportChannelPattern,
@JsonProperty(value = "botTrapChannelPattern",
required = true) String botTrapChannelPattern,
@JsonProperty(value = "trustedUserRolePattern",
required = true) String trustedUserRolePattern,
@JsonProperty(value = "suspiciousKeywords",
required = true) Set<String> suspiciousKeywords,
@JsonProperty(value = "hostWhitelist", required = true) Set<String> hostWhitelist,
Expand All @@ -47,6 +50,7 @@ private ScamBlockerConfig(@JsonProperty(value = "mode", required = true) Mode mo
this.mode = Objects.requireNonNull(mode);
this.reportChannelPattern = Objects.requireNonNull(reportChannelPattern);
this.botTrapChannelPattern = Objects.requireNonNull(botTrapChannelPattern);
this.trustedUserRolePattern = Objects.requireNonNull(trustedUserRolePattern);
this.suspiciousKeywords = new HashSet<>(Objects.requireNonNull(suspiciousKeywords));
this.hostWhitelist = new HashSet<>(Objects.requireNonNull(hostWhitelist));
this.hostBlacklist = new HashSet<>(Objects.requireNonNull(hostBlacklist));
Expand Down Expand Up @@ -86,6 +90,15 @@ public String getBotTrapChannelPattern() {
return botTrapChannelPattern;
}

/**
* Gets the REGEX pattern used to identify roles that will be ignored for scam detection.
*
* @return the REGEX pattern
*/
public String getTrustedUserRolePattern() {
return trustedUserRolePattern;
}

/**
* Gets the set of keywords that are considered suspicious if they appear in a message.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.togetherjava.tjbot.features.moderation.scam;

import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;

import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.config.ScamBlockerConfig;
Expand All @@ -24,6 +26,7 @@ public final class ScamDetector {
private static final Pattern TOKENIZER = Pattern.compile("[\\s,]");
private final ScamBlockerConfig config;
private final Predicate<String> isSuspiciousAttachmentName;
private final Predicate<String> hasTrustedRole;

/**
* Creates a new instance with the given configuration
Expand All @@ -32,9 +35,12 @@ public final class ScamDetector {
*/
public ScamDetector(Config config) {
this.config = config.getScamBlocker();

isSuspiciousAttachmentName =
Pattern.compile(config.getScamBlocker().getSuspiciousAttachmentNamePattern())
Pattern.compile(this.config.getSuspiciousAttachmentNamePattern())
.asMatchPredicate();
hasTrustedRole =
Pattern.compile(this.config.getTrustedUserRolePattern()).asMatchPredicate();
}

/**
Expand All @@ -44,6 +50,13 @@ public ScamDetector(Config config) {
* @return Whether the message classifies as scam
*/
public boolean isScam(Message message) {
Member author = message.getMember();
boolean isTrustedUser = author != null
&& author.getRoles().stream().map(Role::getName).noneMatch(hasTrustedRole);
if (isTrustedUser) {
return false;
}

String content = message.getContentDisplay();
List<Message.Attachment> attachments = message.getAttachments();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.togetherjava.tjbot.features.moderation.scam;

import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -49,6 +51,8 @@ void setUp() {
when(scamConfig.getSuspiciousAttachmentNamePattern())
.thenReturn(SUSPICIOUS_ATTACHMENT_NAME);

when(scamConfig.getTrustedUserRolePattern()).thenReturn("Moderator");

scamDetector = new ScamDetector(config);
}

Expand Down Expand Up @@ -204,6 +208,23 @@ void ignoresHarmlessAttachments() {
assertFalse(isScamResult);
}

@Test
@DisplayName("Suspicious messages send by trusted users are not flagged")
void ignoreTrustedUser() {
// GIVEN a scam message send by a trusted user
String content = "Checkout https://bit.ly/3IhcLiO to get your free nitro !";
Member trustedUser = createAuthorMock(List.of("Moderator"));
Message message = createMessageMock(content, List.of());

when(message.getMember()).thenReturn(trustedUser);

// WHEN analyzing it
boolean isScamResult = scamDetector.isScam(message);

// THEN flags it as harmless
assertTrue(isScamResult);
}

private static Message createMessageMock(String content, List<Message.Attachment> attachments) {
Message message = mock(Message.class);
when(message.getContentRaw()).thenReturn(content);
Expand All @@ -219,6 +240,19 @@ private static Message.Attachment createImageAttachmentMock(String name) {
return attachment;
}

private static Member createAuthorMock(List<String> roleNames) {
List<Role> roles = new ArrayList<>();
for (String roleName : roleNames) {
Role role = mock(Role.class);
when(role.getName()).thenReturn(roleName);
roles.add(role);
}

Member member = mock(Member.class);
when(member.getRoles()).thenReturn(roles);
return member;
}

private static List<String> provideRealScamMessages() {
return List.of("""
🤩bro steam gived nitro - https://nitro-ds.online/LfgUfMzqYyx12""",
Expand Down
Loading