From 6d8ceeebbf1d1327ea72658f2b29ff89fd60d7f0 Mon Sep 17 00:00:00 2001 From: christolis Date: Thu, 17 Oct 2024 22:56:26 +0300 Subject: [PATCH 01/23] feat: add role application system config --- application/config.json.template | 7 +++++ .../org/togetherjava/tjbot/config/Config.java | 15 +++++++++- .../config/RoleApplicationSystemConfig.java | 30 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java diff --git a/application/config.json.template b/application/config.json.template index a32f8fd440..e29b642a3a 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -175,5 +175,12 @@ "fallbackChannelPattern": "java-news-and-changes", "pollIntervalInMinutes": 10 }, + "roleApplicationSystem": { + "submissionsChannelPattern": "staff-applications", + "defaultQuestion": "What makes you a valuable addition to the team? 😎", + "minimumAnswerLength": 50, + "maximumAnswerLength": 500, + "applicationSubmitCooldownMinutes": 5 + }, "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 79c04e6cad..7ff4a80f38 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -48,6 +48,7 @@ public final class Config { private final RSSFeedsConfig rssFeedsConfig; private final String selectRolesChannelPattern; private final String memberCountCategoryPattern; + private final RoleApplicationSystemConfig roleApplicationSystemConfig; @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @@ -100,7 +101,9 @@ private Config(@JsonProperty(value = "token", required = true) String token, required = true) FeatureBlacklistConfig featureBlacklistConfig, @JsonProperty(value = "rssConfig", required = true) RSSFeedsConfig rssFeedsConfig, @JsonProperty(value = "selectRolesChannelPattern", - required = true) String selectRolesChannelPattern) { + required = true) String selectRolesChannelPattern, + @JsonProperty(value = "roleApplicationSystem", + required = true) RoleApplicationSystemConfig roleApplicationSystemConfig) { this.token = Objects.requireNonNull(token); this.githubApiKey = Objects.requireNonNull(githubApiKey); this.databasePath = Objects.requireNonNull(databasePath); @@ -135,6 +138,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig); this.rssFeedsConfig = Objects.requireNonNull(rssFeedsConfig); this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern); + this.roleApplicationSystemConfig = roleApplicationSystemConfig; } /** @@ -437,6 +441,15 @@ public String getMemberCountCategoryPattern() { return memberCountCategoryPattern; } + /** + * The configuration related to the application form. + * + * @return the application form config + */ + public RoleApplicationSystemConfig getRoleApplicationSystemConfig() { + return roleApplicationSystemConfig; + } + /** * Gets the RSS feeds configuration. * diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java new file mode 100644 index 0000000000..1e3644c149 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java @@ -0,0 +1,30 @@ +package org.togetherjava.tjbot.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +/** + * Represents the configuration for an application form, including roles and application channel + * pattern. + */ +public record RoleApplicationSystemConfig( + @JsonProperty(value = "submissionsChannelPattern", + required = true) String submissionsChannelPattern, + @JsonProperty(value = "defaultQuestion", required = true) String defaultQuestion, + @JsonProperty(value = "minimumAnswerLength", required = true) int minimumAnswerLength, + @JsonProperty(value = "maximumAnswerLength", required = true) int maximumAnswerLength, + @JsonProperty(value = "applicationSubmitCooldownMinutes", + required = true) int applicationSubmitCooldownMinutes) { + + /** + * Constructs an instance of {@link RoleApplicationSystemConfig} with the provided parameters. + * + * @param submissionsChannelPattern the pattern used to identify the application channel + * @param defaultQuestion the default question for the form + */ + public RoleApplicationSystemConfig { + Objects.requireNonNull(submissionsChannelPattern); + Objects.requireNonNull(defaultQuestion); + } +} From a42a4285d073fe0cd170df717ed3e9585b7f729b Mon Sep 17 00:00:00 2001 From: christolis Date: Thu, 17 Oct 2024 22:57:12 +0300 Subject: [PATCH 02/23] feat: create role application command Co-authored-by: Suraj Kumar <76599223+surajkumar@users.noreply.github.com> --- .../togetherjava/tjbot/features/Features.java | 2 + .../ApplicationApplyHandler.java | 132 ++++++++ .../ApplicationCreateCommand.java | 297 ++++++++++++++++++ .../roleapplication/package-info.java | 12 + 4 files changed, 443 insertions(+) create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java 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 99241f5689..959251e2b0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -66,6 +66,7 @@ import org.togetherjava.tjbot.features.projects.ProjectsThreadCreatedListener; import org.togetherjava.tjbot.features.reminder.RemindRoutine; import org.togetherjava.tjbot.features.reminder.ReminderCommand; +import org.togetherjava.tjbot.features.roleapplication.ApplicationCreateCommand; import org.togetherjava.tjbot.features.system.BotCore; import org.togetherjava.tjbot.features.system.LogLevelCommand; import org.togetherjava.tjbot.features.tags.TagCommand; @@ -197,6 +198,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new BookmarksCommand(bookmarksSystem)); features.add(new ChatGptCommand(chatGptService, helpSystemHelper)); features.add(new JShellCommand(jshellEval)); + features.add(new ApplicationCreateCommand(config)); FeatureBlacklist> blacklist = blacklistConfig.normal(); return blacklist.filterStream(features.stream(), Object::getClass).toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java new file mode 100644 index 0000000000..10f6ec37ee --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java @@ -0,0 +1,132 @@ +package org.togetherjava.tjbot.features.roleapplication; + +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.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.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; + +import org.togetherjava.tjbot.config.RoleApplicationSystemConfig; + +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * Handles the actual process of submitting role applications. + *

