From 90c7a757318b0efbacd3cccc1f51fb2c1cb24357 Mon Sep 17 00:00:00 2001 From: d3vel0per Date: Fri, 22 Oct 2021 11:03:51 +0530 Subject: [PATCH 1/3] TopHelpers with review and upstream changes --- application/build.gradle | 5 +- .../org/togetherjava/tjbot/Application.java | 4 +- .../togetherjava/tjbot/commands/Commands.java | 2 +- .../tjbot/commands/TopHelpers.java | 94 +++++++++++++++++++ .../listener/MessageMetadataListener.java | 70 ++++++++++++++ .../tjbot/listener/package-info.java | 4 + .../org/togetherjava/tjbot/util/JDAUtils.java | 24 +++++ .../tjbot/util/MessageTemplate.java | 17 ++++ .../tjbot/util/PresentationUtils.java | 40 ++++++++ .../togetherjava/tjbot/util/package-info.java | 4 + .../db/V3__Create_Table_Message_Metadata.sql | 7 ++ 11 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/listener/package-info.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/util/JDAUtils.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/util/package-info.java create mode 100644 application/src/main/resources/db/V3__Create_Table_Message_Metadata.sql diff --git a/application/build.gradle b/application/build.gradle index ce7e55194a..99a6e20355 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -60,10 +60,11 @@ dependencies { implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.13.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' - testImplementation 'org.mockito:mockito-core:3.12.4' - testRuntimeOnly 'org.mockito:mockito-core:3.12.4' + implementation 'com.github.freva:ascii-table:1.2.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + testImplementation 'org.mockito:mockito-core:4.0.0' } application { diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index 60a1d84b28..bb0fba60f9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -8,6 +8,7 @@ import org.togetherjava.tjbot.commands.system.CommandSystem; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.listener.MessageMetadataListener; import javax.security.auth.login.LoginException; import java.io.IOException; @@ -76,7 +77,8 @@ public static void runBot(String token, Path databasePath) { Database database = new Database("jdbc:sqlite:" + databasePath.toAbsolutePath()); JDA jda = JDABuilder.createDefault(token) - .addEventListeners(new CommandSystem(database)) + .addEventListeners(new CommandSystem(database), + new MessageMetadataListener(database)) .build(); jda.awaitReady(); logger.info("Bot is ready"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java b/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java index 3961b19ed8..c7d52a7ef9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java @@ -41,6 +41,6 @@ public enum Commands { // available. return List.of(new PingCommand(), new DatabaseCommand(database), new TeXCommand(), new TagCommand(tagSystem), new TagManageCommand(tagSystem), - new TagsCommand(tagSystem)); + new TagsCommand(tagSystem), new TopHelpers(database)); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java b/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java new file mode 100644 index 0000000000..f27b2eb4e9 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java @@ -0,0 +1,94 @@ +package org.togetherjava.tjbot.commands; + +import com.github.freva.asciitable.HorizontalAlign; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import org.jooq.Records; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.util.PresentationUtils; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.jooq.impl.DSL.*; +import static org.togetherjava.tjbot.db.generated.tables.MessageMetadata.MESSAGE_METADATA; +import static org.togetherjava.tjbot.util.MessageTemplate.PLAINTEXT_MESSAGE_TEMPLATE; + +public final class TopHelpers extends SlashCommandAdapter { + + private static final String COUNT_OPTION = "count"; + private static final String NO_ENTRIES = "No entries"; + + private static final long MIN_COUNT = 10L; + private static final long MAX_COUNT = 30L; + + private record TopHelperRow(Integer serialId, Long userId, Long messageCount) { + } + + private final Database database; + + /** + * Initializes TopHelpers with a database. + * + * @param database the database to store the key-value pairs in + */ + public TopHelpers(Database database) { + super("tophelpers", "Find top helpers for last 30 days", SlashCommandVisibility.GUILD); + this.database = database; + getData().addOptions(new OptionData(OptionType.INTEGER, COUNT_OPTION, + "Count of top helpers to be retrieved (default is 10 and capped at 30)", false)); + } + + @SuppressWarnings("squid:S1192") + @Override + public void onSlashCommand(SlashCommandEvent event) { + Long guildId = event.getGuild().getIdLong(); + Long count = getBoundedCountAsLong(event.getOption(COUNT_OPTION)); + database.read(dsl -> { + List records = dsl.with("TOPHELPERS") + .as(select(MESSAGE_METADATA.USER_ID, count().as("COUNT")).from(MESSAGE_METADATA) + .where(MESSAGE_METADATA.GUILD_ID.eq(guildId)) + .groupBy(MESSAGE_METADATA.USER_ID) + .orderBy(count().desc()) + .limit(count)) + .select(rowNumber().over(orderBy(field(name("COUNT")).desc())).as("#"), + field(name("USER_ID"), Long.class), field(name("COUNT"), Long.class)) + .from(table(name("TOPHELPERS"))) + .fetch(Records.mapping(TopHelperRow::new)); + generateResponse(event, records); + }); + } + + private static long getBoundedCountAsLong(OptionMapping count) { + return count == null ? MIN_COUNT : Math.min(count.getAsLong(), MAX_COUNT); + } + + private static String prettyPrintOutput(List> dataFrame) { + return String.format(PLAINTEXT_MESSAGE_TEMPLATE, + dataFrame.isEmpty() ? NO_ENTRIES + : PresentationUtils.dataFrameToAsciiTable(dataFrame, + new String[] {"#", "Name", "Message Count (in the last 30 days)"}, + new HorizontalAlign[] {HorizontalAlign.RIGHT, HorizontalAlign.LEFT, + HorizontalAlign.RIGHT})); + } + + private static void generateResponse(SlashCommandEvent event, List records) { + List userIds = records.stream().map(TopHelperRow::userId).toList(); + event.getGuild().retrieveMembersByIds(userIds).onSuccess(members -> { + Map activeUserIdToEffectiveNames = members.stream() + .collect(Collectors.toMap(Member::getIdLong, Member::getEffectiveName)); + List> topHelpersDataframe = + records.stream() + .map(topHelperRow -> List.of(topHelperRow.serialId.toString(), + activeUserIdToEffectiveNames.getOrDefault(topHelperRow.userId, + "[UNKNOWN]"), + topHelperRow.messageCount.toString())) + .toList(); + event.reply(prettyPrintOutput(topHelpersDataframe)).queue(); + }); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java b/application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java new file mode 100644 index 0000000000..7b3737837e --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java @@ -0,0 +1,70 @@ +package org.togetherjava.tjbot.listener; + +import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.util.JDAUtils; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static org.togetherjava.tjbot.db.generated.tables.MessageMetadata.MESSAGE_METADATA; + +/** + * Listener responsible for persistence of text message metadata + */ +public final class MessageMetadataListener extends ListenerAdapter { + private static final Logger logger = LoggerFactory.getLogger(MessageMetadataListener.class); + + private static final int MESSAGE_METADATA_ARCHIVAL_DAYS = 30; + + private final Database database; + + /** + * Creates a new message metadata listener, using the given database. + * + * @param database the database to store message metadata + */ + public MessageMetadataListener(Database database) { + this.database = database; + } + + /** + * Stores the relevant message metadata for on message received event. + * + * @param event incoming message received event. + */ + @Override + public void onGuildMessageReceived(GuildMessageReceivedEvent event) { + var channel = event.getChannel(); + if (!event.getAuthor().isBot() && !event.isWebhookMessage() + && JDAUtils.isAHelpChannel(channel)) { + var messageId = event.getMessage().getIdLong(); + var guildId = event.getGuild().getIdLong(); + var channelId = channel.getIdLong(); + var userId = event.getAuthor().getIdLong(); + var createTimestamp = event.getMessage().getTimeCreated().toEpochSecond(); + database.write(dsl -> { + dsl.newRecord(MESSAGE_METADATA) + .setMessageId(messageId) + .setGuildId(guildId) + .setChannelId(channelId) + .setUserId(userId) + .setCreateTimestamp(createTimestamp) + .insert(); + int noOfRowsDeleted = dsl.deleteFrom(MESSAGE_METADATA) + .where(MESSAGE_METADATA.CREATE_TIMESTAMP.le(Instant.now() + .minus(MESSAGE_METADATA_ARCHIVAL_DAYS, ChronoUnit.DAYS) + .getEpochSecond())) + .execute(); + if (noOfRowsDeleted > 0) { + logger.debug( + "{} old records have been deleted based on archival criteria of {} days.", + noOfRowsDeleted, MESSAGE_METADATA_ARCHIVAL_DAYS); + } + }); + } + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java b/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java new file mode 100644 index 0000000000..ed09893815 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains non-user interactive event listeners + */ +package org.togetherjava.tjbot.listener; diff --git a/application/src/main/java/org/togetherjava/tjbot/util/JDAUtils.java b/application/src/main/java/org/togetherjava/tjbot/util/JDAUtils.java new file mode 100644 index 0000000000..133fe5b89f --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/util/JDAUtils.java @@ -0,0 +1,24 @@ +package org.togetherjava.tjbot.util; + +import net.dv8tion.jda.api.entities.TextChannel; + +import java.util.regex.Pattern; + +/** + * Miscellaneous utilities for JDA entities. + */ +public final class JDAUtils { + private static final Pattern HELP_CHANNEL_PATTERN = Pattern.compile(".*\\Qhelp\\E.*"); + + private JDAUtils() {} + + /** + * Checks if a provided channel is a help channel. + * + * @param textChannel provided channel. + * @return true if the provided channel is a help channel, false otherwise. + */ + public static boolean isAHelpChannel(TextChannel textChannel) { + return HELP_CHANNEL_PATTERN.matcher(textChannel.getName()).matches(); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java b/application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java new file mode 100644 index 0000000000..5afd20afb3 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java @@ -0,0 +1,17 @@ +package org.togetherjava.tjbot.util; + +/** + * Utility class holding various markdown templates to be used in conjunction with String#format() + */ +public final class MessageTemplate { + private MessageTemplate() {} + + public static final String PLAINTEXT_MESSAGE_TEMPLATE = "```\n%s\n```"; + public static final String JAVA_MESSAGE_TEMPLATE = "```java\n%s\n```"; + public static final String KOTLIN_MESSAGE_TEMPLATE = "```kotlin\n%s\n```"; + public static final String SQL_MESSAGE_TEMPLATE = "```sql\n%s\n```"; + public static final String SHELL_MESSAGE_TEMPLATE = "```shell\n%s\n```"; + public static final String JAVASCRIPT_MESSAGE_TEMPLATE = "```javascript\n%s\n```"; + public static final String TAGLIST_MESSAGE_TEMPLATE = + "All available tags:\n" + PLAINTEXT_MESSAGE_TEMPLATE; +} diff --git a/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java b/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java new file mode 100644 index 0000000000..2ad02b6963 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java @@ -0,0 +1,40 @@ +package org.togetherjava.tjbot.util; + +import com.github.freva.asciitable.AsciiTable; +import com.github.freva.asciitable.Column; +import com.github.freva.asciitable.ColumnData; +import com.github.freva.asciitable.HorizontalAlign; + +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; + +/** + * Utility class for representation of data in various formats + */ +public final class PresentationUtils { + private PresentationUtils() {} + + /** + * Flattens a dataFrame to String representation of a table. + * + * @param dataFrame dataframe represented as List> where List represents a + * single row + * @param headers column headers for the table + * @param horizontalAligns column alignment for the table + * @return String representation of the dataFrame in tabular form + */ + public static String dataFrameToAsciiTable(List> dataFrame, String[] headers, + HorizontalAlign[] horizontalAligns) { + Objects.requireNonNull(dataFrame, "DataFrame cannot be null"); + Objects.requireNonNull(headers, "Headers cannot be null"); + Objects.requireNonNull(horizontalAligns, "HorizontalAligns cannot be null"); + Character[] characters = AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS_NO_OUTSIDE_BORDER; + List>> columnData = IntStream.range(0, headers.length) + .mapToObj(i -> new Column().header(headers[i]) + .dataAlign(horizontalAligns[i]) + .>with(row -> row.get(i))) + .toList(); + return AsciiTable.getTable(characters, dataFrame, columnData); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/util/package-info.java b/application/src/main/java/org/togetherjava/tjbot/util/package-info.java new file mode 100644 index 0000000000..4af2cc2303 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/util/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains miscellaneous utilities + */ +package org.togetherjava.tjbot.util; diff --git a/application/src/main/resources/db/V3__Create_Table_Message_Metadata.sql b/application/src/main/resources/db/V3__Create_Table_Message_Metadata.sql new file mode 100644 index 0000000000..8f5052f3c6 --- /dev/null +++ b/application/src/main/resources/db/V3__Create_Table_Message_Metadata.sql @@ -0,0 +1,7 @@ +CREATE TABLE message_metadata( + message_id BIGINT NOT NULL PRIMARY KEY, + guild_id BIGINT NOT NULL, + channel_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + create_timestamp BIGINT NOT NULL +) From 40cddc0c3d62e93904954525847a09ad75a252a7 Mon Sep 17 00:00:00 2001 From: d3vel0per Date: Tue, 26 Oct 2021 11:53:16 +0530 Subject: [PATCH 2/3] Accomodating review comments + upstream changes --- .../org/togetherjava/tjbot/Application.java | 4 +-- .../tjbot/commands/TopHelpers.java | 26 +++++++++++-------- ...r.java => TopHelpersMetadataListener.java} | 14 +++++----- .../tjbot/listener/package-info.java | 2 +- .../org/togetherjava/tjbot/util/JdaUtils.java | 24 +++++++++++++++++ .../tjbot/util/PresentationUtils.java | 5 ++-- 6 files changed, 52 insertions(+), 23 deletions(-) rename application/src/main/java/org/togetherjava/tjbot/listener/{MessageMetadataListener.java => TopHelpersMetadataListener.java} (87%) create mode 100644 application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index bb0fba60f9..f748929fc0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -8,7 +8,7 @@ import org.togetherjava.tjbot.commands.system.CommandSystem; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; -import org.togetherjava.tjbot.listener.MessageMetadataListener; +import org.togetherjava.tjbot.listener.TopHelpersMetadataListener; import javax.security.auth.login.LoginException; import java.io.IOException; @@ -78,7 +78,7 @@ public static void runBot(String token, Path databasePath) { JDA jda = JDABuilder.createDefault(token) .addEventListeners(new CommandSystem(database), - new MessageMetadataListener(database)) + new TopHelpersMetadataListener(database)) .build(); jda.awaitReady(); logger.info("Bot is ready"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java b/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java index f27b2eb4e9..930c849dbb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java @@ -6,6 +6,8 @@ import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jooq.Records; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.util.PresentationUtils; @@ -63,11 +65,11 @@ public void onSlashCommand(SlashCommandEvent event) { }); } - private static long getBoundedCountAsLong(OptionMapping count) { + private static long getBoundedCountAsLong(@Nullable OptionMapping count) { return count == null ? MIN_COUNT : Math.min(count.getAsLong(), MAX_COUNT); } - private static String prettyPrintOutput(List> dataFrame) { + private static String prettyFormatOutput(@NotNull List> dataFrame) { return String.format(PLAINTEXT_MESSAGE_TEMPLATE, dataFrame.isEmpty() ? NO_ENTRIES : PresentationUtils.dataFrameToAsciiTable(dataFrame, @@ -76,19 +78,21 @@ private static String prettyPrintOutput(List> dataFrame) { HorizontalAlign.RIGHT})); } - private static void generateResponse(SlashCommandEvent event, List records) { + private static void generateResponse(@NotNull SlashCommandEvent event, + @NotNull List records) { List userIds = records.stream().map(TopHelperRow::userId).toList(); event.getGuild().retrieveMembersByIds(userIds).onSuccess(members -> { Map activeUserIdToEffectiveNames = members.stream() .collect(Collectors.toMap(Member::getIdLong, Member::getEffectiveName)); - List> topHelpersDataframe = - records.stream() - .map(topHelperRow -> List.of(topHelperRow.serialId.toString(), - activeUserIdToEffectiveNames.getOrDefault(topHelperRow.userId, - "[UNKNOWN]"), - topHelperRow.messageCount.toString())) - .toList(); - event.reply(prettyPrintOutput(topHelpersDataframe)).queue(); + List> topHelpersDataframe = records.stream() + .map(topHelperRow -> List.of(topHelperRow.serialId.toString(), + activeUserIdToEffectiveNames.getOrDefault(topHelperRow.userId, + // Any user who is no more a part of the guild is marked as + // [UNKNOWN] + "[UNKNOWN]"), + topHelperRow.messageCount.toString())) + .toList(); + event.reply(prettyFormatOutput(topHelpersDataframe)).queue(); }); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java b/application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java similarity index 87% rename from application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java rename to application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java index 7b3737837e..0767ee8213 100644 --- a/application/src/main/java/org/togetherjava/tjbot/listener/MessageMetadataListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java @@ -5,7 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.db.Database; -import org.togetherjava.tjbot.util.JDAUtils; +import org.togetherjava.tjbot.util.JdaUtils; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -13,10 +13,10 @@ import static org.togetherjava.tjbot.db.generated.tables.MessageMetadata.MESSAGE_METADATA; /** - * Listener responsible for persistence of text message metadata + * Listener responsible for persistence of text message metadata. */ -public final class MessageMetadataListener extends ListenerAdapter { - private static final Logger logger = LoggerFactory.getLogger(MessageMetadataListener.class); +public final class TopHelpersMetadataListener extends ListenerAdapter { + private static final Logger logger = LoggerFactory.getLogger(TopHelpersMetadataListener.class); private static final int MESSAGE_METADATA_ARCHIVAL_DAYS = 30; @@ -25,9 +25,9 @@ public final class MessageMetadataListener extends ListenerAdapter { /** * Creates a new message metadata listener, using the given database. * - * @param database the database to store message metadata + * @param database the database to store message metadata. */ - public MessageMetadataListener(Database database) { + public TopHelpersMetadataListener(Database database) { this.database = database; } @@ -40,7 +40,7 @@ public MessageMetadataListener(Database database) { public void onGuildMessageReceived(GuildMessageReceivedEvent event) { var channel = event.getChannel(); if (!event.getAuthor().isBot() && !event.isWebhookMessage() - && JDAUtils.isAHelpChannel(channel)) { + && JdaUtils.isAHelpChannel(channel)) { var messageId = event.getMessage().getIdLong(); var guildId = event.getGuild().getIdLong(); var channelId = channel.getIdLong(); diff --git a/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java b/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java index ed09893815..cbef12ab08 100644 --- a/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/listener/package-info.java @@ -1,4 +1,4 @@ /** - * This package contains non-user interactive event listeners + * This package contains non-user interactive event listeners. */ package org.togetherjava.tjbot.listener; diff --git a/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java b/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java new file mode 100644 index 0000000000..32938085cb --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java @@ -0,0 +1,24 @@ +package org.togetherjava.tjbot.util; + +import net.dv8tion.jda.api.entities.TextChannel; + +import java.util.regex.Pattern; + +/** + * Miscellaneous utilities for JDA entities. + */ +public final class JdaUtils { + private static final Pattern HELP_CHANNEL_PATTERN = Pattern.compile(".*\\Qhelp\\E.*"); + + private JdaUtils() {} + + /** + * Checks if a provided channel is a help channel. + * + * @param textChannel provided channel. + * @return true if the provided channel is a help channel, false otherwise. + */ + public static boolean isAHelpChannel(TextChannel textChannel) { + return HELP_CHANNEL_PATTERN.matcher(textChannel.getName()).matches(); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java b/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java index 2ad02b6963..a5d858c343 100644 --- a/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java @@ -4,6 +4,7 @@ import com.github.freva.asciitable.Column; import com.github.freva.asciitable.ColumnData; import com.github.freva.asciitable.HorizontalAlign; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Objects; @@ -24,8 +25,8 @@ private PresentationUtils() {} * @param horizontalAligns column alignment for the table * @return String representation of the dataFrame in tabular form */ - public static String dataFrameToAsciiTable(List> dataFrame, String[] headers, - HorizontalAlign[] horizontalAligns) { + public static String dataFrameToAsciiTable(@NotNull List> dataFrame, + @NotNull String[] headers, @NotNull HorizontalAlign[] horizontalAligns) { Objects.requireNonNull(dataFrame, "DataFrame cannot be null"); Objects.requireNonNull(headers, "Headers cannot be null"); Objects.requireNonNull(horizontalAligns, "HorizontalAligns cannot be null"); From 20dffc70a84706e1204e9423e679586efb2a64c1 Mon Sep 17 00:00:00 2001 From: d3vel0per Date: Tue, 26 Oct 2021 13:44:42 +0530 Subject: [PATCH 3/3] Updates based on review comments --- .../tjbot/commands/TopHelpers.java | 18 +++++++++++------- .../org/togetherjava/tjbot/config/Config.java | 16 +++++++++++++--- .../listener/TopHelpersMetadataListener.java | 2 +- .../org/togetherjava/tjbot/util/JdaUtils.java | 2 +- .../tjbot/util/MessageTemplate.java | 17 ----------------- .../tjbot/util/PresentationUtils.java | 19 +++++++++++++++++++ .../togetherjava/tjbot/util/package-info.java | 2 +- 7 files changed, 46 insertions(+), 30 deletions(-) delete mode 100644 application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java b/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java index 930c849dbb..44ebe38b27 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/TopHelpers.java @@ -6,22 +6,26 @@ import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jooq.Records; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.util.PresentationUtils; +import javax.annotation.ParametersAreNonnullByDefault; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static org.jooq.impl.DSL.*; import static org.togetherjava.tjbot.db.generated.tables.MessageMetadata.MESSAGE_METADATA; -import static org.togetherjava.tjbot.util.MessageTemplate.PLAINTEXT_MESSAGE_TEMPLATE; +/** + * Command to retrieve top helpers for last 30 days. + */ +@ParametersAreNonnullByDefault public final class TopHelpers extends SlashCommandAdapter { + public static final String PLAINTEXT_MESSAGE_TEMPLATE = "```\n%s\n```"; private static final String COUNT_OPTION = "count"; private static final String NO_ENTRIES = "No entries"; @@ -48,8 +52,8 @@ public TopHelpers(Database database) { @SuppressWarnings("squid:S1192") @Override public void onSlashCommand(SlashCommandEvent event) { - Long guildId = event.getGuild().getIdLong(); - Long count = getBoundedCountAsLong(event.getOption(COUNT_OPTION)); + long guildId = event.getGuild().getIdLong(); + long count = getBoundedCountAsLong(event.getOption(COUNT_OPTION)); database.read(dsl -> { List records = dsl.with("TOPHELPERS") .as(select(MESSAGE_METADATA.USER_ID, count().as("COUNT")).from(MESSAGE_METADATA) @@ -69,7 +73,7 @@ private static long getBoundedCountAsLong(@Nullable OptionMapping count) { return count == null ? MIN_COUNT : Math.min(count.getAsLong(), MAX_COUNT); } - private static String prettyFormatOutput(@NotNull List> dataFrame) { + private static String prettyFormatOutput(List> dataFrame) { return String.format(PLAINTEXT_MESSAGE_TEMPLATE, dataFrame.isEmpty() ? NO_ENTRIES : PresentationUtils.dataFrameToAsciiTable(dataFrame, @@ -78,8 +82,8 @@ private static String prettyFormatOutput(@NotNull List> dataFrame) HorizontalAlign.RIGHT})); } - private static void generateResponse(@NotNull SlashCommandEvent event, - @NotNull List records) { + private static void generateResponse(SlashCommandEvent event, + List records) { List userIds = records.stream().map(TopHelperRow::userId).toList(); event.getGuild().retrieveMembersByIds(userIds).onSuccess(members -> { Map activeUserIdToEffectiveNames = members.stream() 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 88097957f4..50a4726be0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -23,16 +23,19 @@ public final class Config { private final String databasePath; private final String projectWebsite; private final String discordGuildInvite; + private final String helpChannelRegex; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private Config(@JsonProperty("token") String token, - @JsonProperty("databasePath") String databasePath, - @JsonProperty("projectWebsite") String projectWebsite, - @JsonProperty("discordGuildInvite") String discordGuildInvite) { + @JsonProperty("databasePath") String databasePath, + @JsonProperty("projectWebsite") String projectWebsite, + @JsonProperty("discordGuildInvite") String discordGuildInvite, + @JsonProperty("helpChannelRegex") String helpChannelRegex) { this.token = token; this.databasePath = databasePath; this.projectWebsite = projectWebsite; this.discordGuildInvite = discordGuildInvite; + this.helpChannelRegex = helpChannelRegex; } /** @@ -94,4 +97,11 @@ public String getProjectWebsite() { public String getDiscordGuildInvite() { return discordGuildInvite; } + + /** + * Gets regex pattern to identify help channels. + * + * @return string representation of regex pattern to identify help channels + */ + public String getHelpChannelRegex() { return helpChannelRegex; } } diff --git a/application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java b/application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java index 0767ee8213..880418c4bc 100644 --- a/application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/listener/TopHelpersMetadataListener.java @@ -40,7 +40,7 @@ public TopHelpersMetadataListener(Database database) { public void onGuildMessageReceived(GuildMessageReceivedEvent event) { var channel = event.getChannel(); if (!event.getAuthor().isBot() && !event.isWebhookMessage() - && JdaUtils.isAHelpChannel(channel)) { + && JdaUtils.isHelpChannel(channel)) { var messageId = event.getMessage().getIdLong(); var guildId = event.getGuild().getIdLong(); var channelId = channel.getIdLong(); diff --git a/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java b/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java index 32938085cb..cba72bb7f4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/util/JdaUtils.java @@ -18,7 +18,7 @@ private JdaUtils() {} * @param textChannel provided channel. * @return true if the provided channel is a help channel, false otherwise. */ - public static boolean isAHelpChannel(TextChannel textChannel) { + public static boolean isHelpChannel(TextChannel textChannel) { return HELP_CHANNEL_PATTERN.matcher(textChannel.getName()).matches(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java b/application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java deleted file mode 100644 index 5afd20afb3..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/util/MessageTemplate.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.togetherjava.tjbot.util; - -/** - * Utility class holding various markdown templates to be used in conjunction with String#format() - */ -public final class MessageTemplate { - private MessageTemplate() {} - - public static final String PLAINTEXT_MESSAGE_TEMPLATE = "```\n%s\n```"; - public static final String JAVA_MESSAGE_TEMPLATE = "```java\n%s\n```"; - public static final String KOTLIN_MESSAGE_TEMPLATE = "```kotlin\n%s\n```"; - public static final String SQL_MESSAGE_TEMPLATE = "```sql\n%s\n```"; - public static final String SHELL_MESSAGE_TEMPLATE = "```shell\n%s\n```"; - public static final String JAVASCRIPT_MESSAGE_TEMPLATE = "```javascript\n%s\n```"; - public static final String TAGLIST_MESSAGE_TEMPLATE = - "All available tags:\n" + PLAINTEXT_MESSAGE_TEMPLATE; -} diff --git a/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java b/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java index a5d858c343..bf23fcd894 100644 --- a/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/util/PresentationUtils.java @@ -18,6 +18,25 @@ private PresentationUtils() {} /** * Flattens a dataFrame to String representation of a table. + * + * eg. + *
+     * {@code
+     *     var dataframe = List.of(List.of("Apple", "Fruit"), List.of("Potato", "Vegetable"));
+     *     var columnHeaders = new String[] {"Item", "Category"};
+     *     var horizontalAlignment = new HorizontalAlign[] {HorizontalAlign.LEFT, HorizontalAlign.LEFT};
+     *     dataFrameToAsciiTable(dataframe, columnHeaders, horizontalAlignment);
+     * }
+     * 
+ * will return: + *
+     * {@code
+     *  Item   | Category
+     * --------+-----------
+     *  Apple  | Fruit
+     *  Potato | Vegetable
+     * }
+     * 
* * @param dataFrame dataframe represented as List> where List represents a * single row diff --git a/application/src/main/java/org/togetherjava/tjbot/util/package-info.java b/application/src/main/java/org/togetherjava/tjbot/util/package-info.java index 4af2cc2303..6937afec0a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/util/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/util/package-info.java @@ -1,4 +1,4 @@ /** - * This package contains miscellaneous utilities + * This package contains miscellaneous utilities. */ package org.togetherjava.tjbot.util;