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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;

import org.togetherjava.tjbot.commands.MessageReceiverAdapter;
Expand All @@ -20,11 +21,11 @@
public final class TopHelpersMessageListener extends MessageReceiverAdapter {
/**
* Matches invisible control characters and unused code points
*
*
* @see <a href="https://www.regular-expressions.info/unicode.html#category">Unicode
* Categories</a>
*/
private static final Pattern UNCOUNTED_CHARS = Pattern.compile("\\p{C}");
private static final Pattern INVALID_CHARACTERS = Pattern.compile("\\p{C}");

private final Database database;

Expand All @@ -50,31 +51,15 @@ public TopHelpersMessageListener(Database database, Config config) {

@Override
public void onMessageReceived(MessageReceivedEvent event) {
if (event.getAuthor().isBot() || event.isWebhookMessage()) {
return;
}

if (!isHelpThread(event)) {
if (shouldIgnoreMessage(event)) {
return;
}

addMessageRecord(event);
}

private boolean isHelpThread(MessageReceivedEvent event) {
if (event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) {
return false;
}

ThreadChannel thread = event.getChannel().asThreadChannel();
String rootChannelName = thread.getParentChannel().getName();
return isStagingChannelName.test(rootChannelName)
|| isOverviewChannelName.test(rootChannelName);
}

private void addMessageRecord(MessageReceivedEvent event) {
String messageContent = event.getMessage().getContentRaw();
long messageLength = UNCOUNTED_CHARS.matcher(messageContent).replaceAll("").length();
long messageLength = countValidCharacters(event.getMessage().getContentRaw());

database.write(context -> context.newRecord(HELP_CHANNEL_MESSAGES)
.setMessageId(event.getMessage().getIdLong())
Expand All @@ -85,4 +70,25 @@ private void addMessageRecord(MessageReceivedEvent event) {
.setMessageLength(messageLength)
.insert());
}

boolean shouldIgnoreMessage(MessageReceivedEvent event) {
return event.getAuthor().isBot() || event.isWebhookMessage()
|| !isHelpThread(event.getChannel());
}

boolean isHelpThread(MessageChannelUnion channel) {
if (channel.getType() != ChannelType.GUILD_PUBLIC_THREAD) {
return false;
}

ThreadChannel thread = channel.asThreadChannel();
String rootChannelName = thread.getParentChannel().getName();
return isStagingChannelName.test(rootChannelName)
|| isOverviewChannelName.test(rootChannelName);
}

static long countValidCharacters(String messageContent) {
return INVALID_CHARACTERS.matcher(messageContent).replaceAll("").length();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
Expand Down Expand Up @@ -102,7 +103,8 @@ private MessageReceivedEvent sendMessage(MessageCreateData message) {

private MessageReceivedEvent sendMessage(MessageCreateData message,
List<Message.Attachment> attachments) {
MessageReceivedEvent event = jdaTester.createMessageReceiveEvent(message, attachments);
MessageReceivedEvent event =
jdaTester.createMessageReceiveEvent(message, attachments, ChannelType.TEXT);
mediaOnlyChannelListener.onMessageReceived(event);
return event;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package org.togetherjava.tjbot.commands.tophelper;

import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.config.HelpSystemConfig;
import org.togetherjava.tjbot.db.Database;
import org.togetherjava.tjbot.jda.JdaTester;

import java.util.List;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.togetherjava.tjbot.db.generated.tables.HelpChannelMessages.HELP_CHANNEL_MESSAGES;

final class TopHelperMessageListenerTest {

private static final String STAGING_CHANNEL_PATTERN = "ask_here";
private static final String OVERVIEW_CHANNEL_PATTERN = "active_questions";

private static JdaTester jdaTester;
private static TopHelpersMessageListener topHelpersListener;

@BeforeAll
static void setUp() {
Database database = Database.createMemoryDatabase(HELP_CHANNEL_MESSAGES);
Config config = mock(Config.class);
HelpSystemConfig helpSystemConfig = mock(HelpSystemConfig.class);

when(helpSystemConfig.getStagingChannelPattern()).thenReturn(STAGING_CHANNEL_PATTERN);
when(helpSystemConfig.getOverviewChannelPattern()).thenReturn(OVERVIEW_CHANNEL_PATTERN);

when(config.getHelpSystem()).thenReturn(helpSystemConfig);

jdaTester = new JdaTester();
topHelpersListener = new TopHelpersMessageListener(database, config);
}

@Test
void recognizesValidMessages() {
// GIVEN a message by a human in a help channel
MessageReceivedEvent event =
createMessageReceivedEvent(false, false, true, OVERVIEW_CHANNEL_PATTERN);

// WHEN checking if the message should be ignored
boolean shouldBeIgnored = topHelpersListener.shouldIgnoreMessage(event);

// THEN the message is not ignored
assertFalse(shouldBeIgnored);
}

@Test
void ignoresBots() {
// GIVEN a message from a bot
MessageReceivedEvent event =
createMessageReceivedEvent(true, false, true, OVERVIEW_CHANNEL_PATTERN);

// WHEN checking if the message should be ignored
boolean shouldBeIgnored = topHelpersListener.shouldIgnoreMessage(event);

// THEN the message is ignored
assertTrue(shouldBeIgnored);
}

@Test
void ignoresWebhooks() {
// GIVEN a message from a webhook
MessageReceivedEvent event =
createMessageReceivedEvent(false, true, true, OVERVIEW_CHANNEL_PATTERN);

// WHEN checking if the message should be ignored
boolean shouldBeIgnored = topHelpersListener.shouldIgnoreMessage(event);

// THEN the message is ignored
assertTrue(shouldBeIgnored);
}

@Test
void ignoresWrongChannels() {
// GIVEN a message outside a help thread
MessageReceivedEvent eventNotAThread =
createMessageReceivedEvent(false, false, false, OVERVIEW_CHANNEL_PATTERN);
MessageReceivedEvent eventWrongParentName =
createMessageReceivedEvent(false, false, true, "memes");

// WHEN checking if the message should be ignored
boolean ignoresNonThreadChannels = topHelpersListener.shouldIgnoreMessage(eventNotAThread);
boolean ignoresWrongParentNames =
topHelpersListener.shouldIgnoreMessage(eventWrongParentName);

// THEN the message is ignored
assertTrue(ignoresNonThreadChannels, "Failed to ignore non-thread channels");
assertTrue(ignoresWrongParentNames, "Failed to ignore wrong parent channel names");
}


MessageReceivedEvent createMessageReceivedEvent(boolean isBot, boolean isWebhook,
boolean isThread, String parentChannelName) {
try (MessageCreateData message = new MessageCreateBuilder().setContent("Any").build()) {
MessageReceivedEvent event = jdaTester.createMessageReceiveEvent(message, List.of(),
isThread ? ChannelType.GUILD_PUBLIC_THREAD : ChannelType.TEXT);

when(jdaTester.getMemberSpy().getUser().isBot()).thenReturn(isBot);
when(event.getMessage().isWebhookMessage()).thenReturn(isWebhook);
when(jdaTester.getTextChannelSpy().getName()).thenReturn(parentChannelName);

return event;
}
}


@ParameterizedTest
@MethodSource("provideInvalidCharactersWithDescription")
void excludesInvalidCharacters(String invalidChars, String description) {
// GIVEN a string of invalid characters

// WHEN counting the amount of valid characters
long validCharacterCount = TopHelpersMessageListener.countValidCharacters(invalidChars);

// THEN no characters are counted
assertEquals(0, validCharacterCount,
"Characters [%s] were not fully ignored".formatted(description));
}


@ParameterizedTest
@MethodSource("provideValidCharacters")
void countsValidCharacters(String validChars) {
// GIVEN a string of valid characters

// WHEN counting the amount of valid characters
long validCharCount = TopHelpersMessageListener.countValidCharacters(validChars);

// THEN all characters are counted
assertEquals(validChars.length(), validCharCount,
"Characters [%s] were not fully ignored".formatted(validChars));
}


private static Stream<Arguments> provideInvalidCharactersWithDescription() {
return Stream.of( // Invalid characters
Arguments.of("\u061C", "Arabic Letter Mark"),
Arguments.of("\u0600", "Arabic Number Sign"),
Arguments.of("\u180E", "Mongolian Vowel Separator"),
Arguments.of("\u200B", "Zero Width Space"),
Arguments.of("\u200C", "Zero Width Non-Joiner"),
Arguments.of("\u200D", "Zero Width Joiner"),
Arguments.of("\u200E", "Left-to-Right Mark"),
Arguments.of("\u200F", "Right-to-Left Mark"));
}


private static List<String> provideValidCharacters() {
return List.of( // Valid characters
"a", "A", "b", "B", "c", "C", "x", "X,", "y", "Y", "z", "Z", // Latin alphabet
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", // Numbers
"°", "!", "§", "§", "$", "%", "&", "/", "(", ")", "{", "}", "[", "]", "=", // Other
"+", "*", "~", "-", "_", ".", ",", "?", ":", ";", "|", "<", ">", "@", "€", "µ", // Other
"α", "Α", "β", "Β", "γ", "Γ", "χ", "Χ", "ψ", "Ψ", "ω", "Ω", // Greek alphabet
"ä", "ö", "ü", "ß", // German
"á", "è", "î", // French
"天", "四", "永", // Chinese
"😀", "😛", "❤️", "💚", "⛔" // Emojis
);
}

}
Loading