+ * This class is responsible for managing application submissions via modal interactions, ensuring + * that submissions are sent to the appropriate application channel, and enforcing cooldowns for + * users to prevent spamming. + */ +public class ApplicationApplyHandler { + + private final Cache applicationSubmitCooldown; + private final Predicate applicationChannelPattern; + private final RoleApplicationSystemConfig roleApplicationSystemConfig; + + /** + * Constructs a new {@code ApplicationApplyHandler} instance. + * + * @param roleApplicationSystemConfig the configuration that contains the details for the application form + * including the cooldown duration and channel pattern. + */ + public ApplicationApplyHandler(RoleApplicationSystemConfig roleApplicationSystemConfig) { + this.roleApplicationSystemConfig = roleApplicationSystemConfig; + this.applicationChannelPattern = + Pattern.compile(roleApplicationSystemConfig.submissionsChannelPattern()).asMatchPredicate(); + + final Duration applicationSubmitCooldownDuration = + Duration.ofMinutes(roleApplicationSystemConfig.applicationSubmitCooldownMinutes()); + applicationSubmitCooldown = + Caffeine.newBuilder().expireAfterWrite(applicationSubmitCooldownDuration).build(); + } + + /** + * Sends the result of an application submission to the designated application channel in the + * guild. + *

+ * The {@code args} parameter should contain the applicant's name and the role they are applying + * for. + * + * @param event the modal interaction event triggering the application submission + * @param args the arguments provided in the application submission + * @param answer the answer provided by the applicant to the default question + */ + protected void sendApplicationResult(final ModalInteractionEvent event, List args, + String answer) { + Guild guild = event.getGuild(); + if (args.size() != 2 || guild == null) { + return; + } + + Optional applicationChannel = getApplicationChannel(guild); + if (applicationChannel.isEmpty()) { + return; + } + + User applicant = event.getUser(); + EmbedBuilder embed = + new EmbedBuilder().setAuthor(applicant.getName(), null, applicant.getAvatarUrl()) + .setColor(ApplicationCreateCommand.AMBIENT_COLOR) + .setTimestamp(Instant.now()) + .setFooter("Submitted at"); + + String roleString = args.getLast(); + MessageEmbed.Field roleField = new MessageEmbed.Field("Role", roleString, false); + embed.addField(roleField); + + MessageEmbed.Field answerField = + new MessageEmbed.Field(roleApplicationSystemConfig.defaultQuestion(), answer, false); + embed.addField(answerField); + + applicationChannel.get().sendMessageEmbeds(embed.build()).queue(); + } + + /** + * Retrieves the application channel from the given {@link Guild}. + * + * @param guild the guild from which to retrieve the application channel + * @return an {@link Optional} containing the {@link TextChannel} representing the application + * channel, or an empty {@link Optional} if no such channel is found + */ + private Optional getApplicationChannel(Guild guild) { + return guild.getChannels() + .stream() + .filter(channel -> applicationChannelPattern.test(channel.getName())) + .filter(channel -> channel.getType().isMessage()) + .map(TextChannel.class::cast) + .findFirst(); + } + + public Cache getApplicationSubmitCooldown() { + return applicationSubmitCooldown; + } + + protected void submitApplicationFromModalInteraction(ModalInteractionEvent event, + List args) { + Guild guild = event.getGuild(); + + if (guild == null) { + return; + } + + ModalMapping modalAnswer = event.getValues().getFirst(); + + sendApplicationResult(event, args, modalAnswer.getAsString()); + event.reply("Your application has been submitted. Thank you for applying! 😎") + .setEphemeral(true) + .queue(); + + applicationSubmitCooldown.put(event.getMember(), OffsetDateTime.now()); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java new file mode 100644 index 0000000000..56d66b3fd6 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java @@ -0,0 +1,297 @@ +package org.togetherjava.tjbot.features.roleapplication; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; +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.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.CommandInteraction; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.selections.SelectOption; +import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.interactions.modals.Modal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.togetherjava.tjbot.config.Config; +import org.togetherjava.tjbot.config.RoleApplicationSystemConfig; +import org.togetherjava.tjbot.features.CommandVisibility; +import org.togetherjava.tjbot.features.SlashCommandAdapter; +import org.togetherjava.tjbot.features.componentids.Lifespan; + +import javax.annotation.Nullable; + +import java.awt.Color; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +/** + * Represents a command to create an application form for members to apply for roles. + *

+ * This command is designed to generate an application form for members to apply for roles within a + * guild. + */ +public class ApplicationCreateCommand extends SlashCommandAdapter { + private static final Logger logger = LoggerFactory.getLogger(ApplicationCreateCommand.class); + + protected static final Color AMBIENT_COLOR = new Color(24, 221, 136, 255); + private static final int OPTIONAL_ROLES_AMOUNT = 5; + private static final String ROLE_COMPONENT_ID_HEADER = "application-create"; + private static final String VALUE_DELIMITER = "_"; + private static final int ARG_COUNT = 3; + + private final ApplicationApplyHandler applicationApplyHandler; + private final RoleApplicationSystemConfig roleApplicationSystemConfig; + + /** + * Constructs a new {@link ApplicationCreateCommand} with the specified configuration. + *

+ * This command is designed to generate an application form for members to apply for roles. + * + * @param config the configuration containing the settings for the application form + */ + public ApplicationCreateCommand(Config config) { + super("application-form", "Generates an application form for members to apply for roles.", + CommandVisibility.GUILD); + + this.roleApplicationSystemConfig = config.getRoleApplicationSystemConfig(); + + generateRoleOptions(getData()); + applicationApplyHandler = new ApplicationApplyHandler(roleApplicationSystemConfig); + } + + /** + * Populates a {@link SlashCommandData} object with the proper arguments. + * + * @param data the object to populate + */ + private void generateRoleOptions(SlashCommandData data) { + IntStream.range(1, OPTIONAL_ROLES_AMOUNT + 1).forEach(index -> { + data.addOption(OptionType.STRING, generateOptionId("title", index), + "The title of the role"); + data.addOption(OptionType.STRING, generateOptionId("description", index), + "The description of the role"); + data.addOption(OptionType.STRING, generateOptionId("emoji", index), + "The emoji of the role"); + }); + } + + private static String generateOptionId(String name, int id) { + return "%s%s%d".formatted(name, VALUE_DELIMITER, id); + } + + @Override + public void onSlashCommand(SlashCommandInteractionEvent event) { + if (!handleHasPermissions(event)) { + return; + } + + final List optionMappings = event.getInteraction().getOptions(); + if (optionMappings.isEmpty()) { + event.reply("You have to select at least one role.").setEphemeral(true).queue(); + return; + } + + long incorrectArgsCount = getIncorrectRoleArgsCount(optionMappings); + if (incorrectArgsCount > 0) { + event.reply("Missing information for %d roles.".formatted(incorrectArgsCount)) + .setEphemeral(true) + .queue(); + return; + } + + sendMenu(event); + } + + @Override + public void onStringSelectSelection(StringSelectInteractionEvent event, List args) { + SelectOption selectOption = event.getSelectedOptions().getFirst(); + + if (selectOption == null) { + return; + } + + OffsetDateTime timeSentCache = applicationApplyHandler.getApplicationSubmitCooldown() + .getIfPresent(event.getMember()); + if (timeSentCache != null) { + Duration duration = Duration.between(timeSentCache, OffsetDateTime.now()); + long remainingMinutes = + roleApplicationSystemConfig.applicationSubmitCooldownMinutes() - duration.toMinutes(); + + if (duration.toMinutes() < roleApplicationSystemConfig.applicationSubmitCooldownMinutes()) { + event + .reply("Please wait %d minutes before sending a new application form." + .formatted(remainingMinutes)) + .setEphemeral(true) + .queue(); + return; + } + } + + String questionLabel = roleApplicationSystemConfig.defaultQuestion(); + if (questionLabel.length() > TextInput.MAX_LABEL_LENGTH) { + questionLabel = questionLabel.substring(0, TextInput.MAX_LABEL_LENGTH); + } + + TextInput body = TextInput + .create(generateComponentId(event.getUser().getId()), questionLabel, + TextInputStyle.PARAGRAPH) + .setRequired(true) + .setRequiredRange(roleApplicationSystemConfig.minimumAnswerLength(), roleApplicationSystemConfig.maximumAnswerLength()) + .setPlaceholder("Enter your answer here") + .build(); + + EmojiUnion emoji = selectOption.getEmoji(); + String roleDisplayName; + + if (emoji == null) { + roleDisplayName = selectOption.getLabel(); + } else { + roleDisplayName = "%s %s".formatted(emoji.getFormatted(), selectOption.getLabel()); + } + + Modal modal = Modal + .create(generateComponentId(event.getUser().getId(), roleDisplayName), + String.format("Application form - %s", selectOption.getLabel())) + .addActionRow(ActionRow.of(body).getComponents()) + .build(); + + event.replyModal(modal).queue(); + } + + /** + * Checks a given list of passed arguments (from a user) and calculates how many roles have + * missing data. + * + * @param args the list of passed arguments + * @return the amount of roles with missing data + */ + private static long getIncorrectRoleArgsCount(final List args) { + final Map frequencyMap = new HashMap<>(); + + args.stream() + .map(OptionMapping::getName) + .map(name -> name.split(VALUE_DELIMITER)[1]) + .forEach(number -> frequencyMap.merge(number, 1, Integer::sum)); + + return frequencyMap.values().stream().filter(value -> value != 3).count(); + } + + /** + * Populates a {@link StringSelectMenu.Builder} with application roles. + * + * @param menuBuilder the menu builder to populate + * @param args the arguments which contain data about the roles + */ + private void addRolesToMenu(StringSelectMenu.Builder menuBuilder, + final List args) { + final Map roles = new HashMap<>(); + + for (int i = 0; i < args.size(); i += ARG_COUNT) { + OptionMapping optionTitle = args.get(i); + OptionMapping optionDescription = args.get(i + 1); + OptionMapping optionEmoji = args.get(i + 2); + + roles.put(i, + new MenuRole(optionTitle.getAsString(), + generateComponentId(ROLE_COMPONENT_ID_HEADER, + optionTitle.getAsString()), + optionDescription.getAsString(), + Emoji.fromFormatted(optionEmoji.getAsString()))); + } + + roles.values() + .forEach(role -> menuBuilder.addOption(role.title(), role.value(), role.description(), + role.emoji())); + } + + private boolean handleHasPermissions(SlashCommandInteractionEvent event) { + Member member = event.getMember(); + Guild guild = event.getGuild(); + + if (member == null || guild == null) { + return false; + } + + if (!member.hasPermission(Permission.MANAGE_ROLES)) { + event.reply("You do not have the required manage role permission to use this command") + .setEphemeral(true) + .queue(); + return false; + } + + Member selfMember = guild.getSelfMember(); + if (!selfMember.hasPermission(Permission.MANAGE_ROLES)) { + event.reply( + "Sorry, but I was not set up correctly. I need the manage role permissions for this.") + .setEphemeral(true) + .queue(); + logger.error("The bot requires the manage role permissions for /{}.", getName()); + return false; + } + + return true; + } + + /** + * Sends the initial embed and a button which displays role openings. + * + * @param event the command interaction event triggering the menu + */ + private void sendMenu(final CommandInteraction event) { + MessageEmbed embed = createApplicationEmbed(); + + StringSelectMenu.Builder menuBuilder = StringSelectMenu + .create(generateComponentId(Lifespan.PERMANENT, event.getUser().getId())) + .setPlaceholder("Select role to apply for") + .setRequiredRange(1, 1); + + addRolesToMenu(menuBuilder, event.getOptions()); + + event.replyEmbeds(embed).addActionRow(menuBuilder.build()).queue(); + } + + private static MessageEmbed createApplicationEmbed() { + return new EmbedBuilder().setTitle("Apply for roles") + .setDescription( + """ + We are always looking for community members that want to contribute to our community \ + and take charge. If you are interested, you can apply for various positions here!""") + .setColor(AMBIENT_COLOR) + .build(); + } + + public ApplicationApplyHandler getApplicationApplyHandler() { + return applicationApplyHandler; + } + + @Override + public void onModalSubmitted(ModalInteractionEvent event, List args) { + getApplicationApplyHandler().submitApplicationFromModalInteraction(event, args); + } + + /** + * Wrapper class which represents a menu role for the application create command. + *

+ * The reason this exists is due to the fact that {@link StringSelectMenu.Builder} does not have + * a method which takes emojis as input as of writing this, so we have to elegantly pass in + * custom data from this POJO. + */ + private record MenuRole(String title, String value, String description, @Nullable Emoji emoji) { + + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java new file mode 100644 index 0000000000..ac6ed5b52b --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java @@ -0,0 +1,12 @@ +/** + * This packages offers all the functionality for the application-create command as well as the + * application system. The core class is + * {@link org.togetherjava.tjbot.features.roleapplication.ApplicationCreateCommand}. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package org.togetherjava.tjbot.features.roleapplication; + +import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; From dcb241c24575028814a39bdfb7f731d61de81e4b Mon Sep 17 00:00:00 2001 From: christolis Date: Thu, 17 Oct 2024 23:23:09 +0300 Subject: [PATCH 03/23] fix(handle-permissions): remove unnecessary check This removes the permission check for the bot to have the `MANAGE_ROLES` permission in order to execute the command. --- .../roleapplication/ApplicationCreateCommand.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java index 56d66b3fd6..87e1abbd53 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java @@ -234,16 +234,6 @@ private boolean handleHasPermissions(SlashCommandInteractionEvent event) { return false; } - Member selfMember = guild.getSelfMember(); - if (!selfMember.hasPermission(Permission.MANAGE_ROLES)) { - event.reply( - "Sorry, but I was not set up correctly. I need the manage role permissions for this.") - .setEphemeral(true) - .queue(); - logger.error("The bot requires the manage role permissions for /{}.", getName()); - return false; - } - return true; } From 375ae45e1ac681362bb92df86e819e484241733f Mon Sep 17 00:00:00 2001 From: christolis Date: Thu, 17 Oct 2024 23:34:24 +0300 Subject: [PATCH 04/23] refactor: make cooldown minutes check into separate method --- .../ApplicationApplyHandler.java | 21 ++++++++++--- .../ApplicationCreateCommand.java | 31 +++++++------------ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java index 10f6ec37ee..84639f06b8 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java @@ -37,13 +37,14 @@ public class ApplicationApplyHandler { /** * Constructs a new {@code ApplicationApplyHandler} instance. * - * @param roleApplicationSystemConfig the configuration that contains the details for the application form - * including the cooldown duration and channel pattern. + * @param roleApplicationSystemConfig the configuration that contains the details for the + * application form including the cooldown duration and channel pattern. */ public ApplicationApplyHandler(RoleApplicationSystemConfig roleApplicationSystemConfig) { this.roleApplicationSystemConfig = roleApplicationSystemConfig; this.applicationChannelPattern = - Pattern.compile(roleApplicationSystemConfig.submissionsChannelPattern()).asMatchPredicate(); + Pattern.compile(roleApplicationSystemConfig.submissionsChannelPattern()) + .asMatchPredicate(); final Duration applicationSubmitCooldownDuration = Duration.ofMinutes(roleApplicationSystemConfig.applicationSubmitCooldownMinutes()); @@ -85,8 +86,8 @@ protected void sendApplicationResult(final ModalInteractionEvent event, List args) { SelectOption selectOption = event.getSelectedOptions().getFirst(); + Member member = event.getMember(); - if (selectOption == null) { + if (selectOption == null || member == null) { return; } - OffsetDateTime timeSentCache = applicationApplyHandler.getApplicationSubmitCooldown() - .getIfPresent(event.getMember()); - if (timeSentCache != null) { - Duration duration = Duration.between(timeSentCache, OffsetDateTime.now()); - long remainingMinutes = - roleApplicationSystemConfig.applicationSubmitCooldownMinutes() - duration.toMinutes(); - - if (duration.toMinutes() < roleApplicationSystemConfig.applicationSubmitCooldownMinutes()) { - event - .reply("Please wait %d minutes before sending a new application form." - .formatted(remainingMinutes)) - .setEphemeral(true) - .queue(); - return; - } + long remainingMinutes = applicationApplyHandler.getMemberCooldownMinutes(member); + if (remainingMinutes > 0) { + event + .reply("Please wait %d minutes before sending a new application form." + .formatted(remainingMinutes)) + .setEphemeral(true) + .queue(); + return; } String questionLabel = roleApplicationSystemConfig.defaultQuestion(); @@ -151,7 +143,8 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List Date: Thu, 17 Oct 2024 23:40:31 +0300 Subject: [PATCH 05/23] feat: move substring of label into config record --- application/config.json.template | 2 +- .../tjbot/config/RoleApplicationSystemConfig.java | 5 +++++ .../features/roleapplication/ApplicationCreateCommand.java | 7 +------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/application/config.json.template b/application/config.json.template index e29b642a3a..76e072b18c 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -177,7 +177,7 @@ }, "roleApplicationSystem": { "submissionsChannelPattern": "staff-applications", - "defaultQuestion": "What makes you a valuable addition to the team? 😎", + "defaultQuestion": "What makes you a good addition to the team?", "minimumAnswerLength": 50, "maximumAnswerLength": 500, "applicationSubmitCooldownMinutes": 5 diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java index 1e3644c149..e8e20e0c85 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java @@ -1,6 +1,7 @@ package org.togetherjava.tjbot.config; import com.fasterxml.jackson.annotation.JsonProperty; +import net.dv8tion.jda.api.interactions.components.text.TextInput; import java.util.Objects; @@ -26,5 +27,9 @@ public record RoleApplicationSystemConfig( public RoleApplicationSystemConfig { Objects.requireNonNull(submissionsChannelPattern); Objects.requireNonNull(defaultQuestion); + + if (defaultQuestion.length() > TextInput.MAX_LABEL_LENGTH) { + throw new IllegalArgumentException("defaultQuestion length is too long! Cannot be greater than %d".formatted(TextInput.MAX_LABEL_LENGTH)); + } } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java index bae54a5cb5..3d7b0fea6a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java @@ -134,13 +134,8 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List TextInput.MAX_LABEL_LENGTH) { - questionLabel = questionLabel.substring(0, TextInput.MAX_LABEL_LENGTH); - } - TextInput body = TextInput - .create(generateComponentId(event.getUser().getId()), questionLabel, + .create(generateComponentId(event.getUser().getId()), roleApplicationSystemConfig.defaultQuestion(), TextInputStyle.PARAGRAPH) .setRequired(true) .setRequiredRange(roleApplicationSystemConfig.minimumAnswerLength(), From 0a04d90b8953e33d74abbeb458976585575a7950 Mon Sep 17 00:00:00 2001 From: christolis Date: Thu, 17 Oct 2024 23:47:39 +0300 Subject: [PATCH 06/23] docs: rewrite documentation for `RoleApplicationSystemConfig` --- .../tjbot/config/RoleApplicationSystemConfig.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java index e8e20e0c85..1ff493e5a1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java @@ -8,6 +8,12 @@ /** * Represents the configuration for an application form, including roles and application channel * pattern. + * + * @param submissionsChannelPattern the pattern used to identify the submissions channel where applications are sent + * @param defaultQuestion the default question that will be asked in the role application form + * @param minimumAnswerLength the minimum number of characters required for the applicant's answer + * @param maximumAnswerLength the maximum number of characters allowed for the applicant's answer + * @param applicationSubmitCooldownMinutes the cooldown time in minutes before the user can submit another application */ public record RoleApplicationSystemConfig( @JsonProperty(value = "submissionsChannelPattern", @@ -20,9 +26,9 @@ public record RoleApplicationSystemConfig( /** * Constructs an instance of {@link RoleApplicationSystemConfig} with the provided parameters. - * - * @param submissionsChannelPattern the pattern used to identify the application channel - * @param defaultQuestion the default question for the form + *

+ * This constructor ensures that {@code submissionsChannelPattern} and {@code defaultQuestion} + * are not null and that the length of the {@code defaultQuestion} does not exceed the maximum allowed length. */ public RoleApplicationSystemConfig { Objects.requireNonNull(submissionsChannelPattern); From 8ab2f40151334da6d753e24714d3e46faa98af97 Mon Sep 17 00:00:00 2001 From: christolis Date: Thu, 17 Oct 2024 23:55:16 +0300 Subject: [PATCH 07/23] style: run spotlessApply gradle task --- .../tjbot/config/RoleApplicationSystemConfig.java | 13 +++++++++---- .../roleapplication/ApplicationApplyHandler.java | 1 - .../roleapplication/ApplicationCreateCommand.java | 8 ++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java index 1ff493e5a1..8446484585 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java @@ -9,11 +9,13 @@ * Represents the configuration for an application form, including roles and application channel * pattern. * - * @param submissionsChannelPattern the pattern used to identify the submissions channel where applications are sent + * @param submissionsChannelPattern the pattern used to identify the submissions channel where + * applications are sent * @param defaultQuestion the default question that will be asked in the role application form * @param minimumAnswerLength the minimum number of characters required for the applicant's answer * @param maximumAnswerLength the maximum number of characters allowed for the applicant's answer - * @param applicationSubmitCooldownMinutes the cooldown time in minutes before the user can submit another application + * @param applicationSubmitCooldownMinutes the cooldown time in minutes before the user can submit + * another application */ public record RoleApplicationSystemConfig( @JsonProperty(value = "submissionsChannelPattern", @@ -28,14 +30,17 @@ public record RoleApplicationSystemConfig( * Constructs an instance of {@link RoleApplicationSystemConfig} with the provided parameters. *

* This constructor ensures that {@code submissionsChannelPattern} and {@code defaultQuestion} - * are not null and that the length of the {@code defaultQuestion} does not exceed the maximum allowed length. + * are not null and that the length of the {@code defaultQuestion} does not exceed the maximum + * allowed length. */ public RoleApplicationSystemConfig { Objects.requireNonNull(submissionsChannelPattern); Objects.requireNonNull(defaultQuestion); if (defaultQuestion.length() > TextInput.MAX_LABEL_LENGTH) { - throw new IllegalArgumentException("defaultQuestion length is too long! Cannot be greater than %d".formatted(TextInput.MAX_LABEL_LENGTH)); + throw new IllegalArgumentException( + "defaultQuestion length is too long! Cannot be greater than %d" + .formatted(TextInput.MAX_LABEL_LENGTH)); } } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java index 84639f06b8..0c8b9443d5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java @@ -29,7 +29,6 @@ * users to prevent spamming. */ public class ApplicationApplyHandler { - private final Cache applicationSubmitCooldown; private final Predicate applicationChannelPattern; private final RoleApplicationSystemConfig roleApplicationSystemConfig; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java index 3d7b0fea6a..d74a25af96 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java @@ -20,8 +20,6 @@ import net.dv8tion.jda.api.interactions.components.text.TextInput; import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; import net.dv8tion.jda.api.interactions.modals.Modal; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.RoleApplicationSystemConfig; @@ -44,8 +42,6 @@ * guild. */ public class ApplicationCreateCommand extends SlashCommandAdapter { - private static final Logger logger = LoggerFactory.getLogger(ApplicationCreateCommand.class); - protected static final Color AMBIENT_COLOR = new Color(24, 221, 136, 255); private static final int OPTIONAL_ROLES_AMOUNT = 5; private static final String ROLE_COMPONENT_ID_HEADER = "application-create"; @@ -135,8 +131,8 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List Date: Sat, 19 Oct 2024 22:43:32 +0300 Subject: [PATCH 08/23] feat(cooldown): use the proper word depending on count --- .../ApplicationCreateCommand.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java index d74a25af96..f0460beb1e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java @@ -121,10 +121,12 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List 0) { event - .reply("Please wait %d minutes before sending a new application form." - .formatted(remainingMinutes)) + .reply("Please wait %d %s before sending a new application form." + .formatted(remainingMinutes, correctMinutesWord)) .setEphemeral(true) .queue(); return; @@ -157,6 +159,23 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 09/23] application-create: hardcode certain config values Certain configuration values pertaining to the application form creating and handling for custom roles do not need to be there and are deemed more appropriate as hardcoded values inside the code itself. Remove the configuration entries from the `config.json.template` as well as references to such keys in the codebase and introduce them as static constants in the appropriate class files. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- application/config.json.template | 5 +---- .../tjbot/config/RoleApplicationSystemConfig.java | 10 +--------- .../roleapplication/ApplicationApplyHandler.java | 7 ++++--- .../roleapplication/ApplicationCreateCommand.java | 5 +++-- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/application/config.json.template b/application/config.json.template index 76e072b18c..fdbd8a1870 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -177,10 +177,7 @@ }, "roleApplicationSystem": { "submissionsChannelPattern": "staff-applications", - "defaultQuestion": "What makes you a good addition to the team?", - "minimumAnswerLength": 50, - "maximumAnswerLength": 500, - "applicationSubmitCooldownMinutes": 5 + "defaultQuestion": "What makes you a good addition to the team?" }, "memberCountCategoryPattern": "Info" } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java index 8446484585..0979556b58 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/RoleApplicationSystemConfig.java @@ -12,19 +12,11 @@ * @param submissionsChannelPattern the pattern used to identify the submissions channel where * applications are sent * @param defaultQuestion the default question that will be asked in the role application form - * @param minimumAnswerLength the minimum number of characters required for the applicant's answer - * @param maximumAnswerLength the maximum number of characters allowed for the applicant's answer - * @param applicationSubmitCooldownMinutes the cooldown time in minutes before the user can submit - * another application */ public record RoleApplicationSystemConfig( @JsonProperty(value = "submissionsChannelPattern", required = true) String submissionsChannelPattern, - @JsonProperty(value = "defaultQuestion", required = true) String defaultQuestion, - @JsonProperty(value = "minimumAnswerLength", required = true) int minimumAnswerLength, - @JsonProperty(value = "maximumAnswerLength", required = true) int maximumAnswerLength, - @JsonProperty(value = "applicationSubmitCooldownMinutes", - required = true) int applicationSubmitCooldownMinutes) { + @JsonProperty(value = "defaultQuestion", required = true) String defaultQuestion) { /** * Constructs an instance of {@link RoleApplicationSystemConfig} with the provided parameters. diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java index 0c8b9443d5..1b0072c4c1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java @@ -29,6 +29,8 @@ * users to prevent spamming. */ public class ApplicationApplyHandler { + private static final int APPLICATION_SUBMIT_COOLDOWN_MINUTES = 5; + private final Cache applicationSubmitCooldown; private final Predicate applicationChannelPattern; private final RoleApplicationSystemConfig roleApplicationSystemConfig; @@ -46,7 +48,7 @@ public ApplicationApplyHandler(RoleApplicationSystemConfig roleApplicationSystem .asMatchPredicate(); final Duration applicationSubmitCooldownDuration = - Duration.ofMinutes(roleApplicationSystemConfig.applicationSubmitCooldownMinutes()); + Duration.ofMinutes(APPLICATION_SUBMIT_COOLDOWN_MINUTES); applicationSubmitCooldown = Caffeine.newBuilder().expireAfterWrite(applicationSubmitCooldownDuration).build(); } @@ -134,8 +136,7 @@ protected long getMemberCooldownMinutes(Member member) { OffsetDateTime timeSentCache = getApplicationSubmitCooldown().getIfPresent(member); if (timeSentCache != null) { Duration duration = Duration.between(timeSentCache, OffsetDateTime.now()); - return roleApplicationSystemConfig.applicationSubmitCooldownMinutes() - - duration.toMinutes(); + return APPLICATION_SUBMIT_COOLDOWN_MINUTES - duration.toMinutes(); } return 0L; } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java index f0460beb1e..34224e5bb6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationCreateCommand.java @@ -47,6 +47,8 @@ public class ApplicationCreateCommand extends SlashCommandAdapter { private static final String ROLE_COMPONENT_ID_HEADER = "application-create"; private static final String VALUE_DELIMITER = "_"; private static final int ARG_COUNT = 3; + private static final int MINIMUM_ANSWER_LENGTH = 50; + private static final int MAXIMUM_ANSWER_LENGTH = 500; private final ApplicationApplyHandler applicationApplyHandler; private final RoleApplicationSystemConfig roleApplicationSystemConfig; @@ -136,8 +138,7 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 10/23] refactor: rename to CreateRoleApplicationCommand Rename the command to match the package name that this class is located in. Primarily for organizational purposes and to not come up with differnt names every time we reference this feature. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../main/java/org/togetherjava/tjbot/features/Features.java | 4 ++-- .../features/roleapplication/ApplicationApplyHandler.java | 2 +- ...CreateCommand.java => CreateRoleApplicationCommand.java} | 6 +++--- .../tjbot/features/roleapplication/package-info.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename application/src/main/java/org/togetherjava/tjbot/features/roleapplication/{ApplicationCreateCommand.java => CreateRoleApplicationCommand.java} (98%) 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 959251e2b0..578cdd2357 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -66,7 +66,7 @@ import org.togetherjava.tjbot.features.projects.ProjectsThreadCreatedListener; import org.togetherjava.tjbot.features.reminder.RemindRoutine; import org.togetherjava.tjbot.features.reminder.ReminderCommand; -import org.togetherjava.tjbot.features.roleapplication.ApplicationCreateCommand; +import org.togetherjava.tjbot.features.roleapplication.CreateRoleApplicationCommand; import org.togetherjava.tjbot.features.system.BotCore; import org.togetherjava.tjbot.features.system.LogLevelCommand; import org.togetherjava.tjbot.features.tags.TagCommand; @@ -198,7 +198,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new BookmarksCommand(bookmarksSystem)); features.add(new ChatGptCommand(chatGptService, helpSystemHelper)); features.add(new JShellCommand(jshellEval)); - features.add(new ApplicationCreateCommand(config)); + features.add(new CreateRoleApplicationCommand(config)); FeatureBlacklist> blacklist = blacklistConfig.normal(); return blacklist.filterStream(features.stream(), Object::getClass).toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java index 1b0072c4c1..98b0d2cbc3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java @@ -79,7 +79,7 @@ protected void sendApplicationResult(final ModalInteractionEvent event, List * This command is designed to generate an application form for members to apply for roles. * * @param config the configuration containing the settings for the application form */ - public ApplicationCreateCommand(Config config) { + public CreateRoleApplicationCommand(Config config) { super("application-form", "Generates an application form for members to apply for roles.", CommandVisibility.GUILD); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java index ac6ed5b52b..cc527ac009 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/package-info.java @@ -1,7 +1,7 @@ /** * This packages offers all the functionality for the application-create command as well as the * application system. The core class is - * {@link org.togetherjava.tjbot.features.roleapplication.ApplicationCreateCommand}. + * {@link org.togetherjava.tjbot.features.roleapplication.CreateRoleApplicationCommand}. */ @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault From ee96742d90fd332740d42495ee2f8b092cba2300 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 11/23] refactor: rename to RoleApplicationHandler Rename the handler to match the package name that this class is located in. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../roleapplication/CreateRoleApplicationCommand.java | 10 +++++----- ...onApplyHandler.java => RoleApplicationHandler.java} | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) rename application/src/main/java/org/togetherjava/tjbot/features/roleapplication/{ApplicationApplyHandler.java => RoleApplicationHandler.java} (96%) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 5a282a7858..2f66e80b7e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -50,7 +50,7 @@ public class CreateRoleApplicationCommand extends SlashCommandAdapter { private static final int MINIMUM_ANSWER_LENGTH = 50; private static final int MAXIMUM_ANSWER_LENGTH = 500; - private final ApplicationApplyHandler applicationApplyHandler; + private final RoleApplicationHandler roleApplicationHandler; private final RoleApplicationSystemConfig roleApplicationSystemConfig; /** @@ -67,7 +67,7 @@ public CreateRoleApplicationCommand(Config config) { this.roleApplicationSystemConfig = config.getRoleApplicationSystemConfig(); generateRoleOptions(getData()); - applicationApplyHandler = new ApplicationApplyHandler(roleApplicationSystemConfig); + roleApplicationHandler = new RoleApplicationHandler(roleApplicationSystemConfig); } /** @@ -122,7 +122,7 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List 0) { @@ -269,8 +269,8 @@ private static MessageEmbed createApplicationEmbed() { .build(); } - public ApplicationApplyHandler getApplicationApplyHandler() { - return applicationApplyHandler; + public RoleApplicationHandler getApplicationApplyHandler() { + return roleApplicationHandler; } @Override diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java similarity index 96% rename from application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java rename to application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java index 98b0d2cbc3..670d40290e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/ApplicationApplyHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java @@ -28,7 +28,7 @@ * that submissions are sent to the appropriate application channel, and enforcing cooldowns for * users to prevent spamming. */ -public class ApplicationApplyHandler { +public class RoleApplicationHandler { private static final int APPLICATION_SUBMIT_COOLDOWN_MINUTES = 5; private final Cache applicationSubmitCooldown; @@ -36,12 +36,12 @@ public class ApplicationApplyHandler { private final RoleApplicationSystemConfig roleApplicationSystemConfig; /** - * Constructs a new {@code ApplicationApplyHandler} instance. + * Constructs a new {@code RoleApplicationHandler} instance. * * @param roleApplicationSystemConfig the configuration that contains the details for the * application form including the cooldown duration and channel pattern. */ - public ApplicationApplyHandler(RoleApplicationSystemConfig roleApplicationSystemConfig) { + public RoleApplicationHandler(RoleApplicationSystemConfig roleApplicationSystemConfig) { this.roleApplicationSystemConfig = roleApplicationSystemConfig; this.applicationChannelPattern = Pattern.compile(roleApplicationSystemConfig.submissionsChannelPattern()) From b6db802262944c22d22a2616a5883d2deb79ec7a Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 12/23] refactor: simplify variable naming Rename the variable name of the class-wide `RoleApplicationSystemConfig` to `config` and the variable name of `RoleApplicationHandler` to `handler`, more simple and recognizable names. The original ones are too verbose and unnecessarily yield long horizontal lines of code where unnecessary. These verbose names do work but only in the situation where there are multiple configurations or handlers used in the same class. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../CreateRoleApplicationCommand.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 2f66e80b7e..7b3be47064 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -50,8 +50,8 @@ public class CreateRoleApplicationCommand extends SlashCommandAdapter { private static final int MINIMUM_ANSWER_LENGTH = 50; private static final int MAXIMUM_ANSWER_LENGTH = 500; - private final RoleApplicationHandler roleApplicationHandler; - private final RoleApplicationSystemConfig roleApplicationSystemConfig; + private final RoleApplicationHandler handler; + private final RoleApplicationSystemConfig config; /** * Constructs a new {@link CreateRoleApplicationCommand} with the specified configuration. @@ -64,10 +64,10 @@ public CreateRoleApplicationCommand(Config config) { super("application-form", "Generates an application form for members to apply for roles.", CommandVisibility.GUILD); - this.roleApplicationSystemConfig = config.getRoleApplicationSystemConfig(); + this.config = config.getRoleApplicationSystemConfig(); generateRoleOptions(getData()); - roleApplicationHandler = new RoleApplicationHandler(roleApplicationSystemConfig); + handler = new RoleApplicationHandler(this.config); } /** @@ -122,7 +122,7 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List 0) { @@ -135,8 +135,8 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 13/23] generateRoleOptions: remove unnecessary parameter As it currently is, the helper method `generateRoleOptions` takes one `SlashCommandData` parameter and is used once throughout the entire project, specifically in the same class it is defined. In that one case, `generateRoleOptions` would take in `getData()` as its input, a method that is accessible through the entire class due to the simple fact that `CreateRoleApplicationCommand` inherits methods from `SlashCommandAdapter`. Modify the `generateRoleOptions` helper method to take no input and inside it, store a reference of `getData()` for the method to make use of. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../roleapplication/CreateRoleApplicationCommand.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 7b3be47064..5818e57be4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -66,16 +66,17 @@ public CreateRoleApplicationCommand(Config config) { this.config = config.getRoleApplicationSystemConfig(); - generateRoleOptions(getData()); + generateRoleOptions(); handler = new RoleApplicationHandler(this.config); } /** - * Populates a {@link SlashCommandData} object with the proper arguments. - * - * @param data the object to populate + * Populates this command's instance {@link SlashCommandData} object with the proper arguments + * for this command. */ - private void generateRoleOptions(SlashCommandData data) { + private void generateRoleOptions() { + final SlashCommandData data = getData(); + IntStream.range(1, OPTIONAL_ROLES_AMOUNT + 1).forEach(index -> { data.addOption(OptionType.STRING, generateOptionId("title", index), "The title of the role"); From 81b6557ac2dfc0709cd23d6526a0c788669eaa33 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 14/23] refactor: make consants more verbose and precise Rename `VALUE_DELIMITER` to `OPTION_PARAM_ID_DELIMITER` and `ARG_COUNT` to `OPTIONS_PER_ROLE` for better and less ambiguous naming. The current ones do not accurately reflect what they are delimiting and where they should be used specifically, so we use more verbose names for that purpose. They are constants that aren't widely used anywhere in major places, though we should still take good care of all naming regardless for maximum code readability. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../roleapplication/CreateRoleApplicationCommand.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 5818e57be4..3f75a27504 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -45,8 +45,8 @@ public class CreateRoleApplicationCommand extends SlashCommandAdapter { protected static final Color AMBIENT_COLOR = new Color(24, 221, 136, 255); private static final int OPTIONAL_ROLES_AMOUNT = 5; private static final String ROLE_COMPONENT_ID_HEADER = "application-create"; - private static final String VALUE_DELIMITER = "_"; - private static final int ARG_COUNT = 3; + private static final String OPTION_PARAM_ID_DELIMITER = "_"; + private static final int OPTIONS_PER_ROLE = 3; private static final int MINIMUM_ANSWER_LENGTH = 50; private static final int MAXIMUM_ANSWER_LENGTH = 500; @@ -88,7 +88,7 @@ private void generateRoleOptions() { } private static String generateOptionId(String name, int id) { - return "%s%s%d".formatted(name, VALUE_DELIMITER, id); + return "%s%s%d".formatted(name, OPTION_PARAM_ID_DELIMITER, id); } @Override @@ -190,7 +190,7 @@ private static long getIncorrectRoleArgsCount(final List args) { args.stream() .map(OptionMapping::getName) - .map(name -> name.split(VALUE_DELIMITER)[1]) + .map(name -> name.split(OPTION_PARAM_ID_DELIMITER)[1]) .forEach(number -> frequencyMap.merge(number, 1, Integer::sum)); return frequencyMap.values().stream().filter(value -> value != 3).count(); @@ -206,7 +206,7 @@ private void addRolesToMenu(StringSelectMenu.Builder menuBuilder, final List args) { final Map roles = new HashMap<>(); - for (int i = 0; i < args.size(); i += ARG_COUNT) { + for (int i = 0; i < args.size(); i += OPTIONS_PER_ROLE) { OptionMapping optionTitle = args.get(i); OptionMapping optionDescription = args.get(i + 1); OptionMapping optionEmoji = args.get(i + 2); From 83df552b1a930620b2fee3070c0fdc47dcba9060 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 15/23] refactor: use OPTIONS_PER_ROLE where needed In some part of the code, we have the `OPTIONS_PER_ROLE` value hardcoded as in return frequencyMap.values().stream().filter(value -> value != 3).count(); Replace the hardcoded 3 with the actual constant we have previously made so that it mirrors any potential changes we make in the future regarding this value (perhaps we decide to remove emojis as arguments and suddenly we find ourselves in a situation where the `OPTIONS_PER_ROLE` constant is now 2. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../features/roleapplication/CreateRoleApplicationCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 3f75a27504..a242d80745 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -193,7 +193,7 @@ private static long getIncorrectRoleArgsCount(final List args) { .map(name -> name.split(OPTION_PARAM_ID_DELIMITER)[1]) .forEach(number -> frequencyMap.merge(number, 1, Integer::sum)); - return frequencyMap.values().stream().filter(value -> value != 3).count(); + return frequencyMap.values().stream().filter(value -> value != OPTIONS_PER_ROLE).count(); } /** From 4690d67b49ff182abc4a58773434bf97b5e5174e Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 16/23] refactor: soften up application message There is currently the following message displayed in an embed to those who make use of the `/application-form` slash command: We are always looking for community members that want to contribute to our community and take charge. If you are interested, you can apply for various positions here! This is too hardened and serious according to feedback. Add a smiley face in the form of a cool sunglasses emoji to soften it up. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../features/roleapplication/CreateRoleApplicationCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index a242d80745..c7ddbfd34e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -265,7 +265,7 @@ private static MessageEmbed createApplicationEmbed() { .setDescription( """ We are always looking for community members that want to contribute to our community \ - and take charge. If you are interested, you can apply for various positions here!""") + and take charge. If you are interested, you can apply for various positions here! 😎""") .setColor(AMBIENT_COLOR) .build(); } From 98cdec5c043bf1f8efb921b1c51850aa30e5f49c Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 17/23] refactor: remove unneeded word plural helper method The `selectWordFromCount` helper method, while serving a seemingly helpful purpose, is only used once and is also private to be used in the same class only. Besides the fact that we can technically move it somewhere more appropriate and increase its visibility so other classes can utilize it, remove the helepr method and resort to ternary operators for that one time we need to determine the plurarity of the word "minute" in our `CreateRoleApplicationCommand` class. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../CreateRoleApplicationCommand.java | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index c7ddbfd34e..819a5bd14a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -124,7 +124,7 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List 0) { event @@ -161,23 +161,6 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 18/23] refactor: move `correctMinutesWord` inside block Move the `correctMinutesWord` string inside the if-statement below it due to the fact that that is the only place where that variable is being used. `remainingMinutes` is left intact in the same scope since it is needed as a reference in the if-statement itself to compare against zero. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../roleapplication/CreateRoleApplicationCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 819a5bd14a..22428b952d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -124,9 +124,9 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List 0) { + String correctMinutesWord = remainingMinutes == 1 ? "minute" : "minutes"; + event .reply("Please wait %d %s before sending a new application form." .formatted(remainingMinutes, correctMinutesWord)) From 643d6004823485c464c641400a83f82dd225710a Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 19/23] refactor: move `setTimestamp()` after `setFooter()` Move the call chain order to be ordered in such a way so that the story telling is being told better and so that the code is more readable. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../features/roleapplication/RoleApplicationHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java index 670d40290e..df4a921bfa 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java @@ -80,8 +80,8 @@ protected void sendApplicationResult(final ModalInteractionEvent event, List Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 20/23] refactor: handle `roleString` from early on Get a reference of `roleString` as early as possible, and be more explicit about which argument we want to get from the arguments list. At any given point (think, a future update to some library like JDA), the list we are getting all of our arguments from could end up looking from this: [1192015871509315, "Role String"] to: [1192015871509315, "Role String", 24] Therefore, making `args.getLast()` entirely obsolete. Being more explicit about the argument number we would like to get reduces the chances of something like this happening. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../tjbot/features/roleapplication/RoleApplicationHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java index df4a921bfa..267ab175f7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java @@ -71,6 +71,8 @@ protected void sendApplicationResult(final ModalInteractionEvent event, List applicationChannel = getApplicationChannel(guild); if (applicationChannel.isEmpty()) { return; @@ -83,7 +85,6 @@ protected void sendApplicationResult(final ModalInteractionEvent event, List Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 21/23] refactor(application-form): better exception handling There are currently many undesired conditions the code could run into when it comes to the `RoleApplicationHandler` which we would not desire and we would not be prepared for, particularly in situations where: - We would somehow find ourselves handling application form submissions in a non-guild environment. - The arguments size including the clicked component ID as well as the role name (the title of that component ID in essence) would have an unexpected size. - The staff-monitored application submissions text channel would not be found by the bot. We do not have entire control over these cases, but we need to handle them properly and set ourselves up for success in the rare case they happen. Make `sendApplicationResult` throw an `IllegalArgumentException` with descriptive messages if anything goes wrong, and log appropriate stacktraces if anything goes wrong in the logger for easy debugging. I thought about printing straight to the logger and not using any `IllegalArgumentException` in the code (or at least, using a different or maybe custom `Exception` but that would be too much), but with this method, we get to see custom messages along with exceptions for each individual case - all that without unnecessarily overcomplicating the code and sacrificing its readability to a considerable extent. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../RoleApplicationHandler.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java index 267ab175f7..958d7afc29 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java @@ -10,6 +10,8 @@ import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.RoleApplicationSystemConfig; @@ -29,8 +31,9 @@ * users to prevent spamming. */ public class RoleApplicationHandler { - private static final int APPLICATION_SUBMIT_COOLDOWN_MINUTES = 5; + private static final Logger logger = LoggerFactory.getLogger(RoleApplicationHandler.class); + private static final int APPLICATION_SUBMIT_COOLDOWN_MINUTES = 5; private final Cache applicationSubmitCooldown; private final Predicate applicationChannelPattern; private final RoleApplicationSystemConfig roleApplicationSystemConfig; @@ -65,17 +68,26 @@ public RoleApplicationHandler(RoleApplicationSystemConfig roleApplicationSystemC * @param answer the answer provided by the applicant to the default question */ protected void sendApplicationResult(final ModalInteractionEvent event, List args, - String answer) { + String answer) throws IllegalArgumentException { Guild guild = event.getGuild(); - if (args.size() != 2 || guild == null) { - return; + + if (guild == null) { + throw new IllegalArgumentException( + "sendApplicationResult() got fired in a non-guild environment."); + } + + if (args.size() != 2) { + throw new IllegalArgumentException( + "Received application result after user submitted one, and did not receive 2 arguments. Args: " + + args); } String roleString = args.get(1); Optional applicationChannel = getApplicationChannel(guild); if (applicationChannel.isEmpty()) { - return; + throw new IllegalArgumentException("Application channel %s could not be found." + .formatted(roleApplicationSystemConfig.submissionsChannelPattern())); } User applicant = event.getUser(); @@ -125,10 +137,17 @@ protected void submitApplicationFromModalInteraction(ModalInteractionEvent event ModalMapping modalAnswer = event.getValues().getFirst(); - sendApplicationResult(event, args, modalAnswer.getAsString()); - event.reply("Your application has been submitted. Thank you for applying! 😎") - .setEphemeral(true) - .queue(); + try { + sendApplicationResult(event, args, modalAnswer.getAsString()); + event.reply("Your application has been submitted. Thank you for applying! 😎") + .setEphemeral(true) + .queue(); + } catch (IllegalArgumentException e) { + logger.error("A role application could not be submitted. ", e); + event.reply("Your application could not be submitted. Please contact the staff team.") + .setEphemeral(true) + .queue(); + } applicationSubmitCooldown.put(event.getMember(), OffsetDateTime.now()); } From c4aeb1a7a2b7da69b9df5003a5d0231386fb52c6 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 22/23] refactor(application-form): reduce visibility The `RoleApplicationHandler` class is not making any use of inheritance as suggested by @Zabuzard and what's more, certain methods are unnecessarily marked as protected. Reduce visibility of methods marked as protected and make the `RoleApplicationHandler` class final. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../features/roleapplication/RoleApplicationHandler.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java index 958d7afc29..4e8e45bc08 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/RoleApplicationHandler.java @@ -30,7 +30,7 @@ * that submissions are sent to the appropriate application channel, and enforcing cooldowns for * users to prevent spamming. */ -public class RoleApplicationHandler { +public final class RoleApplicationHandler { private static final Logger logger = LoggerFactory.getLogger(RoleApplicationHandler.class); private static final int APPLICATION_SUBMIT_COOLDOWN_MINUTES = 5; @@ -67,7 +67,7 @@ public RoleApplicationHandler(RoleApplicationSystemConfig roleApplicationSystemC * @param args the arguments provided in the application submission * @param answer the answer provided by the applicant to the default question */ - protected void sendApplicationResult(final ModalInteractionEvent event, List args, + private void sendApplicationResult(final ModalInteractionEvent event, List args, String answer) throws IllegalArgumentException { Guild guild = event.getGuild(); @@ -127,8 +127,7 @@ public Cache getApplicationSubmitCooldown() { return applicationSubmitCooldown; } - protected void submitApplicationFromModalInteraction(ModalInteractionEvent event, - List args) { + void submitApplicationFromModalInteraction(ModalInteractionEvent event, List args) { Guild guild = event.getGuild(); if (guild == null) { @@ -152,7 +151,7 @@ protected void submitApplicationFromModalInteraction(ModalInteractionEvent event applicationSubmitCooldown.put(event.getMember(), OffsetDateTime.now()); } - protected long getMemberCooldownMinutes(Member member) { + long getMemberCooldownMinutes(Member member) { OffsetDateTime timeSentCache = getApplicationSubmitCooldown().getIfPresent(member); if (timeSentCache != null) { Duration duration = Duration.between(timeSentCache, OffsetDateTime.now()); From 4d2673d1173ab86dbcc4bb0db87dae19372eceb5 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Tue, 1 Jul 2025 15:45:38 +0300 Subject: [PATCH 23/23] log(application-command): handle null cases During the event `onStringSelectSelection` which would be called when someone would select one role in the application form, there would be a possibility for: - The `member` to be null (most likely from interacting in a non-guild environment, which is rare and almost impossible). - `event.getSelectedOptions()` to be an empty list, effectively meaning that a form was somehow made with no options and a user (or should we say member in our case) managed to interact with it. In any case, properly log errors in the logger in case either of these happen to be null or otherwise have unexpected values. Suggested-by: Zabuzard Signed-off-by: Chris Sdogkos --- .../CreateRoleApplicationCommand.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java index 22428b952d..984e3b4ee6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/roleapplication/CreateRoleApplicationCommand.java @@ -20,6 +20,8 @@ import net.dv8tion.jda.api.interactions.components.text.TextInput; import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; import net.dv8tion.jda.api.interactions.modals.Modal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.RoleApplicationSystemConfig; @@ -53,6 +55,9 @@ public class CreateRoleApplicationCommand extends SlashCommandAdapter { private final RoleApplicationHandler handler; private final RoleApplicationSystemConfig config; + private static final Logger logger = + LoggerFactory.getLogger(CreateRoleApplicationCommand.class); + /** * Constructs a new {@link CreateRoleApplicationCommand} with the specified configuration. *

@@ -119,7 +124,13 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List