diff --git a/application/build.gradle b/application/build.gradle index be4f6d34a2..663428f86d 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -74,7 +74,7 @@ dependencies { implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1' - testImplementation 'org.mockito:mockito-core:4.9.0' + testImplementation 'org.mockito:mockito-core:4.10.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/bookmarks/LeftoverBookmarksListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/bookmarks/LeftoverBookmarksListener.java index b18f7ecb70..5252f6ea07 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/bookmarks/LeftoverBookmarksListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/bookmarks/LeftoverBookmarksListener.java @@ -3,7 +3,6 @@ import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.EventReceiver; @@ -25,14 +24,14 @@ public LeftoverBookmarksListener(BookmarksSystem bookmarksSystem) { } @Override - public void onGuildMemberRemove(@NotNull GuildMemberRemoveEvent event) { + public void onGuildMemberRemove(GuildMemberRemoveEvent event) { long userID = event.getUser().getIdLong(); bookmarksSystem.startDeletionPeriodForUser(userID); } @Override - public void onGuildMemberJoin(@NotNull GuildMemberJoinEvent event) { + public void onGuildMemberJoin(GuildMemberJoinEvent event) { long userID = event.getUser().getIdLong(); bookmarksSystem.cancelDeletionPeriodForUser(userID); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index df1c5c89c5..e66fea0997 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -32,9 +32,11 @@ import java.io.InputStream; import java.util.*; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Helper class offering certain methods used by the help system. @@ -48,6 +50,11 @@ public final class HelpSystemHelper { private final Predicate isHelpForumName; private final String helpForumPattern; + /** + * Compares categories by how common they are, ascending. I.e., the most uncommon or specific + * category comes first. + */ + private final Comparator byCategoryCommonnessAsc; private final Set categories; private final Set threadActivityTagNames; private final String categoryRoleSuffix; @@ -66,9 +73,18 @@ public HelpSystemHelper(Config config, Database database) { helpForumPattern = helpConfig.getHelpForumPattern(); isHelpForumName = Pattern.compile(helpForumPattern).asMatchPredicate(); - categories = new HashSet<>(helpConfig.getCategories()); + List categoriesList = helpConfig.getCategories(); + categories = new HashSet<>(categoriesList); categoryRoleSuffix = helpConfig.getCategoryRoleSuffix(); + Map categoryToCommonDesc = IntStream.range(0, categoriesList.size()) + .boxed() + .collect(Collectors.toMap(categoriesList::get, Function.identity())); + byCategoryCommonnessAsc = Comparator + .comparingInt( + tag -> categoryToCommonDesc.getOrDefault(tag.getName(), categories.size())) + .reversed(); + threadActivityTagNames = Arrays.stream(ThreadActivity.values()) .map(ThreadActivity::getTagName) .collect(Collectors.toSet()); @@ -97,10 +113,6 @@ private RestAction sendExplanationMessage(GuildMessageChannel threadCha List embeds = List.of(HelpSystemHelper.embedWith( "Code is much easier to read if posted with **syntax highlighting** and proper formatting.", useCodeSyntaxExampleImage ? "attachment://" + CODE_SYNTAX_EXAMPLE_PATH : null), - HelpSystemHelper.embedWith( - """ - If your code is **long**, or you have **multiple files** to share, consider posting it on sites \ - like https://pastebin.com/ and share the link instead, that is easier to browse for helpers."""), HelpSystemHelper.embedWith( """ If nobody is calling back, that usually means that your question was **not well asked** and \ @@ -171,12 +183,12 @@ Optional getActivityTagOfChannel(ThreadChannel channel) { return getFirstMatchingTagOfChannel(threadActivityTagNames, channel); } - private static Optional getFirstMatchingTagOfChannel(Set tagNamesToMatch, + private Optional getFirstMatchingTagOfChannel(Set tagNamesToMatch, ThreadChannel channel) { return channel.getAppliedTags() .stream() .filter(tag -> tagNamesToMatch.contains(tag.getName())) - .findFirst(); + .min(byCategoryCommonnessAsc); } RestAction changeChannelCategory(ThreadChannel channel, String category) { @@ -187,8 +199,8 @@ RestAction changeChannelActivity(ThreadChannel channel, ThreadActivity act return changeMatchingTagOfChannel(activity.getTagName(), threadActivityTagNames, channel); } - private static RestAction changeMatchingTagOfChannel(String tagName, - Set tagNamesToMatch, ThreadChannel channel) { + private RestAction changeMatchingTagOfChannel(String tagName, Set tagNamesToMatch, + ThreadChannel channel) { List tags = new ArrayList<>(channel.getAppliedTags()); Optional currentTag = getFirstMatchingTagOfChannel(tagNamesToMatch, channel); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadCreatedListener.java index e018d80edc..002e7df3bf 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadCreatedListener.java @@ -9,13 +9,15 @@ import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.requests.RestAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.EventReceiver; -import javax.annotation.Nonnull; - import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** @@ -25,6 +27,9 @@ * user. */ public final class HelpThreadCreatedListener extends ListenerAdapter implements EventReceiver { + private static final Logger logger = LoggerFactory.getLogger(HelpThreadCreatedListener.class); + private static final ScheduledExecutorService SERVICE = Executors.newScheduledThreadPool(2); + private final HelpSystemHelper helper; private final Cache threadIdToCreatedAtCache = Caffeine.newBuilder() .maximumSize(1_000) @@ -41,7 +46,7 @@ public HelpThreadCreatedListener(HelpSystemHelper helper) { } @Override - public void onChannelCreate(@Nonnull ChannelCreateEvent createEvent) { + public void onChannelCreate(ChannelCreateEvent createEvent) { if (!createEvent.getChannelType().isThread()) { return; } @@ -69,7 +74,20 @@ private boolean wasThreadAlreadyHandled(long threadChannelId) { private void handleHelpThreadCreated(ThreadChannel threadChannel) { helper.writeHelpThreadToDatabase(threadChannel.getOwnerIdLong(), threadChannel); - createMessages(threadChannel).queue(); + Runnable createMessages = () -> { + try { + createMessages(threadChannel).queue(); + } catch (Exception e) { + logger.error( + "Unknown error while creating messages after help-thread ({}) creation", + threadChannel.getId(), e); + } + }; + + // The creation is delayed, because otherwise it could be too fast and be executed + // after Discord created the thread, but before Discord send OPs initial message. + // Sending messages at that moment is not allowed. + SERVICE.schedule(createMessages, 5, TimeUnit.SECONDS); } private RestAction createMessages(ThreadChannel threadChannel) { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java index c957b34899..4278f5e1ac 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java @@ -1,6 +1,7 @@ /** - * This package offers all functionality for the help system. For example commands that let users - * ask questions, such as {@link org.togetherjava.tjbot.commands.help.AskCommand}. + * This package offers all functionality for the help system. For example commands that handles the + * actions taken after creating a help thread + * {@link org.togetherjava.tjbot.commands.help.HelpThreadCreatedListener}. */ @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault diff --git a/build.gradle b/build.gradle index 6132f0e837..9c45235c19 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' id "com.diffplug.spotless" version "6.12.0" id "org.sonarqube" version "3.5.0.2730" - id "name.remal.sonarlint" version "2.0.0" + id "name.remal.sonarlint" version "2.1.0" } group 'org.togetherjava'