Skip to content

Commit 07e0fe5

Browse files
authored
Scam Detector fix false positive (#1216)
* Added unit tests for false positives * added feature so sus keywords can require startsWith instead of contains ^foo instead of just foo * CR improvement
1 parent 1b4c13a commit 07e0fe5

File tree

3 files changed

+34
-6
lines changed

3 files changed

+34
-6
lines changed

application/config.json.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"steamcommunity",
4646
"freenitro",
4747
"usd",
48-
"earn",
48+
"^earn",
4949
".exe"
5050
],
5151
"hostWhitelist": [

application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetector.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,13 @@ private boolean containsSuspiciousKeyword(String token) {
114114
return config.getSuspiciousKeywords()
115115
.stream()
116116
.map(keyword -> keyword.toLowerCase(Locale.US))
117-
.anyMatch(preparedToken::contains);
117+
.anyMatch(keyword -> {
118+
// Simple regex-inspired syntax "^foo"
119+
if (startsWith(keyword, '^')) {
120+
return preparedToken.startsWith(keyword.substring(1));
121+
}
122+
return preparedToken.contains(keyword);
123+
});
118124
}
119125

120126
private boolean isHostSimilarToKeyword(String host, String keyword) {
@@ -140,6 +146,10 @@ private boolean isHostSimilarToKeyword(String host, String keyword) {
140146
return false;
141147
}
142148

149+
private static boolean startsWith(CharSequence text, char prefixToTest) {
150+
return !text.isEmpty() && text.charAt(0) == prefixToTest;
151+
}
152+
143153
private static class AnalyseResults {
144154
private boolean pingsEveryone;
145155
private boolean containsSuspiciousKeyword;

application/src/test/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetectorTest.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ void setUp() {
2626
ScamBlockerConfig scamConfig = mock(ScamBlockerConfig.class);
2727
when(config.getScamBlocker()).thenReturn(scamConfig);
2828

29-
when(scamConfig.getSuspiciousKeywords())
30-
.thenReturn(Set.of("nitro", "boob", "sexy", "sexi", "esex", "steam", "gift", "onlyfans",
31-
"bitcoin", "btc", "promo", "trader", "trading", "whatsapp", "crypto", "claim",
32-
"teen", "adobe", "hack", "steamcommunity", "freenitro", "usd", "earn", ".exe"));
29+
when(scamConfig.getSuspiciousKeywords()).thenReturn(Set.of("nitro", "boob", "sexy", "sexi",
30+
"esex", "steam", "gift", "onlyfans", "bitcoin", "btc", "promo", "trader", "trading",
31+
"whatsapp", "crypto", "claim", "teen", "adobe", "hack", "steamcommunity",
32+
"freenitro", "usd", "^earn", ".exe"));
3333
when(scamConfig.getHostWhitelist()).thenReturn(Set.of("discord.com", "discord.media",
3434
"discordapp.com", "discordapp.net", "discordstatus.com"));
3535
when(scamConfig.getHostBlacklist()).thenReturn(Set.of("bit.ly", "discord.gg", "teletype.in",
@@ -54,6 +54,18 @@ void detectsRealScam(String scamMessage) {
5454
assertTrue(isScamResult);
5555
}
5656

57+
@ParameterizedTest
58+
@MethodSource("provideRealFalsePositiveMessages")
59+
@DisplayName("Can ignore real false positive messages")
60+
void ignoresFalsePositives(String falsePositiveMessage) {
61+
// GIVEN a real false positive message
62+
// WHEN analyzing it
63+
boolean isScamResult = scamDetector.isScam(falsePositiveMessage);
64+
65+
// THEN does not flag it as scam
66+
assertFalse(isScamResult);
67+
}
68+
5769
@Test
5870
@DisplayName("Can detect messages that contain blacklisted websites as scam")
5971
void detectsBlacklistedWebsite() {
@@ -227,4 +239,10 @@ private static List<String> provideRealScamMessages() {
227239
"Urgently looking for mods & collab managers https://discord.gg/cryptohireo",
228240
"Check this - https://transfer.sh/get/ajmkh3l7tzop/Setup.exe");
229241
}
242+
243+
private static List<String> provideRealFalsePositiveMessages() {
244+
return List
245+
.of("""
246+
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/anonymous-types""");
247+
}
230248
}

0 commit comments

Comments
 (0)