From 835a951b812b168c9ac5f0de2f0eb6f4d992568d Mon Sep 17 00:00:00 2001 From: Nathan Weisz <151496498+nateweisz@users.noreply.github.com> Date: Sat, 13 Apr 2024 13:33:06 -0700 Subject: [PATCH 01/13] Java RSS Feed (#1037) * wip: initial commit * style(spotlessApply): run uncalled task * fix: register routine in features list * feat: update config.json.template * wip(rss-config): change config design * feat: make targetChannelPattern into a Map * refactor: don't hardcode number * feat: improve constructEmbedMessage * feat: improve the description constructing * feat: increase MAX_CONTENTS to 300 * wip: changes * feat(rss-feed): convert polling interval to be configurable * feat: improve date parsing Co-authored-by: Ethan McCue <5004262+bowbahdoe@users.noreply.github.com> * feat: update config.json.template * feat: improve date handling and add embed timestamp * fix: malformed `config.json.template` * fix: now correctly finds the latest date * feat: improve date handling and add embed timestamp * fix: now correctly finds the latest date * feat: you can now optionally declare a specific channel for a feed to go to * finished javadoc todos * improved embed * feat(rss-routine): working version - Added JavaDocs on most parts - Simplified the code and cleaned up unused stuff * feat(rss-routine): increase MAX_CONTENTS to 1000 * refactor(rss-routine): rename to RSSHandlerRoutine While the original idea was to add a Java news and changes RSS feed, this was expanded to all types of RSS feeds, so a more appropriate name makes things more clear. * docs(rss-routine): add JavaDocs for constructor and class * feat: add @NotNull annotation * Update RSSHandlerRoutine.java * fix: reverse feed so it posts in correct order * refactor: use variable types instead of var * feat: use fetchAny() instead of fetch() * fix(rss-handler): remove redundant empty check Co-authored-by: Ethan McCue <5004262+bowbahdoe@users.noreply.github.com> * refactor(rss-handler): remove AtomicReference usage Co-authored-by: Ethan McCue <5004262+bowbahdoe@users.noreply.github.com> * resolve a couple issues * refactor(rss): switch to using a record for the config * feat: rename to fallbackChannelPattern * refactor: remove unnecessary throws exception in method signature * docs: add a few missing parameters and fix typos * refactor: reduce try-catch scope and added clarifying comment * perf: use Stream instead of StringBuilder * refactor: put fallback case into an else statement * refactor: switch to Map#containsKey() for targetChannelPatterns * feat: modularize sendRss() method and improve variable names This also changes the functionality of how new RSS feeds get dealt with for the first time. Before this commit, all items would get posted as embeds on Discord, and as a result, that would bombard the target channel with RSS posts once the routine executes. This commit changes this behavior by assuming that all RSS posts have been posted and it should only consider posting posts newer than the registered date. * refactor: remove star import * fix: add Objects#requireNonNull() on rssConfig * changes * feat: improvements from code reviews Co-authored-by: Nathan Weisz <151496498+nateweisz@users.noreply.github.com> * feat: add DateTimeParseException in signature * refactor: use .orElseThrow() instead of .get() * fixed missing coma after rss configuration Co-authored-by: Tanish * fix(rss): feeds support multiple channels fix(rss): polling interval is now clear on unit of time fix(rss): posting messages now uses forEachOrdered * Optional> -> List * extract item post predicate to seperate function * various changes from null to optional --------- Co-authored-by: christolis Co-authored-by: Ethan McCue <5004262+bowbahdoe@users.noreply.github.com> Co-authored-by: Tanish --- application/build.gradle | 3 + application/config.json.template | 15 +- .../org/togetherjava/tjbot/config/Config.java | 13 + .../togetherjava/tjbot/config/RSSFeed.java | 30 ++ .../tjbot/config/RSSFeedsConfig.java | 34 ++ .../togetherjava/tjbot/features/Features.java | 2 + .../features/javamail/RSSHandlerRoutine.java | 414 ++++++++++++++++++ .../tjbot/features/javamail/package-info.java | 10 + .../resources/db/V14__Add_Rss_Feed_Cache.sql | 5 + 9 files changed, 524 insertions(+), 2 deletions(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/config/RSSFeed.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/config/RSSFeedsConfig.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/javamail/package-info.java create mode 100644 application/src/main/resources/db/V14__Add_Rss_Feed_Cache.sql 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..3071633109 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": "", + "targetChannelPattern": "", + "dateFormatterPattern": "EEE, dd MMM yyyy HH:mm:ss zzz" + } + ], + "fallbackChannelPattern": "", + "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..9b837799ef 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -24,6 +24,7 @@ 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.javamail.RSSHandlerRoutine; import org.togetherjava.tjbot.features.jshell.JShellCommand; import org.togetherjava.tjbot.features.jshell.JShellEval; import org.togetherjava.tjbot.features.mathcommands.TeXCommand; @@ -109,6 +110,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)); 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..8ab9c2d528 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java @@ -0,0 +1,414 @@ +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 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, got empty response (channel {} not found)", + feedConfig.targetChannelPattern()); + 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) { + // Attempt to find the target channel, use the fallback otherwise + if (targetChannelPatterns.containsKey(feed)) { + return jda.getTextChannelCache() + .stream() + .filter(channel -> targetChannelPatterns.get(feed).test(channel.getName())) + .toList(); + } else { + return jda.getTextChannelCache() + .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/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 From adc0e165c576c38b9a29493fd44a9f0edd81483e Mon Sep 17 00:00:00 2001 From: Nathan Weisz <151496498+nateweisz@users.noreply.github.com> Date: Sat, 13 Apr 2024 13:58:50 -0700 Subject: [PATCH 02/13] quickfix (#1087) --- .../tjbot/features/javamail/RSSHandlerRoutine.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 8ab9c2d528..7edc42f7ef 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java @@ -145,8 +145,9 @@ private void sendRSS(JDA jda, RSSFeed feedConfig) { final Optional> shouldItemBePosted = prepareItemPostPredicate(feedConfig, rssItems); - if (shouldItemBePosted.isEmpty()) + if (shouldItemBePosted.isEmpty()) { return; + } rssItems.reversed() .stream() .filter(shouldItemBePosted.get()) From f109c56ef6b4455bbce0bd71b429343a496eebfe Mon Sep 17 00:00:00 2001 From: Daniel Tischner Date: Mon, 22 Apr 2024 10:08:53 +0200 Subject: [PATCH 03/13] Proper RSS default config (#1098) working config for RSS feed that fits majority of users --- application/config.json.template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/config.json.template b/application/config.json.template index 3071633109..a1aec8f470 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -107,12 +107,12 @@ "rssConfig": { "feeds": [ { - "url": "", - "targetChannelPattern": "", - "dateFormatterPattern": "EEE, dd MMM yyyy HH:mm:ss zzz" + "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": "", + "fallbackChannelPattern": "java-news-and-changes", "pollIntervalInMinutes": 10 }, "memberCountCategoryPattern": "Info" From 2c3da71cddfc0094f0194abbbe096b5973931d6e Mon Sep 17 00:00:00 2001 From: Simon Verhoeven Date: Mon, 6 May 2024 17:54:52 +0200 Subject: [PATCH 04/13] chore: add safety checks to NoteCommand (#1086) chore: add safety checks to NoteCommand --- .../tjbot/features/moderation/NoteCommand.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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..4a39fb7056 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 @@ -51,10 +51,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)) { From b097294a6ef4d7bdf44809d0588e70652c6475a2 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven Date: Mon, 6 May 2024 18:17:28 +0200 Subject: [PATCH 05/13] chore: rename ban history options to be clearer (#1082) * chore: rename ban history options to be clearer * chore: update ban command period description --- .../togetherjava/tjbot/features/moderation/BanCommand.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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..f5032c7118 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 @@ -65,8 +65,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); } From 2738a982d50b290816812bc0629d45324fc3ce02 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven Date: Mon, 6 May 2024 19:35:41 +0200 Subject: [PATCH 06/13] bug: do not archive pinned threads (resolves #1084) (#1088) --- .../features/help/HelpThreadAutoArchiver.java | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) 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) { From 4e7b9b6721285e68e20d6efc5a8f6ae34964b7f9 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Sun, 12 May 2024 14:08:57 +0300 Subject: [PATCH 07/13] fix(rss): logic for acquiring target channels (#1090) This commit attempts to change the way a text channel list for RSS feeds gets generated, as it would originally not consider any channels that matched the fallback channel pattern and it would constantly log a warning that would clutter the log channels and skip sending any RSS posts as a result. The method which is responsible for finding the text channels from a given RSS feed configuration now focuses on collecting a list of text channels that match the target channel pattern from the configuration, and if no channels are found, the same collection attempt happens with the fallback channel pattern. In case an empty list is still yielded, a now-improved and more accurate warning message gets logged. --- .../features/javamail/RSSHandlerRoutine.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 index 7edc42f7ef..e114f53a61 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/javamail/RSSHandlerRoutine.java @@ -6,6 +6,7 @@ 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; @@ -125,8 +126,8 @@ public void runRoutine(@Nonnull JDA jda) { private void sendRSS(JDA jda, RSSFeed feedConfig) { List textChannels = getTextChannelsFromFeed(jda, feedConfig); if (textChannels.isEmpty()) { - logger.warn("Tried to send an RSS post, got empty response (channel {} not found)", - feedConfig.targetChannelPattern()); + logger.warn( + "Tried to send an RSS post, but neither a target channel nor a fallback channel was found."); return; } @@ -326,18 +327,18 @@ private static boolean isValidDateFormat(Item rssItem, RSSFeed feedConfig) { * @return an {@link List} of the text channels found, or empty if none are found */ private List getTextChannelsFromFeed(JDA jda, RSSFeed feed) { - // Attempt to find the target channel, use the fallback otherwise - if (targetChannelPatterns.containsKey(feed)) { - return jda.getTextChannelCache() - .stream() - .filter(channel -> targetChannelPatterns.get(feed).test(channel.getName())) - .toList(); - } else { - return jda.getTextChannelCache() - .stream() - .filter(channel -> fallbackChannelPattern.test(channel.getName())) - .toList(); + 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(); } /** From d198f195b13780d3cbd5618a2750add2d1fd9e42 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven Date: Sun, 12 May 2024 14:39:42 +0200 Subject: [PATCH 08/13] Remove star imports (#1092) chore: remove star imports (resolves #1057) --- .../togetherjava/tjbot/features/Features.java | 24 ++++++++++- .../features/basic/RoleSelectCommand.java | 14 +++++-- .../bookmarks/BookmarksListRemoveHandler.java | 6 ++- .../features/code/CodeMessageHandler.java | 6 ++- .../componentids/ComponentIdStore.java | 14 ++++++- .../tjbot/features/github/GitHubCommand.java | 5 ++- .../features/github/GitHubReference.java | 11 ++++- .../tjbot/features/help/HelpSystemHelper.java | 9 +++- .../features/help/HelpThreadCommand.java | 12 +++++- .../jshell/renderer/RendererUtils.java | 4 +- .../jshell/renderer/ResultEmbedRenderer.java | 2 +- .../renderer/ResultMinimalEmbedRenderer.java | 2 +- .../jshell/renderer/ResultRenderer.java | 2 +- .../wolframalpha/WolframAlphaHandler.java | 22 ++++++++-- .../wolframalpha/WolframAlphaImages.java | 4 +- .../wolframalpha/api/QueryResult.java | 6 ++- .../tjbot/features/moderation/BanCommand.java | 6 ++- .../features/moderation/KickCommand.java | 5 ++- .../features/moderation/ModerationUtils.java | 7 +++- .../features/moderation/MuteCommand.java | 6 ++- .../features/moderation/NoteCommand.java | 6 ++- .../moderation/QuarantineCommand.java | 6 ++- .../features/moderation/ReportCommand.java | 7 +++- .../features/moderation/UnbanCommand.java | 6 ++- .../features/moderation/UnmuteCommand.java | 6 ++- .../moderation/UnquarantineCommand.java | 6 ++- .../features/moderation/WarnCommand.java | 5 ++- .../features/moderation/WhoIsCommand.java | 8 +++- .../moderation/audit/AuditCommand.java | 6 ++- .../moderation/audit/ModAuditLogRoutine.java | 15 ++++++- .../moderation/modmail/ModMailCommand.java | 2 +- .../features/moderation/scam/ScamBlocker.java | 14 ++++++- .../features/reminder/ReminderCommand.java | 6 ++- .../tjbot/features/system/BotCore.java | 19 ++++++++- .../tjbot/features/tags/TagCommand.java | 7 +++- .../tjbot/features/tags/TagManageCommand.java | 9 +++- .../features/tophelper/TopHelpersCommand.java | 13 +++++- .../tjbot/features/utils/StringDistances.java | 8 +++- .../logging/discord/DiscordLogAppender.java | 7 +++- .../logging/discord/DiscordLogForwarder.java | 8 +++- .../basic/SlashCommandEducatorTest.java | 4 +- .../features/mathcommands/TeXCommandTest.java | 4 +- .../MediaOnlyChannelListenerTest.java | 6 ++- .../features/reminder/RemindRoutineTest.java | 8 +++- .../tjbot/features/tags/TagCommandTest.java | 4 +- .../features/tags/TagManageCommandTest.java | 13 +++++- .../tjbot/features/tags/TagSystemTest.java | 10 ++++- .../tjbot/features/tags/TagsCommandTest.java | 3 +- .../TopHelperMessageListenerTest.java | 4 +- .../org/togetherjava/tjbot/jda/JdaTester.java | 41 ++++++++++++++++--- .../SlashCommandInteractionEventBuilder.java | 7 +++- .../formatter/formatting/TokenQueueTest.java | 5 ++- 52 files changed, 366 insertions(+), 74 deletions(-) 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 9b837799ef..e2f5778d37 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -23,14 +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; 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/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/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 f5032c7118..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; 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 4a39fb7056..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; 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/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..76d5082057 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; 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/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 { From 65529a78146ec39c61d7fe765565bd372112b092 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven Date: Sun, 12 May 2024 18:54:11 +0200 Subject: [PATCH 09/13] feature: clarify reporter name to avoid confusion (#1097) --- .../tjbot/features/moderation/modmail/ModMailCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 76d5082057..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 @@ -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) From f045ccfaadae5902594d2e1bdfbadb0a071c8fc5 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Mon, 13 May 2024 08:39:27 +0100 Subject: [PATCH 10/13] Create .gitattributes (#1108) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..fcadb2cf97 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf From f8c91a922f4d6435e6b96c6556919534d5d81189 Mon Sep 17 00:00:00 2001 From: alphaBEE <61616007+ankitsmt211@users.noreply.github.com> Date: Mon, 13 May 2024 19:25:31 +0530 Subject: [PATCH 11/13] using a better method to retrieve start message for thread (#1106) * using a better method to retrieve start message for thread * replace retrieveMessageById with getStartMessage on threadChannel channel type --- .../tjbot/features/help/HelpThreadCreatedListener.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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..8988f305d9 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 @@ -97,8 +97,7 @@ private static boolean isPostedBySelfUser(Message message) { } private RestAction createAIResponse(ThreadChannel threadChannel) { - RestAction originalQuestion = - threadChannel.retrieveMessageById(threadChannel.getIdLong()); + RestAction originalQuestion = threadChannel.retrieveStartMessage(); return originalQuestion.flatMap(HelpThreadCreatedListener::isContextSufficient, message -> helper.constructChatGptAttempt(threadChannel, getMessageContent(message), componentIdInteractor)); @@ -110,7 +109,7 @@ private static boolean isContextSufficient(Message message) { } private RestAction pinOriginalQuestion(ThreadChannel threadChannel) { - return threadChannel.retrieveMessageById(threadChannel.getIdLong()).flatMap(Message::pin); + return threadChannel.retrieveStartMessage().flatMap(Message::pin); } private RestAction generateAutomatedResponse(ThreadChannel threadChannel) { @@ -172,7 +171,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)); From 10718cb3edf97f899d4042252f2a8ae32fa6ba4a Mon Sep 17 00:00:00 2001 From: vishv843 <55508798+vishv843@users.noreply.github.com> Date: Tue, 14 May 2024 09:13:02 -0500 Subject: [PATCH 12/13] Title generation by ChatGPT for transfer-questions command (#1059) * title generation by ChatGPT for transfer-questions command refactoring reviewer comments addressed prompt improved Update prompt Co-authored-by: alphaBEE <61616007+ankitsmt211@users.noreply.github.com> removed tags * refactor prompt message for better titles --------- Co-authored-by: alphaBEE <61616007+ankitsmt211@users.noreply.github.com> --- .../togetherjava/tjbot/features/Features.java | 2 +- .../moderation/TransferQuestionCommand.java | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) 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 e2f5778d37..569db4a881 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -153,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/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 = From d3a19aa9a3f48480e10ff24af6f3b4feaebae3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Connor=20Schweigh=C3=B6fer?= Date: Wed, 15 May 2024 10:27:29 +0200 Subject: [PATCH 13/13] Refactor help thread creation workflow (#1112) Thread creation flow is more clear and optimized by removing unnecessary calls to discord API --- .../help/HelpThreadCreatedListener.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) 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 8988f305d9..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,11 +99,9 @@ private static boolean isPostedBySelfUser(Message message) { return message.getJDA().getSelfUser().equals(message.getAuthor()); } - private RestAction createAIResponse(ThreadChannel threadChannel) { - RestAction originalQuestion = threadChannel.retrieveStartMessage(); - 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) { @@ -108,12 +109,8 @@ private static boolean isContextSufficient(Message message) { && !LinkDetection.containsLink(message.getContentRaw()); } - private RestAction pinOriginalQuestion(ThreadChannel threadChannel) { - return threadChannel.retrieveStartMessage().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) {