diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..fcadb2cf97 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/application/build.gradle b/application/build.gradle index 4d88d97b39..9380b24de3 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -77,6 +77,9 @@ dependencies { implementation 'org.kohsuke:github-api:1.321' + implementation 'org.apache.commons:commons-text:1.11.0' + implementation 'com.apptasticsoftware:rssreader:3.6.0' + testImplementation 'org.mockito:mockito-core:5.11.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' diff --git a/application/config.json.template b/application/config.json.template index b584f10f6a..a1aec8f470 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -103,6 +103,17 @@ "special": [ ] }, - "memberCountCategoryPattern": "Info", - "selectRolesChannelPattern": "select-your-roles" + "selectRolesChannelPattern": "select-your-roles", + "rssConfig": { + "feeds": [ + { + "url": "https://wiki.openjdk.org/spaces/createrssfeed.action?types=page&types=comment&types=blogpost&types=mail&types=attachment&spaces=JDKUpdates&maxResults=15&title=%5BJDK+Updates%5D+All+Content+Feed&publicFeed=true", + "targetChannelPattern": "java-news-and-changes", + "dateFormatterPattern": "yyyy-MM-dd'T'HH:mm:ssX" + } + ], + "fallbackChannelPattern": "java-news-and-changes", + "pollIntervalInMinutes": 10 + }, + "memberCountCategoryPattern": "Info" } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index d14c6279d0..e819f8e7d1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; + /** * Configuration of the application. Create instances using {@link #load(Path)}. */ @@ -42,6 +43,7 @@ public final class Config { private final String sourceCodeBaseUrl; private final JShellConfig jshell; private final FeatureBlacklistConfig featureBlacklistConfig; + private final RSSFeedsConfig rssFeedsConfig; private final String selectRolesChannelPattern; private final String memberCountCategoryPattern; @@ -90,6 +92,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, required = true) String memberCountCategoryPattern, @JsonProperty(value = "featureBlacklist", required = true) FeatureBlacklistConfig featureBlacklistConfig, + @JsonProperty(value = "rssConfig", required = true) RSSFeedsConfig rssFeedsConfig, @JsonProperty(value = "selectRolesChannelPattern", required = true) String selectRolesChannelPattern) { this.token = Objects.requireNonNull(token); @@ -122,6 +125,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.sourceCodeBaseUrl = Objects.requireNonNull(sourceCodeBaseUrl); this.jshell = Objects.requireNonNull(jshell); this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig); + this.rssFeedsConfig = Objects.requireNonNull(rssFeedsConfig); this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern); } @@ -405,4 +409,13 @@ public String getSelectRolesChannelPattern() { public String getMemberCountCategoryPattern() { return memberCountCategoryPattern; } + + /** + * Gets the RSS feeds configuration. + * + * @return the RSS feeds configuration + */ + public RSSFeedsConfig getRSSFeedsConfig() { + return rssFeedsConfig; + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RSSFeed.java b/application/src/main/java/org/togetherjava/tjbot/config/RSSFeed.java new file mode 100644 index 0000000000..a9f68361a6 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/config/RSSFeed.java @@ -0,0 +1,30 @@ +package org.togetherjava.tjbot.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.annotation.Nullable; + +import java.util.Objects; + +/** + * Represents an RSS feed configuration. + */ +public record RSSFeed(@JsonProperty(value = "url", required = true) String url, + @JsonProperty(value = "targetChannelPattern") @Nullable String targetChannelPattern, + @JsonProperty(value = "dateFormatterPattern", + required = true) String dateFormatterPattern) { + + /** + * Constructs an RSSFeed object. + * + * @param url the URL of the RSS feed + * @param targetChannelPattern the target channel pattern + * @param dateFormatterPattern the date formatter pattern + * @throws NullPointerException if any of the parameters are null + */ + public RSSFeed { + Objects.requireNonNull(url); + Objects.requireNonNull(targetChannelPattern); + Objects.requireNonNull(dateFormatterPattern); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RSSFeedsConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/RSSFeedsConfig.java new file mode 100644 index 0000000000..1c3371f71a --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/config/RSSFeedsConfig.java @@ -0,0 +1,34 @@ +package org.togetherjava.tjbot.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Objects; + +/** + * Represents the configuration for an RSS feed, which includes the list of feeds to subscribe to, a + * pattern for identifying Java news channels, and the interval (in minutes) for polling the feeds. + * + * @param feeds The list of RSS feeds to subscribe to. + * @param fallbackChannelPattern The pattern used to identify the fallback text channel to use. + * @param pollIntervalInMinutes The interval (in minutes) for polling the RSS feeds for updates. + */ +public record RSSFeedsConfig(@JsonProperty(value = "feeds", required = true) List feeds, + @JsonProperty(value = "fallbackChannelPattern", + required = true) String fallbackChannelPattern, + @JsonProperty(value = "pollIntervalInMinutes", required = true) int pollIntervalInMinutes) { + + /** + * Constructs a new {@link RSSFeedsConfig}. + * + * @param feeds The list of RSS feeds to subscribe to. + * @param fallbackChannelPattern The pattern used to identify the fallback text channel to use. + * @param pollIntervalInMinutes The interval (in minutes) for polling the RSS feeds for updates. + * @throws NullPointerException if any of the parameters (feeds or fallbackChannelPattern) are + * null + */ + public RSSFeedsConfig { + Objects.requireNonNull(feeds); + Objects.requireNonNull(fallbackChannelPattern); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index ea6b908b90..569db4a881 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -23,13 +23,34 @@ import org.togetherjava.tjbot.features.filesharing.FileSharingMessageListener; import org.togetherjava.tjbot.features.github.GitHubCommand; import org.togetherjava.tjbot.features.github.GitHubReference; -import org.togetherjava.tjbot.features.help.*; +import org.togetherjava.tjbot.features.help.GuildLeaveCloseThreadListener; +import org.togetherjava.tjbot.features.help.HelpSystemHelper; +import org.togetherjava.tjbot.features.help.HelpThreadActivityUpdater; +import org.togetherjava.tjbot.features.help.HelpThreadAutoArchiver; +import org.togetherjava.tjbot.features.help.HelpThreadCommand; +import org.togetherjava.tjbot.features.help.HelpThreadCreatedListener; +import org.togetherjava.tjbot.features.help.HelpThreadMetadataPurger; +import org.togetherjava.tjbot.features.help.PinnedNotificationRemover; +import org.togetherjava.tjbot.features.javamail.RSSHandlerRoutine; import org.togetherjava.tjbot.features.jshell.JShellCommand; import org.togetherjava.tjbot.features.jshell.JShellEval; import org.togetherjava.tjbot.features.mathcommands.TeXCommand; import org.togetherjava.tjbot.features.mathcommands.wolframalpha.WolframAlphaCommand; import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener; -import org.togetherjava.tjbot.features.moderation.*; +import org.togetherjava.tjbot.features.moderation.BanCommand; +import org.togetherjava.tjbot.features.moderation.KickCommand; +import org.togetherjava.tjbot.features.moderation.ModerationActionsStore; +import org.togetherjava.tjbot.features.moderation.MuteCommand; +import org.togetherjava.tjbot.features.moderation.NoteCommand; +import org.togetherjava.tjbot.features.moderation.QuarantineCommand; +import org.togetherjava.tjbot.features.moderation.RejoinModerationRoleListener; +import org.togetherjava.tjbot.features.moderation.ReportCommand; +import org.togetherjava.tjbot.features.moderation.TransferQuestionCommand; +import org.togetherjava.tjbot.features.moderation.UnbanCommand; +import org.togetherjava.tjbot.features.moderation.UnmuteCommand; +import org.togetherjava.tjbot.features.moderation.UnquarantineCommand; +import org.togetherjava.tjbot.features.moderation.WarnCommand; +import org.togetherjava.tjbot.features.moderation.WhoIsCommand; import org.togetherjava.tjbot.features.moderation.attachment.BlacklistedAttachmentListener; import org.togetherjava.tjbot.features.moderation.audit.AuditCommand; import org.togetherjava.tjbot.features.moderation.audit.ModAuditLogRoutine; @@ -109,6 +130,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new HelpThreadAutoArchiver(helpSystemHelper)); features.add(new LeftoverBookmarksCleanupRoutine(bookmarksSystem)); features.add(new MemberCountDisplayRoutine(config)); + features.add(new RSSHandlerRoutine(config, database)); // Message receivers features.add(new TopHelpersMessageListener(database, config)); @@ -131,7 +153,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new HelpThreadCreatedListener(helpSystemHelper)); // Message context commands - features.add(new TransferQuestionCommand(config)); + features.add(new TransferQuestionCommand(config, chatGptService)); // User context commands diff --git a/application/src/main/java/org/togetherjava/tjbot/features/basic/RoleSelectCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/basic/RoleSelectCommand.java index 7cf0dd5b69..1b7c362e48 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/basic/RoleSelectCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/basic/RoleSelectCommand.java @@ -2,7 +2,12 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.IMentionable; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.RoleIcon; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; @@ -21,9 +26,12 @@ import org.togetherjava.tjbot.features.SlashCommandAdapter; import org.togetherjava.tjbot.features.componentids.Lifespan; -import java.awt.*; -import java.util.*; +import java.awt.Color; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/bookmarks/BookmarksListRemoveHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/bookmarks/BookmarksListRemoveHandler.java index 4832e1ef4f..6c6de7b5d5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/bookmarks/BookmarksListRemoveHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/bookmarks/BookmarksListRemoveHandler.java @@ -20,7 +20,11 @@ import org.togetherjava.tjbot.features.utils.MessageUtils; import java.awt.Color; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java index f59e1ef2d2..35a8c8da26 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java @@ -26,7 +26,11 @@ import javax.annotation.Nullable; import java.awt.Color; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/componentids/ComponentIdStore.java b/application/src/main/java/org/togetherjava/tjbot/features/componentids/ComponentIdStore.java index 4dbdc25ffe..7ada38e67d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/componentids/ComponentIdStore.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/componentids/ComponentIdStore.java @@ -18,8 +18,18 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubCommand.java index 2a6f2f1137..53a7eed187 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubCommand.java @@ -14,7 +14,10 @@ import java.io.UncheckedIOException; import java.time.Duration; import java.time.Instant; -import java.util.*; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Queue; import java.util.function.ToIntFunction; import java.util.regex.Matcher; import java.util.stream.Stream; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java b/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java index 5f9ae9a4bb..00a58ae82a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java @@ -8,14 +8,21 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import org.apache.commons.collections4.ListUtils; -import org.kohsuke.github.*; +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHIssueStateReason; +import org.kohsuke.github.GHLabel; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.features.MessageReceiverAdapter; -import java.awt.*; +import java.awt.Color; import java.io.FileNotFoundException; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java index f14342c28d..a2aff66bfb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java @@ -1,7 +1,12 @@ package org.togetherjava.tjbot.features.help; import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.SelfUser; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; @@ -23,7 +28,7 @@ import org.togetherjava.tjbot.features.chatgpt.ChatGptService; import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; -import java.awt.*; +import java.awt.Color; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 1dd6053562..4b0b6a317a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -8,7 +8,6 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.TimeUtil; import org.slf4j.Logger; @@ -70,58 +69,57 @@ private void autoArchiveForGuild(Guild guild) { logger.debug("Found {} active questions", activeThreads.size()); Instant archiveAfterMoment = computeArchiveAfterMoment(); - activeThreads - .forEach(activeThread -> autoArchiveForThread(activeThread, archiveAfterMoment)); + activeThreads.stream() + .filter(activeThread -> shouldBeArchived(activeThread, archiveAfterMoment)) + .forEach(this::autoArchiveForThread); } private Instant computeArchiveAfterMoment() { return Instant.now().minus(ARCHIVE_AFTER_INACTIVITY_OF); } - private void autoArchiveForThread(ThreadChannel threadChannel, Instant archiveAfterMoment) { - if (shouldBeArchived(threadChannel, archiveAfterMoment)) { - logger.debug("Auto archiving help thread {}", threadChannel.getId()); + private void autoArchiveForThread(ThreadChannel threadChannel) { + logger.debug("Auto archiving help thread {}", threadChannel.getId()); - String linkHowToAsk = "https://stackoverflow.com/help/how-to-ask"; + String linkHowToAsk = "https://stackoverflow.com/help/how-to-ask"; - MessageEmbed embed = new EmbedBuilder() - .setDescription( - """ - Your question has been closed due to inactivity. + MessageEmbed embed = new EmbedBuilder() + .setDescription( + """ + Your question has been closed due to inactivity. - If it was not resolved yet, feel free to just post a message below - to reopen it, or create a new thread. + If it was not resolved yet, feel free to just post a message below + to reopen it, or create a new thread. - Note that usually the reason for nobody calling back is that your - question may have been not well asked and hence no one felt confident - enough answering. + Note that usually the reason for nobody calling back is that your + question may have been not well asked and hence no one felt confident + enough answering. - When you reopen the thread, try to use your time to **improve the quality** - of the question by elaborating, providing **details**, context, all relevant code - snippets, any **errors** you are getting, concrete **examples** and perhaps also some - screenshots. Share your **attempt**, explain the **expected results** and compare - them to the current results. + When you reopen the thread, try to use your time to **improve the quality** + of the question by elaborating, providing **details**, context, all relevant code + snippets, any **errors** you are getting, concrete **examples** and perhaps also some + screenshots. Share your **attempt**, explain the **expected results** and compare + them to the current results. - Also try to make the information **easily accessible** by sharing code - or assignment descriptions directly on Discord, not behind a link or - PDF-file; provide some guidance for long code snippets and ensure - the **code is well formatted** and has syntax highlighting. Kindly read through - %s for more. + Also try to make the information **easily accessible** by sharing code + or assignment descriptions directly on Discord, not behind a link or + PDF-file; provide some guidance for long code snippets and ensure + the **code is well formatted** and has syntax highlighting. Kindly read through + %s for more. - With enough info, someone knows the answer for sure 👍""" - .formatted(linkHowToAsk)) - .setColor(HelpSystemHelper.AMBIENT_COLOR) - .build(); + With enough info, someone knows the answer for sure 👍""" + .formatted(linkHowToAsk)) + .setColor(HelpSystemHelper.AMBIENT_COLOR) + .build(); - handleArchiveFlow(threadChannel, embed); - } + handleArchiveFlow(threadChannel, embed); } - private static boolean shouldBeArchived(MessageChannel channel, Instant archiveAfterMoment) { + private static boolean shouldBeArchived(ThreadChannel channel, Instant archiveAfterMoment) { Instant lastActivity = TimeUtil.getTimeCreated(channel.getLatestMessageIdLong()).toInstant(); - return lastActivity.isBefore(archiveAfterMoment); + return !channel.isPinned() && lastActivity.isBefore(archiveAfterMoment); } private void handleArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) { diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCommand.java index a2b1a123a3..9f0fc220a1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCommand.java @@ -3,7 +3,10 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -20,7 +23,12 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Function; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index 3994107a6b..02a959e5e4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -60,9 +60,9 @@ public HelpThreadCreatedListener(HelpSystemHelper helper) { @Override public void onMessageReceived(@NotNull MessageReceivedEvent event) { if (event.isFromThread()) { - Channel parentChannel = event.getChannel().asThreadChannel().getParentChannel(); + ThreadChannel threadChannel = event.getChannel().asThreadChannel(); + Channel parentChannel = threadChannel.getParentChannel(); if (helper.isHelpForumName(parentChannel.getName())) { - ThreadChannel threadChannel = event.getChannel().asThreadChannel(); int messageCount = threadChannel.getMessageCount(); if (messageCount > 1 || wasThreadAlreadyHandled(threadChannel.getIdLong())) { return; @@ -84,8 +84,11 @@ private boolean wasThreadAlreadyHandled(long threadChannelId) { private void handleHelpThreadCreated(ThreadChannel threadChannel) { threadChannel.retrieveStartMessage().flatMap(message -> { registerThreadDataInDB(message, threadChannel); - return generateAutomatedResponse(threadChannel); - }).flatMap(message -> pinOriginalQuestion(threadChannel)).queue(); + return sendHelperHeadsUp(threadChannel) + .flatMap(any -> HelpThreadCreatedListener.isContextSufficient(message), + any -> createAIResponse(threadChannel, message)) + .flatMap(any -> pinOriginalQuestion(message)); + }).queue(); } private static User getMentionedAuthorByMessage(Message message) { @@ -96,12 +99,9 @@ private static boolean isPostedBySelfUser(Message message) { return message.getJDA().getSelfUser().equals(message.getAuthor()); } - private RestAction createAIResponse(ThreadChannel threadChannel) { - RestAction originalQuestion = - threadChannel.retrieveMessageById(threadChannel.getIdLong()); - return originalQuestion.flatMap(HelpThreadCreatedListener::isContextSufficient, - message -> helper.constructChatGptAttempt(threadChannel, getMessageContent(message), - componentIdInteractor)); + private RestAction createAIResponse(ThreadChannel threadChannel, Message message) { + return helper.constructChatGptAttempt(threadChannel, getMessageContent(message), + componentIdInteractor); } private static boolean isContextSufficient(Message message) { @@ -109,12 +109,8 @@ private static boolean isContextSufficient(Message message) { && !LinkDetection.containsLink(message.getContentRaw()); } - private RestAction pinOriginalQuestion(ThreadChannel threadChannel) { - return threadChannel.retrieveMessageById(threadChannel.getIdLong()).flatMap(Message::pin); - } - - private RestAction generateAutomatedResponse(ThreadChannel threadChannel) { - return sendHelperHeadsUp(threadChannel).flatMap(any -> createAIResponse(threadChannel)); + private RestAction pinOriginalQuestion(Message message) { + return message.pin(); } private RestAction sendHelperHeadsUp(ThreadChannel threadChannel) { @@ -172,7 +168,7 @@ public void onButtonClick(ButtonInteractionEvent event, List args) { ThreadChannel channel = event.getChannel().asThreadChannel(); Member interactionUser = Objects.requireNonNull(event.getMember()); - channel.retrieveMessageById(channel.getId()) + channel.retrieveStartMessage() .queue(forumPostMessage -> handleDismiss(interactionUser, channel, forumPostMessage, event, args)); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java b/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java new file mode 100644 index 0000000000..e114f53a61 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java @@ -0,0 +1,416 @@ +package org.togetherjava.tjbot.features.javamail; + +import com.apptasticsoftware.rssreader.Item; +import com.apptasticsoftware.rssreader.RssReader; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView; +import org.apache.commons.text.StringEscapeUtils; +import org.jetbrains.annotations.Nullable; +import org.jooq.tools.StringUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.togetherjava.tjbot.config.Config; +import org.togetherjava.tjbot.config.RSSFeed; +import org.togetherjava.tjbot.config.RSSFeedsConfig; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.db.generated.tables.records.RssFeedRecord; +import org.togetherjava.tjbot.features.Routine; + +import javax.annotation.Nonnull; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.togetherjava.tjbot.db.generated.tables.RssFeed.RSS_FEED; + +/** + * This class orchestrates the retrieval, organization, and distribution of RSS feed posts sourced + * from various channels, all of which can be easily configured via the {@code config.json} + *

+ * To include a new RSS feed, simply define an {@link RSSFeed} entry in the {@code "rssFeeds"} array + * within the configuration file, adhering to the format shown below: + * + *

+ * {@code
+ * {
+ *     "url": "https://example.com/feed",
+ *     "targetChannelPattern": "example",
+ *     "dateFormatterPattern": "EEE, dd MMM yyyy HH:mm:ss Z"
+ * }
+ * }
+ * 
+ * + * Where: + *
    + *
  • {@code url} represents the URL of the RSS feed.
  • + *
  • {@code targetChannelPattern} specifies the pattern to identify the target channel for the + * feed posts.
  • + *
  • {@code dateFormatterPattern} denotes the pattern for parsing the date and time information in + * the feed.
  • + *
+ */ +public final class RSSHandlerRoutine implements Routine { + + private static final Logger logger = LoggerFactory.getLogger(RSSHandlerRoutine.class); + private static final int MAX_CONTENTS = 1000; + private static final ZonedDateTime ZONED_TIME_MIN = + ZonedDateTime.of(LocalDateTime.MIN, ZoneId.systemDefault()); + private final RssReader rssReader; + private final RSSFeedsConfig config; + private final Predicate fallbackChannelPattern; + private final Map> targetChannelPatterns; + private final int interval; + private final Database database; + + /** + * Constructs an RSSHandlerRoutine with the provided configuration and database. + * + * @param config The configuration containing RSS feed details. + * @param database The database for storing RSS feed data. + */ + public RSSHandlerRoutine(Config config, Database database) { + this.config = config.getRSSFeedsConfig(); + this.interval = this.config.pollIntervalInMinutes(); + this.database = database; + this.fallbackChannelPattern = + Pattern.compile(this.config.fallbackChannelPattern()).asMatchPredicate(); + this.targetChannelPatterns = new HashMap<>(); + this.config.feeds().forEach(feed -> { + if (feed.targetChannelPattern() != null) { + Predicate predicate = + Pattern.compile(feed.targetChannelPattern()).asMatchPredicate(); + targetChannelPatterns.put(feed, predicate); + } + }); + this.rssReader = new RssReader(); + } + + @Override + public Schedule createSchedule() { + return new Schedule(ScheduleMode.FIXED_DELAY, 0, interval, TimeUnit.MINUTES); + } + + @Override + public void runRoutine(@Nonnull JDA jda) { + this.config.feeds().forEach(feed -> sendRSS(jda, feed)); + } + + /** + * Sends all the necessary posts from a given RSS feed. + *

+ * This handles fetching the latest posts from the given URL, checking which ones have already + * been posted by reading information from the database and updating the last posted date. + * + * @param jda The JDA instance. + * @param feedConfig The configuration object for the RSS feed. + */ + private void sendRSS(JDA jda, RSSFeed feedConfig) { + List textChannels = getTextChannelsFromFeed(jda, feedConfig); + if (textChannels.isEmpty()) { + logger.warn( + "Tried to send an RSS post, but neither a target channel nor a fallback channel was found."); + return; + } + + List rssItems = fetchRSSItemsFromURL(feedConfig.url()); + if (rssItems.isEmpty()) { + return; + } + + for (Item item : rssItems) { + if (!isValidDateFormat(item, feedConfig)) { + logger.warn("Could not find valid or matching date format for RSS feed {}", + feedConfig.url()); + return; + } + } + + final Optional> shouldItemBePosted = + prepareItemPostPredicate(feedConfig, rssItems); + if (shouldItemBePosted.isEmpty()) { + return; + } + rssItems.reversed() + .stream() + .filter(shouldItemBePosted.get()) + .forEachOrdered(item -> postItem(textChannels, item, feedConfig)); + } + + private Optional> prepareItemPostPredicate(RSSFeed feedConfig, + List rssItems) { + Optional rssFeedRecord = getRssFeedRecordFromDatabase(feedConfig); + Optional lastPostedDate = + getLatestPostDateFromItems(rssItems, feedConfig.dateFormatterPattern()); + + lastPostedDate.ifPresent( + date -> updateLastDateToDatabase(feedConfig, rssFeedRecord.orElse(null), date)); + + if (rssFeedRecord.isEmpty()) { + return Optional.empty(); + } + + Optional lastSavedDate = getLastSavedDateFromDatabaseRecord( + rssFeedRecord.orElseThrow(), feedConfig.dateFormatterPattern()); + + if (lastSavedDate.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(item -> { + ZonedDateTime itemPubDate = + getDateTimeFromItem(item, feedConfig.dateFormatterPattern()); + return itemPubDate.isAfter(lastSavedDate.orElseThrow()); + }); + } + + /** + * Retrieves an RSS feed record from the database based on the provided RSS feed configuration. + * + * @param feedConfig the RSS feed configuration to retrieve the record for + * @return an optional RSS feed record retrieved from the database + */ + private Optional getRssFeedRecordFromDatabase(RSSFeed feedConfig) { + return Optional.ofNullable(database.read(context -> context.selectFrom(RSS_FEED) + .where(RSS_FEED.URL.eq(feedConfig.url())) + .limit(1) + .fetchAny())); + } + + /** + * Retrieves the last saved date from the database record associated with the given RSS feed + * record. + * + * @param rssRecord an existing RSS feed record to retrieve the last saved date from + * @param dateFormatterPattern the pattern used to parse the date from the database record + * @return An {@link Optional} containing the last saved date if it could be retrieved and + * parsed successfully, otherwise an empty {@link Optional} + */ + private Optional getLastSavedDateFromDatabaseRecord(RssFeedRecord rssRecord, + String dateFormatterPattern) throws DateTimeParseException { + try { + ZonedDateTime savedDate = + getZonedDateTime(rssRecord.getLastDate(), dateFormatterPattern); + return Optional.of(savedDate); + } catch (DateTimeParseException e) { + return Optional.empty(); + } + } + + /** + * Retrieves the latest post date from the given list of items. + * + * @param items the list of items to retrieve the latest post date from + * @param dateFormatterPattern the pattern used to parse the date from the database record + * @return the latest post date as a {@link ZonedDateTime} object, or null if the list is empty + */ + private Optional getLatestPostDateFromItems(List items, + String dateFormatterPattern) { + return items.stream() + .map(item -> getDateTimeFromItem(item, dateFormatterPattern)) + .max(ZonedDateTime::compareTo); + } + + /** + * Posts an RSS item to a text channel. + * + * @param textChannels the text channels to which the item will be posted + * @param rssItem the RSS item to post + * @param feedConfig the RSS feed configuration + */ + private void postItem(List textChannels, Item rssItem, RSSFeed feedConfig) { + MessageEmbed embed = constructEmbedMessage(rssItem, feedConfig).build(); + textChannels.forEach(channel -> channel.sendMessageEmbeds(List.of(embed)).queue()); + } + + /** + * Updates the last posted date to the database for the specified RSS feed configuration. + *

+ * This will insert a new entry to the database if the provided {@link RssFeedRecord} is + * null. + * + * @param feedConfig the RSS feed configuration + * @param rssFeedRecord the record representing the RSS feed, can be null if not found in the + * database + * @param lastPostedDate the last posted date to be updated + * + * @throws DateTimeParseException if the date cannot be parsed + */ + private void updateLastDateToDatabase(RSSFeed feedConfig, @Nullable RssFeedRecord rssFeedRecord, + ZonedDateTime lastPostedDate) { + DateTimeFormatter dateTimeFormatter = + DateTimeFormatter.ofPattern(feedConfig.dateFormatterPattern()); + String lastDateStr = lastPostedDate.format(dateTimeFormatter); + + if (rssFeedRecord == null) { + database.write(context -> context.newRecord(RSS_FEED) + .setUrl(feedConfig.url()) + .setLastDate(lastDateStr) + .insert()); + return; + } + + database.write(context -> context.update(RSS_FEED) + .set(RSS_FEED.LAST_DATE, lastDateStr) + .where(RSS_FEED.URL.eq(feedConfig.url())) + .executeAsync()); + } + + /** + * Attempts to get a {@link ZonedDateTime} from an {@link Item} with a provided string date time + * format. + *

+ * If either of the function inputs are null or a {@link DateTimeParseException} is caught, the + * oldest-possible {@link ZonedDateTime} will get returned instead. + * + * @param item The {@link Item} from which to extract the date. + * @param dateTimeFormat The format of the date time string. + * @return The computed {@link ZonedDateTime} + * @throws DateTimeParseException if the date cannot be parsed + */ + private static ZonedDateTime getDateTimeFromItem(Item item, String dateTimeFormat) + throws DateTimeParseException { + Optional pubDate = item.getPubDate(); + + return pubDate.map(s -> getZonedDateTime(s, dateTimeFormat)).orElse(ZONED_TIME_MIN); + + } + + /** + * Checks if the dates between an RSS item and the provided config match. + * + * @param rssItem the RSS feed item + * @param feedConfig the RSS feed configuration containing the date formatter pattern + * @return true if the date format is valid, false otherwise + */ + private static boolean isValidDateFormat(Item rssItem, RSSFeed feedConfig) { + Optional firstRssFeedPubDate = rssItem.getPubDate(); + + if (firstRssFeedPubDate.isEmpty()) { + return false; + } + + try { + // If this throws a DateTimeParseException then it's certain + // that the format pattern defined in the config and the + // feed's actual format differ. + getZonedDateTime(firstRssFeedPubDate.get(), feedConfig.dateFormatterPattern()); + } catch (DateTimeParseException e) { + return false; + } + return true; + } + + /** + * Attempts to find text channels from a given RSS feed configuration. + * + * @param jda the JDA instance + * @param feed the RSS feed configuration to search for text channels + * @return an {@link List} of the text channels found, or empty if none are found + */ + private List getTextChannelsFromFeed(JDA jda, RSSFeed feed) { + final SnowflakeCacheView textChannelCache = jda.getTextChannelCache(); + List textChannels = textChannelCache.stream() + .filter(channel -> targetChannelPatterns.get(feed).test(channel.getName())) + .toList(); + + if (!textChannels.isEmpty()) { + return textChannels; + } + + return textChannelCache.stream() + .filter(channel -> fallbackChannelPattern.test(channel.getName())) + .toList(); + } + + /** + * Provides the {@link EmbedBuilder} from an RSS item used for sending RSS posts. + * + * @param item the RSS item to construct the embed message from + * @param feedConfig the configuration of the RSS feed + * @return the constructed {@link EmbedBuilder} containing information from the RSS item + */ + private static EmbedBuilder constructEmbedMessage(Item item, RSSFeed feedConfig) { + final EmbedBuilder embedBuilder = new EmbedBuilder(); + String title = item.getTitle().orElse("No title"); + String titleLink = item.getLink().orElse(""); + Optional rawDescription = item.getDescription(); + + // Set the item's timestamp to the embed if found + item.getPubDate() + .ifPresent(date -> embedBuilder + .setTimestamp(getZonedDateTime(date, feedConfig.dateFormatterPattern()))); + + embedBuilder.setTitle(title, titleLink); + embedBuilder.setAuthor(item.getChannel().getLink()); + + // Process embed's description if a raw description was provided + if (rawDescription.isPresent() && !rawDescription.orElseThrow().isEmpty()) { + Document fullDescription = + Jsoup.parse(StringEscapeUtils.unescapeHtml4(rawDescription.orElseThrow())); + String finalDescription = fullDescription.body() + .select("*") + .stream() + .map(Element::text) + .collect(Collectors.joining(". ")); + + embedBuilder.setDescription(StringUtils.abbreviate(finalDescription, MAX_CONTENTS)); + } else { + embedBuilder.setDescription("No description"); + } + + return embedBuilder; + } + + /** + * Fetches a list of {@link Item} from a given RSS url. + * + * @param rssUrl the URL of the RSS feed to fetch + * @return a list of {@link Item} parsed from the RSS feed, or an empty list if there's an + * {@link IOException} + */ + private List fetchRSSItemsFromURL(String rssUrl) { + try { + return rssReader.read(rssUrl).toList(); + } catch (IOException e) { + logger.warn("Could not fetch RSS from URL ({})", rssUrl); + return List.of(); + } + } + + /** + * Helper function for parsing a given date value to a {@link ZonedDateTime} with a given + * format. + * + * @param date the date value to parse, can be null + * @param format the format pattern to use for parsing + * @return the parsed {@link ZonedDateTime} object + * @throws DateTimeParseException if the date cannot be parsed + */ + private static ZonedDateTime getZonedDateTime(@Nullable String date, String format) + throws DateTimeParseException { + if (date == null) { + return ZONED_TIME_MIN; + } + + return ZonedDateTime.parse(date, DateTimeFormatter.ofPattern(format)); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/javamail/package-info.java b/application/src/main/java/org/togetherjava/tjbot/features/javamail/package-info.java new file mode 100644 index 0000000000..36305399af --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/javamail/package-info.java @@ -0,0 +1,10 @@ +/** + * This package forwards rss feeds to discord + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package org.togetherjava.tjbot.features.javamail; + +import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/RendererUtils.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/RendererUtils.java index 48dfe416e1..e93b5803a1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/RendererUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/RendererUtils.java @@ -14,7 +14,9 @@ import java.util.List; import java.util.Optional; -import static org.togetherjava.tjbot.features.utils.Colors.*; +import static org.togetherjava.tjbot.features.utils.Colors.ERROR_COLOR; +import static org.togetherjava.tjbot.features.utils.Colors.PARTIAL_SUCCESS_COLOR; +import static org.togetherjava.tjbot.features.utils.Colors.SUCCESS_COLOR; class RendererUtils { private RendererUtils() {} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultEmbedRenderer.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultEmbedRenderer.java index ecbd215eb4..4d7c77b4e0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultEmbedRenderer.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultEmbedRenderer.java @@ -10,7 +10,7 @@ import javax.annotation.Nullable; -import java.awt.*; +import java.awt.Color; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultMinimalEmbedRenderer.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultMinimalEmbedRenderer.java index 333d3bd69f..6870dfa398 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultMinimalEmbedRenderer.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultMinimalEmbedRenderer.java @@ -7,7 +7,7 @@ import javax.annotation.Nullable; -import java.awt.*; +import java.awt.Color; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultRenderer.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultRenderer.java index d264b9b8c1..b1f5fbdcc1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultRenderer.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/renderer/ResultRenderer.java @@ -10,7 +10,7 @@ import javax.annotation.Nullable; -import java.awt.*; +import java.awt.Color; /** * Allows to render JShell results. diff --git a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaHandler.java index 4b0719e98e..856256c2c9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaHandler.java @@ -8,17 +8,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.*; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.DidYouMean; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.DidYouMeans; import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.Error; - -import java.awt.*; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.FutureTopic; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.LanguageMessage; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.Pod; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.QueryResult; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.RelatedExample; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.RelatedExamples; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.SubPod; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.Tip; +import org.togetherjava.tjbot.features.mathcommands.wolframalpha.api.Tips; + +import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.http.HttpResponse; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaImages.java b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaImages.java index 3bad6ee03e..a9fd8cac82 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaImages.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaImages.java @@ -5,7 +5,9 @@ import javax.imageio.ImageIO; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/api/QueryResult.java b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/api/QueryResult.java index 15579560e8..f2fab087ec 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/api/QueryResult.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/api/QueryResult.java @@ -1,6 +1,10 @@ package org.togetherjava.tjbot.features.mathcommands.wolframalpha.api; -import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/BanCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/BanCommand.java index 1c0bf953ef..91a61da62a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/BanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/BanCommand.java @@ -1,7 +1,11 @@ package org.togetherjava.tjbot.features.moderation; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -65,8 +69,9 @@ public BanCommand(ModerationActionsStore actionsStore) { .addOptions(durationData) .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be banned", true) .addOptions(new OptionData(OptionType.INTEGER, DELETE_HISTORY_OPTION, - "the amount of days of the message history to delete, none means no messages are deleted.", - true).addChoice("none", 0).addChoice("recent", 1).addChoice("all", 7)); + "the message history to delete", true).addChoice("none", 0) + .addChoice("day", 1) + .addChoice("week", 7)); this.actionsStore = Objects.requireNonNull(actionsStore); } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/KickCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/KickCommand.java index f4cecf1a31..01d6e9958d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/KickCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/KickCommand.java @@ -1,7 +1,10 @@ package org.togetherjava.tjbot.features.moderation; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java index 9151a3f0de..d6aba6dc68 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java @@ -2,7 +2,12 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.IPermissionHolder; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/MuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/MuteCommand.java index e4ad3a4074..b94199697a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/MuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/MuteCommand.java @@ -1,6 +1,10 @@ package org.togetherjava.tjbot.features.moderation; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/NoteCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/NoteCommand.java index 2a3ed399fa..69412ebf9c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/NoteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/NoteCommand.java @@ -1,6 +1,10 @@ package org.togetherjava.tjbot.features.moderation; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.ISnowflake; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; @@ -51,10 +55,13 @@ public NoteCommand(ModerationActionsStore actionsStore) { @Override public void onSlashCommand(SlashCommandInteractionEvent event) { - OptionMapping targetOption = event.getOption(USER_OPTION); - Member author = event.getMember(); - Guild guild = event.getGuild(); - String content = event.getOption(CONTENT_OPTION).getAsString(); + OptionMapping targetOption = + Objects.requireNonNull(event.getOption(USER_OPTION), "The user is null"); + Member author = Objects.requireNonNull(event.getMember()); + Guild guild = Objects.requireNonNull(event.getGuild()); + String content = + Objects.requireNonNull(event.getOption(CONTENT_OPTION), "The content is null") + .getAsString(); if (!handleChecks(guild.getSelfMember(), author, targetOption.getAsMember(), content, event)) { diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/QuarantineCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/QuarantineCommand.java index 4d19167beb..876d1bcc24 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/QuarantineCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/QuarantineCommand.java @@ -1,6 +1,10 @@ package org.togetherjava.tjbot.features.moderation; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ReportCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ReportCommand.java index e56f56be60..d7c5192a1d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ReportCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ReportCommand.java @@ -3,7 +3,10 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; @@ -24,7 +27,7 @@ import org.togetherjava.tjbot.features.MessageContextCommand; import org.togetherjava.tjbot.features.utils.MessageUtils; -import java.awt.*; +import java.awt.Color; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/TransferQuestionCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/TransferQuestionCommand.java index aadf9ab61a..59924023a2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/TransferQuestionCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/TransferQuestionCommand.java @@ -32,6 +32,7 @@ import org.togetherjava.tjbot.features.BotCommandAdapter; import org.togetherjava.tjbot.features.CommandVisibility; import org.togetherjava.tjbot.features.MessageContextCommand; +import org.togetherjava.tjbot.features.chatgpt.ChatGptService; import org.togetherjava.tjbot.features.utils.StringDistances; import java.awt.Color; @@ -64,20 +65,23 @@ public final class TransferQuestionCommand extends BotCommandAdapter private static final int INPUT_MIN_LENGTH = 3; private final Predicate isHelpForumName; private final List tags; + private final ChatGptService chatGptService; /** * Creates a new instance. * * @param config to get the helper forum and tags + * @param chatGptService the service used to ask ChatGPT questions via the API. */ - public TransferQuestionCommand(Config config) { + public TransferQuestionCommand(Config config, ChatGptService chatGptService) { super(Commands.message(COMMAND_NAME), CommandVisibility.GUILD); isHelpForumName = Pattern.compile(config.getHelpSystem().getHelpForumPattern()).asMatchPredicate(); tags = config.getHelpSystem().getCategories(); + this.chatGptService = chatGptService; } @Override @@ -92,12 +96,20 @@ public void onMessageContext(MessageContextInteractionEvent event) { String originalChannelId = event.getTarget().getChannel().getId(); String authorId = event.getTarget().getAuthor().getId(); String mostCommonTag = tags.get(0); + String chatGptPrompt = + "Summarize the following text into a concise title or heading not more than 4-5 words, remove quotations if any: %s" + .formatted(originalMessage); + Optional chatGptResponse = chatGptService.ask(chatGptPrompt, ""); + String title = chatGptResponse.orElse(createTitle(originalMessage)); + if (title.length() > TITLE_MAX_LENGTH) { + title = title.substring(0, TITLE_MAX_LENGTH); + } TextInput modalTitle = TextInput.create(MODAL_TITLE_ID, "Title", TextInputStyle.SHORT) .setMaxLength(TITLE_MAX_LENGTH) .setMinLength(TITLE_MIN_LENGTH) .setPlaceholder("Describe the question in short") - .setValue(createTitle(originalMessage)) + .setValue(title) .build(); Builder modalInputBuilder = diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnbanCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnbanCommand.java index 1a3c2d24c0..1f4031646f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnbanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnbanCommand.java @@ -1,7 +1,11 @@ package org.togetherjava.tjbot.features.moderation; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.IPermissionHolder; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnmuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnmuteCommand.java index 8b0c6669e2..62530268f6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnmuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnmuteCommand.java @@ -1,6 +1,10 @@ package org.togetherjava.tjbot.features.moderation; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnquarantineCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnquarantineCommand.java index 4acdc63076..6659f5d43a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnquarantineCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/UnquarantineCommand.java @@ -1,6 +1,10 @@ package org.togetherjava.tjbot.features.moderation; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/WarnCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/WarnCommand.java index aa1805070e..2aca00ca2c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/WarnCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/WarnCommand.java @@ -1,6 +1,9 @@ package org.togetherjava.tjbot.features.moderation; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/WhoIsCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/WhoIsCommand.java index 137be42f08..e7f7f4b8e9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/WhoIsCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/WhoIsCommand.java @@ -1,7 +1,11 @@ package org.togetherjava.tjbot.features.moderation; import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.GuildVoiceState; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; @@ -14,7 +18,7 @@ import javax.annotation.CheckReturnValue; -import java.awt.*; +import java.awt.Color; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/AuditCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/AuditCommand.java index c9191f5760..42627c86d6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/AuditCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/AuditCommand.java @@ -31,7 +31,11 @@ import java.time.Instant; import java.time.ZoneOffset; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java index 25e6031f0f..b143a5cf64 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java @@ -28,15 +28,26 @@ import javax.annotation.Nullable; import java.awt.Color; -import java.time.*; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAccessor; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.stream.Collectors; + /** * Routine that automatically checks moderator actions on a schedule and logs them to dedicated * channels. diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/modmail/ModMailCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/modmail/ModMailCommand.java index 7737f0ae0c..723587378b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/modmail/ModMailCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/modmail/ModMailCommand.java @@ -27,7 +27,7 @@ import org.togetherjava.tjbot.features.SlashCommandAdapter; import org.togetherjava.tjbot.features.utils.DiscordClientAction; -import java.awt.*; +import java.awt.Color; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; @@ -204,7 +204,7 @@ private void sendMessage(ModalInteractionEvent event, MessageCreateAction messag } private MessageEmbed createModMailMessage(@Nullable User author, String userMessage) { - String authorTag = author == null ? "Anonymous" : author.getName(); + String authorTag = (author == null ? "Anonymous" : author.getName()) + " (Reporter)"; String authorAvatar = author == null ? null : author.getAvatarUrl(); return new EmbedBuilder().setTitle("Modmail") .setAuthor(authorTag, null, authorAvatar) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java index f60dc91b41..7af44533fa 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java @@ -2,7 +2,13 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.SelfUser; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; @@ -32,7 +38,11 @@ import org.togetherjava.tjbot.logging.LogMarkers; import java.awt.Color; -import java.util.*; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/reminder/ReminderCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/reminder/ReminderCommand.java index 273e832988..e67f9ce61a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/reminder/ReminderCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/reminder/ReminderCommand.java @@ -31,7 +31,11 @@ import org.togetherjava.tjbot.features.utils.Pagination; import org.togetherjava.tjbot.features.utils.StringDistances; -import java.time.*; +import java.time.Duration; +import java.time.Instant; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.temporal.TemporalAmount; import java.util.List; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java b/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java index 83f7496496..c57d813dbc 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java @@ -22,13 +22,28 @@ import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; -import org.togetherjava.tjbot.features.*; +import org.togetherjava.tjbot.features.EventReceiver; +import org.togetherjava.tjbot.features.Feature; +import org.togetherjava.tjbot.features.Features; +import org.togetherjava.tjbot.features.MessageContextCommand; +import org.togetherjava.tjbot.features.MessageReceiver; +import org.togetherjava.tjbot.features.Routine; +import org.togetherjava.tjbot.features.SlashCommand; +import org.togetherjava.tjbot.features.UserContextCommand; +import org.togetherjava.tjbot.features.UserInteractionType; +import org.togetherjava.tjbot.features.UserInteractor; import org.togetherjava.tjbot.features.componentids.ComponentId; import org.togetherjava.tjbot.features.componentids.ComponentIdParser; import org.togetherjava.tjbot.features.componentids.ComponentIdStore; import org.togetherjava.tjbot.features.componentids.InvalidComponentIdFormatException; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java index 2cf1994fba..1bf13983bf 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java @@ -24,7 +24,12 @@ import org.togetherjava.tjbot.features.utils.StringDistances; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; /** * Implements the {@code /tag} command which lets the bot respond content of a tag that has been diff --git a/application/src/main/java/org/togetherjava/tjbot/features/tags/TagManageCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/tags/TagManageCommand.java index c6b327ecf2..bb69ffb433 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/tags/TagManageCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/tags/TagManageCommand.java @@ -22,7 +22,14 @@ import java.nio.charset.StandardCharsets; import java.time.temporal.TemporalAccessor; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/tophelper/TopHelpersCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/tophelper/TopHelpersCommand.java index 52692f139b..04295920e1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/tophelper/TopHelpersCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/tophelper/TopHelpersCommand.java @@ -23,9 +23,18 @@ import javax.annotation.Nullable; import java.math.BigDecimal; -import java.time.*; +import java.time.Instant; +import java.time.LocalTime; +import java.time.Month; +import java.time.YearMonth; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.format.TextStyle; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.function.Function; import java.util.function.IntFunction; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/StringDistances.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/StringDistances.java index 583623cae2..563d5324e5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/StringDistances.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/StringDistances.java @@ -1,6 +1,12 @@ package org.togetherjava.tjbot.features.utils; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Queue; import java.util.stream.IntStream; import java.util.stream.Stream; diff --git a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogAppender.java b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogAppender.java index fbf6e97a83..f770618483 100644 --- a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogAppender.java +++ b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogAppender.java @@ -1,6 +1,11 @@ package org.togetherjava.tjbot.logging.discord; -import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.Plugin; diff --git a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java index b86a6eec16..3b35cb9ac2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java +++ b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java @@ -18,7 +18,13 @@ import java.io.StringWriter; import java.net.URI; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Queue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; diff --git a/application/src/main/resources/db/V14__Add_Rss_Feed_Cache.sql b/application/src/main/resources/db/V14__Add_Rss_Feed_Cache.sql new file mode 100644 index 0000000000..ed65d30fb0 --- /dev/null +++ b/application/src/main/resources/db/V14__Add_Rss_Feed_Cache.sql @@ -0,0 +1,5 @@ +CREATE TABLE rss_feed +( + url TEXT NOT NULL PRIMARY KEY, + last_date TEXT NOT NULL +) \ No newline at end of file diff --git a/application/src/test/java/org/togetherjava/tjbot/features/basic/SlashCommandEducatorTest.java b/application/src/test/java/org/togetherjava/tjbot/features/basic/SlashCommandEducatorTest.java index 3b7035dc20..03d07c1a2c 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/basic/SlashCommandEducatorTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/basic/SlashCommandEducatorTest.java @@ -16,7 +16,9 @@ import java.util.stream.Stream; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; final class SlashCommandEducatorTest { private JdaTester jdaTester; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/mathcommands/TeXCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/features/mathcommands/TeXCommandTest.java index 4ba05958b2..483d9d48e6 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/mathcommands/TeXCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/mathcommands/TeXCommandTest.java @@ -14,7 +14,9 @@ import java.util.ArrayList; import java.util.List; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.description; import static org.mockito.Mockito.verify; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/mediaonly/MediaOnlyChannelListenerTest.java b/application/src/test/java/org/togetherjava/tjbot/features/mediaonly/MediaOnlyChannelListenerTest.java index fcc9f8b71b..c31a49589e 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/mediaonly/MediaOnlyChannelListenerTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/mediaonly/MediaOnlyChannelListenerTest.java @@ -15,7 +15,11 @@ import java.util.List; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; final class MediaOnlyChannelListenerTest { diff --git a/application/src/test/java/org/togetherjava/tjbot/features/reminder/RemindRoutineTest.java b/application/src/test/java/org/togetherjava/tjbot/features/reminder/RemindRoutineTest.java index 96e83aebd7..533fd82c58 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/reminder/RemindRoutineTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/reminder/RemindRoutineTest.java @@ -24,7 +24,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.togetherjava.tjbot.db.generated.tables.PendingReminders.PENDING_REMINDERS; final class RemindRoutineTest { diff --git a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagCommandTest.java index bdd119a311..bb8f3cea5b 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagCommandTest.java @@ -15,7 +15,9 @@ import javax.annotation.Nullable; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; final class TagCommandTest { private TagSystem system; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagManageCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagManageCommandTest.java index 8789d2b679..13027fcf63 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagManageCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagManageCommandTest.java @@ -23,9 +23,18 @@ import java.io.IOException; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; final class TagManageCommandTest { private TagSystem system; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagSystemTest.java b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagSystemTest.java index bd0efcc9d9..97b1b2ab3f 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagSystemTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagSystemTest.java @@ -11,8 +11,14 @@ import java.util.Optional; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; final class TagSystemTest { private TagSystem system; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagsCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagsCommandTest.java index 22a821f3da..4869ef2a5b 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/tags/TagsCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/tags/TagsCommandTest.java @@ -16,7 +16,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; final class TagsCommandTest { private TagSystem system; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/tophelper/TopHelperMessageListenerTest.java b/application/src/test/java/org/togetherjava/tjbot/features/tophelper/TopHelperMessageListenerTest.java index e4cb316a6e..a0cd9fa9f3 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/tophelper/TopHelperMessageListenerTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/tophelper/TopHelperMessageListenerTest.java @@ -18,7 +18,9 @@ import java.util.List; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.togetherjava.tjbot.db.generated.tables.HelpChannelMessages.HELP_CHANNEL_MESSAGES; diff --git a/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java b/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java index 944a978f51..f84f0ec1f9 100644 --- a/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java +++ b/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java @@ -2,7 +2,11 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; @@ -19,7 +23,12 @@ import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.components.ItemComponent; import net.dv8tion.jda.api.interactions.components.LayoutComponent; -import net.dv8tion.jda.api.requests.*; +import net.dv8tion.jda.api.requests.ErrorResponse; +import net.dv8tion.jda.api.requests.Response; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.RestConfig; +import net.dv8tion.jda.api.requests.RestRateLimiter; +import net.dv8tion.jda.api.requests.SequentialRestRateLimiter; import net.dv8tion.jda.api.requests.restaction.CacheRestAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.AttachmentProxy; @@ -29,7 +38,12 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData; import net.dv8tion.jda.api.utils.messages.MessageEditData; import net.dv8tion.jda.internal.JDAImpl; -import net.dv8tion.jda.internal.entities.*; +import net.dv8tion.jda.internal.entities.EntityBuilder; +import net.dv8tion.jda.internal.entities.GuildImpl; +import net.dv8tion.jda.internal.entities.MemberImpl; +import net.dv8tion.jda.internal.entities.RoleImpl; +import net.dv8tion.jda.internal.entities.SelfUserImpl; +import net.dv8tion.jda.internal.entities.UserImpl; import net.dv8tion.jda.internal.entities.channel.concrete.PrivateChannelImpl; import net.dv8tion.jda.internal.entities.channel.concrete.TextChannelImpl; import net.dv8tion.jda.internal.entities.channel.concrete.ThreadChannelImpl; @@ -52,7 +66,12 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -64,7 +83,19 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.AdditionalMatchers.not; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; /** * Utility class for testing {@link SlashCommand}s. diff --git a/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java b/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java index 62ed9a81cd..31b27e4e19 100644 --- a/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java +++ b/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java @@ -16,7 +16,12 @@ import org.togetherjava.tjbot.jda.payloads.PayloadChannel; import org.togetherjava.tjbot.jda.payloads.PayloadMember; import org.togetherjava.tjbot.jda.payloads.PayloadUser; -import org.togetherjava.tjbot.jda.payloads.slashcommand.*; +import org.togetherjava.tjbot.jda.payloads.slashcommand.PayloadSlashCommand; +import org.togetherjava.tjbot.jda.payloads.slashcommand.PayloadSlashCommandData; +import org.togetherjava.tjbot.jda.payloads.slashcommand.PayloadSlashCommandMembers; +import org.togetherjava.tjbot.jda.payloads.slashcommand.PayloadSlashCommandOption; +import org.togetherjava.tjbot.jda.payloads.slashcommand.PayloadSlashCommandResolved; +import org.togetherjava.tjbot.jda.payloads.slashcommand.PayloadSlashCommandUsers; import javax.annotation.Nullable; diff --git a/formatter/src/test/java/org/togetherjava/tjbot/formatter/formatting/TokenQueueTest.java b/formatter/src/test/java/org/togetherjava/tjbot/formatter/formatting/TokenQueueTest.java index 04b0aa4146..d29a86e97e 100644 --- a/formatter/src/test/java/org/togetherjava/tjbot/formatter/formatting/TokenQueueTest.java +++ b/formatter/src/test/java/org/togetherjava/tjbot/formatter/formatting/TokenQueueTest.java @@ -8,7 +8,10 @@ import java.util.List; import java.util.NoSuchElementException; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; final class TokenQueueTest {